java.lang.reflect.TypeVariable getBounds is not thread safe. Calling it from multiple threads might even crash your JVM

The following method shows you the use of getBounds(). The method getBounds is used to get the upper bound(s) of a generic type:

public void testGetBounds() { Class cl = GenericInterface.class; TypeVariable typeVariable = cl.getTypeParameters()[0]; typeVariable.getBounds()[0].getTypeName(); }

To make the examples and tests shorter, I do not iterate over the returned array but simply use the first element. Here is the generic interface used in the example:

package com.vmlens.stressTest.util; public interface GenericInterface> { }

If you call testGetBounds from multiple threads, calling getBounds leads to a race condition.

The race condition

package sun.reflect.generics.reflectiveObjects; // import statements omitted public class TypeVariableImpl extends LazyReflectiveObjectGenerator implements TypeVariable { // upper bounds - evaluated lazily private Type[] bounds; public Type[] getBounds() { // lazily initialize bounds if necessary if (bounds == null) { FieldTypeSignature[] fts = getBoundASTs(); // get AST // allocate result array; note that // keeping ts and bounds separate helps with threads Type[] ts = new Type[fts.length]; // iterate over bound trees, reifying each in turn for ( int j = 0; j < fts.length; j++) { Reifier r = getReifier(); fts[j].accept(r); ts[j] = r.getResult(); } // cache result bounds = ts; // could throw away bound ASTs here; thread safety? } return bounds.clone(); // return cached bounds } // other fields and methods omitted }

Since the array of TypeVariables returned by getTypeParameters is cached in the volatile field genericInfo, each thread works on the same TypeVariable instance. And the class TypeVariableImpl implementing the TypeVariable interface modifies the not volatile field bounds without synchronization:

In line 15 and 30 the field bounds is read and in line 27 it is written. If the code is executed in the given order everything is o.k. But if some component reorders the statements, the array is not completely initialized. In pseudo code the method getBounds looks like this:

if instance variable bounds is null { set local variable ts to new Array initialize the array set instance variable bounds to the local variable ts } return instance variable bounds.clone

If the statements get reordered another thread sees an uninitialised array:

Thread A set local variable ts to new Array Thread A set instance variable bounds to the local variable ts Thread B if instance variable bounds is null Thread B Thread B return instance variable bounds.clone // the array is not yet completely initialized

One such component is the cache system of the CPU. ARM compatible processors like in smartphones or the Raspberry Pi reorder reads and writes to improve performance, leading to a scenario as described above.

Reproducing the error

The Java Concurrency Stress tests (jcstress) is an experimental harness and a suite of tests to aid the research in the correctness of concurrency support in the JVM, class libraries, and hardware.

To reproduce the error I use jcstress , an open JDK code tool:

I use the following test class:

@JCStressTest @Outcome(id = "0, 0", expect = Expect.ACCEPTABLE, desc = "Default outcome.") @State public class TypeVariableGetBounds { private final Class cl; public TypeVariableGetBounds() { try { cl = (new StressTestClassLoader(TypeVariableGetBounds.class.getClassLoader())) .loadClass("com.vmlens.stressTest.util.GenericInterface"); } catch (Exception e) { throw new RuntimeException("Test setup incorrect", e); } } public void callContainsDataRace() { TypeVariable typeVariable = cl.getTypeParameters()[0]; typeVariable.getBounds()[0].getTypeName(); } @Actor public void actor1(IntResult2 r) { callContainsDataRace(); } @Actor public void actor2(IntResult2 r) { callContainsDataRace(); } }

Jcstress runs this test multiple times always calling the method actor1 and actor2 from separate threads. To separate the tests, I use a special classloader, which always reloads a class.

When I call this test on a raspberry pi using the test mode tough or stress, I see a crash of the JVM:

# # A fatal error has been detected by the Java Runtime Environment: # # SIGSEGV (0xb) at pc=0x7665b4c0, pid=16112, tid=1680598112 # # JRE version: Java(TM) SE Runtime Environment (8.0_65-b17) (build 1.8.0_65-b17) # Java VM: Java HotSpot(TM) Client VM (25.65-b01 mixed mode linux-arm ) # Problematic frame: # V [libjvm.so+0x27a4c0]

Stack: [0x6426f000,0x642bf000], sp=0x642bd8b8, free space=314k Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code) V [libjvm.so+0x27a4c0] Java frames: (J=compiled Java code, j=interpreted, Vv=VM code) J 1303 java.lang.Object.clone()Ljava/lang/Object; (0 bytes) @ 0x742ba71c [0x742ba6e0+0x3c] J 1313 C1 sun.reflect.generics.repository.GenericDeclRepository.getTypeParameters()[Ljava/lang/reflect/TypeVariable; (80 bytes) @ 0x742b8210 [0x742b7f40+0x2d0] J 1229 C1 com.vmlens.stressTest.tests.TypeVariableGetBounds_jcstress.actor1()Ljava/lang/Void; (109 bytes) @ 0x742a6cb8 [0x742a6bf0+0xc8] j com.vmlens.stressTest.tests.TypeVariableGetBounds_jcstress$$Lambda$6.call()Ljava/lang/Object;+4 j java.util.concurrent.FutureTask.run()V+42 j java.util.concurrent.ThreadPoolExecutor.runWorker(Ljava/util/concurrent/ThreadPoolExecutor$Worker;)V+95 j java.util.concurrent.ThreadPoolExecutor$Worker.run()V+5 j java.lang.Thread.run()V+11 v ~StubRoutines::call_stub

The JVM error log shows the following:

It seems that the native array clone method can not cope with an uninitialized array.

So far I could not reproduce this error on my intel i5 workstation.

Conclusion

java.lang.reflect.TypeVariable getBounds is not thread safe. Calling it from multiple threads might lead, depending on the java platform you are using, to strange errors.