Hi Rafael,

I see that mocking/proxying/testing framework should be looked at separately since its requirements and approaches can be different than tool agents.

On 4/17/18 5:06 AM, Rafael Winterhalter wrote:
Hei Mandy,

I have looked into several Java agents that I have worked on and for many of them, this API does unfortunately not supply sufficient access. I would therefore still prefer a method Instrumentation::defineClass.

The problem is that some agents need to define classes in other packages then in that of the instrumented class. For example, I might need to enhance a library that defines a set of callback classes in package A. All these classes share a common super class with a package-private constructor. I want to instrument some class in package B to use a callback that the library does not supply and need to add a new callback class to A. This is not possible using the current API.


Are these callback classes made available statically?  or just dynamically defining additional class as needed?  Is Lookup::defineClass an alternative if you get a hold of common super class in A?

I could however achieve do so by calling Instrumentation::retransform on one of the classes in A after registering a class file transformer. Once the retransformation is triggered, I can now define a class in A. Of course this is inefficient and I would rather open the jdk.internal.misc module and use the "old" API instead.

For this reason, I argue that this rather restrained API is not convenient while it does not add anything to security. Also, for the use case of Mockito, this would neither be sufficient as Mockito sometimes redefines classes and sometimes adds a subclass without retransforming. We would rather have direct access to class definition once we are already running with the privileges of a Java agent.

I would therefore suggest to add a method:

interface Instrumentation {
  Class<?> defineClass(byte[] bytes, ProtectionDomain pd);
}

which can be implemented simply by delegating to jdk.internal.misc.Unsafe.

On a side note. Does JavaLangAccess::defineClass work with the bootstrap class loader? I have not tried it but I always thought it was just an access layer for the class loader API that cannot access the null value.


The JVM entry point does allow null loader.

Mandy

Thanks for considering this use case!
Best regards, Rafael

2018-04-15 8:23 GMT+02:00 mandy chung <mandy.ch...@oracle.com <mailto:mandy.ch...@oracle.com>>:

    Background:

    Java agents support both load time and dynamic instrumentation.  
    At load time,
    the agent's ClassFileTransformer is invoked to transform class
    bytes.  There is
    no Class objects at this time.  Dynamic instrumentation is when
    redefineClasses
    or retransformClasses is used to redefine an existing loaded
    class.  The
    ClassFileTransformer is invoked with class bytes where the Class
    object is present.

    Java agent doing instrumentation needs a means to define auxiliary
    classes
    that are visible and accessible to the instrumented class. 
    Existing agents
    have been using sun.misc.Unsafe::defineClass to define aux classes
    directly
    or accessing protected ClassLoader::defineClass method with
    setAccessible to
    suppress the language access check (see [1] where this issue was
    brought up).

    Instrumentation::appendToBootstrapClassLoaderSearch and
    appendToSystemClassLoaderSearch
    APIs are existing means to supply additional classes.  It's too
    limited
    for example it can't inject a class in the same runtime package as
    the class
    being transformed.

    Proposal:

    This proposes to add a new ClassFileTransformer.transform method
    taking additional ClassDefiner parameter.  A transformer can
    define additional
    classes during the transformation process, i.e.
    when ClassFileTransformer::transform is invoked. Some details:

    1. ClassDefiner::defineClass defines a class in the same runtime
    package
       as the class being transformed.
    2. The class is defined in the same thread as the transformers are
    being
       invoked.   ClassDefiner::defineClass returns Class object directly
       before the transformed class is defined.
    3. No transformation is applied to classes defined by
    ClassDefiner::defineClass.

    The first prototype we did is to collect the auxiliary classes and
    define
    them  until all transformers are invoked and have these aux
    classes to go
    through the transformation pipeline.  Several complicated issues
    would
    need to be resolved for example timing whether the auxiliary
    classes should
    be defined before the transformed class (otherwise a potential
    race where
    some other thread references the transformed class and cause the
    code to
    execute that in turn reference the auxiliary classes. The current
    implementation has a native reentrancy check that ensure one class
    is being
    transformed to avoid potential circularity issues.  This may need
    JVM TI
    support to be reliable.

    This proposal would allow java agents to migrate from internal API
    and ClassDefiner to be enhanced in the future.

    Webrev:
    http://cr.openjdk.java.net/~mchung/jdk11/webrevs/8200559/webrev.00/
    <http://cr.openjdk.java.net/%7Emchung/jdk11/webrevs/8200559/webrev.00/>

    Mandy
    [1]
    http://mail.openjdk.java.net/pipermail/jdk-dev/2018-January/000405.html
    <http://mail.openjdk.java.net/pipermail/jdk-dev/2018-January/000405.html>



Reply via email to