Search code examples
javajunitclassloadercode-generationbyte-buddy

ByteBuddy: How to test with classes from java.*


I'm trying to write a UnitTest for an agent that intercepts calls to classes from java.io (e.g. FileInputStream).

I've followed the hints given here and had a look at ByteBuddys own UnitTests.

private ClassLoader classLoader;

@Before
@AgentAttachmentRule.Enforce
public void setUp() throws Exception
{
    classLoader = new ByteArrayClassLoader.ChildFirst(getClass().getClassLoader(),
                                           ClassFileExtraction.of(FileInputStream.class),
                                           DEFAULT_PROTECTION_DOMAIN,
                                           AccessController.getContext(),
                                           ByteArrayClassLoader.PersistenceHandler.MANIFEST,
                                           PackageDefinitionStrategy.NoOp.INSTANCE);
}

@Test
public void testAgentForFileInputStream() throws Exception
{
    MyAgent.premain("");
    Class<?> type = classLoader.loadClass(FileInputStream.class.getName());
    type.getDeclaredMethod("open").invoke("test");
}

A similar test for non-java.* classes works fine, however here I receive:

java.lang.SecurityException: Prohibited package name: java.io

at java.lang.ClassLoader.preDefineClass(ClassLoader.java:659)
at java.lang.ClassLoader.defineClass(ClassLoader.java:758)
at net.bytebuddy.dynamic.loading.ByteArrayClassLoader.findClass(ByteArrayClassLoader.java:197)
at net.bytebuddy.dynamic.loading.ByteArrayClassLoader$ChildFirst.loadClass(ByteArrayClassLoader.java:554)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)

I've tried setting the SecurityManager to null, without success:

System.setSecurityManager(null);

If I am not mistaken, intercepting such classes should be possible. This might be due to the use of a custom ClassLoader. However I don't know what needs to be changed...


Solution

  • It is not possible to load any class with the java. prefix from any class loader but the bootstrap class loader. This restriction is not enforced by a SecurityManager but hard-coded into the ClassLoader implementation.

    The only way to test these classes is to:

    1. Adjust the name during the testing process to allow loading in another class loader.
    2. Load the classes into the bootstrap class loader (using the ByteBuddyAgent and ClassInjector.ForInstrumentation). You should still randomize the names to guarantee test repeatability.

    In your case, only option 2 seems feasible. As I do not know what your agent is doing, I can only assume that you try to manipulate the FileInputStream class where you would need to use bootstrap loader injection in any case as the a replication of this class would not work with the class's native method adapters.