cglib: ReflectUtils breaks with Java 9 build 148+

Since Java 9 build 148, it is no longer possible to do setAccessible on any public Java runtime API class (except sun.misc.Unsafe as special case). This affects ClassLoader#defineClass (protected method).

Because of this any mocking library (Mockito, EasyMock,…) that uses CGLIB breaks with ExceptionOnInitializer.

Example in Apache Solr (Mockito/Easymock):

   [junit4] ERROR   0.38s J2 | TestManagedSchemaThreadSafety.testThreadSafety <<<
   [junit4]    > Throwable #1: java.lang.ExceptionInInitializerError
   [junit4]    >        at __randomizedtesting.SeedInfo.seed([8E654E5E1F32A757:142F5A06CCAD15A1]:0)
   [junit4]    >        at org.mockito.cglib.core.KeyFactory$Generator.generateClass(KeyFactory.java:167)
   [junit4]    >        at org.mockito.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
   [junit4]    >        at org.mockito.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:217)
   [junit4]    >        at org.mockito.cglib.core.KeyFactory$Generator.create(KeyFactory.java:145)
   [junit4]    >        at org.mockito.cglib.core.KeyFactory.create(KeyFactory.java:117)
   [junit4]    >        at org.mockito.cglib.core.KeyFactory.create(KeyFactory.java:109)
   [junit4]    >        at org.mockito.cglib.core.KeyFactory.create(KeyFactory.java:105)
   [junit4]    >        at org.mockito.cglib.proxy.Enhancer.<clinit>(Enhancer.java:70)
   [junit4]    >        at org.mockito.internal.creation.jmock.ClassImposterizer.createProxyClass(ClassImposterizer.java:85)
   [junit4]    >        at org.mockito.internal.creation.jmock.ClassImposterizer.imposterise(ClassImposterizer.java:62)
   [junit4]    >        at org.mockito.internal.creation.jmock.ClassImposterizer.imposterise(ClassImposterizer.java:56)
   [junit4]    >        at org.mockito.internal.creation.CglibMockMaker.createMock(CglibMockMaker.java:23)
   [junit4]    >        at org.mockito.internal.util.MockUtil.createMock(MockUtil.java:26)
   [junit4]    >        at org.mockito.internal.MockitoCore.mock(MockitoCore.java:51)
   [junit4]    >        at org.mockito.Mockito.mock(Mockito.java:1243)
   [junit4]    >        at org.apache.solr.schema.TestManagedSchemaThreadSafety.createZkController(TestManagedSchemaThreadSafety.java:135)
   [junit4]    >        at org.apache.solr.schema.TestManagedSchemaThreadSafety.testThreadSafety(TestManagedSchemaThreadSafety.java:118)
   [junit4]    >        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
   [junit4]    >        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
   [junit4]    >        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
   [junit4]    >        at java.base/java.lang.reflect.Method.invoke(Method.java:538)
   [junit4]    >        at java.base/java.lang.Thread.run(Thread.java:844)
   [junit4]    > Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @4cbd6df7
   [junit4]    >        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:207)
   [junit4]    >        at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:192)
   [junit4]    >        at java.base/java.lang.reflect.Method.setAccessible(Method.java:186)
   [junit4]    >        at org.mockito.cglib.core.ReflectUtils$2.run(ReflectUtils.java:57)
   [junit4]    >        at java.base/java.security.AccessController.doPrivileged(Native Method)
   [junit4]    >        at org.mockito.cglib.core.ReflectUtils.<clinit>(ReflectUtils.java:47)
   [junit4]    >        ... 55 more
   [junit4]   2> 64469 INFO  (SUITE-TestManagedSchemaThreadSafety-seed#[8E654E5E1F32A757]-worker) [    ] o.a.s.c.ZkTestServer connecting to 127.0.0.1:51648 51648
   [junit4]   2> 64474 INFO  (Thread-158) [    ] o.a.s.c.ZkTestServer connecting to 127.0.0.1:51648 51648
   [junit4]   2> 64478 INFO  (SUITE-TestManagedSchemaThreadSafety-seed#[8E654E5E1F32A757]-worker) [    ] o.a.s.SolrTestCaseJ4 ###deleteCore
   [junit4]   2> NOTE: leaving temporary files on disk at: C:\Users\Uwe Schindler\Projects\lucene\trunk-lusolr1\solr\build\solr-core\test\J2\temp\solr.schema.TestManagedSchemaThreadSafety_8E654E5E1F32A757-001
   [junit4]   2> Dez. 26, 2016 8:47:14 NACHM. com.carrotsearch.randomizedtesting.ThreadLeakControl checkThreadLeaks
   [junit4]   2> WARNUNG: Will linger awaiting termination of 2 leaked thread(s).
   [junit4]   2> NOTE: test params are: codec=Lucene70, sim=RandomSimilarity(queryNorm=false): {}, locale=so-KE, timezone=Etc/GMT+4
   [junit4]   2> NOTE: Windows 10 10.0 amd64/Oracle Corporation 9-ea (64-bit)/cpus=4,threads=1,free=188820656,total=266338304
   [junit4]   2> NOTE: All tests run in this JVM: [TestRandomFaceting, TestCharFilters, BlockJoinFacetSimpleTest, SolrCmdDistributorTest, TestTolerantUpdateProcessorRandomCloud, TestManagedSchemaThreadSafety]
   [junit4] Completed [31/670 (1!)] on J2 in 0.83s, 1 test, 1 error <<< FAILURES!

I have no idea why CGLib needs to do this (instead of just subclassing ClassLoader). I have the feeling that mocking libs need to “inject” classes into existing classloaders, so the workaround is needed.

A trick to solve this might be using MethodHandles instead of Reflection using a “temporary” child class of ClassLoader (just to get a MethodHandle to the defineClass method). But this would require minimum Java 7. I may provide a PR to do this.

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 29 (12 by maintainers)

Most upvoted comments

It’s not that easy. Creating a new class loader causes the generated code to be loaded by another class loader. Even if the class’s package name is the same, the loaded class will exist in a different runtime package what limits the scope of classes that can be subclassed. There is no backwards-compatible way for us to resolve this.

This issue is not easy to workaround and I still hoping that this will not make the final release of Java 9, this change would break thousands of projects. For now, I suggest to wait and see as creating a new class loader every time is a very breaking change.

This is also a major performance issue as class loaders are quite expensive to create.

As for Mockito: It does no longer use cglib in more recent versions but the problem prevails for any other code generation library.

I stumbled upon the same problem, would have been too good to be true.