jqwik: JUnit tests fail with InaccessibleObjectException with version 1.7
Testing Problem
In our project we are running Junit tests that are using jqwik on the JPMS module path.
While on version 1.6.5, all these tests execute successfully.
However, after upgrading to version 1.7.0, several InaccessibleObjectException
s are thrown during test execution:
java.lang.reflect.InaccessibleObjectException:
Unable to make field private final java.util.function.Predicate java.util.function.Predicate$$Lambda$219/0x00000007c014e7b0.arg$1 accessible:
module java.base does not "opens java.util.function" to module net.jqwik.api
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178)
at java.base/java.lang.reflect.Field.setAccessible(Field.java:172)
at net.jqwik.api@1.7.0/net.jqwik.api.support.LambdaSupport.fieldIsEqualIn(LambdaSupport.java:55)
at net.jqwik.api@1.7.0/net.jqwik.api.support.LambdaSupport.areEqual(LambdaSupport.java:40)
at net.jqwik.engine@1.7.0/net.jqwik.engine.properties.arbitraries.ArbitraryFilter.equals(ArbitraryFilter.java:48)
at java.base/java.util.HashMap.getNode(HashMap.java:570)
at java.base/java.util.LinkedHashMap.get(LinkedHashMap.java:441)
at net.jqwik.engine@1.7.0/net.jqwik.engine.facades.SampleStreamFacade.getGenerator(SampleStreamFacade.java:38)
at net.jqwik.engine@1.7.0/net.jqwik.engine.facades.SampleStreamFacade.lambda$getGeneratorForSampling$0(SampleStreamFacade.java:33)
at net.jqwik.engine@1.7.0/net.jqwik.engine.execution.lifecycle.CurrentTestDescriptor.runWithDescriptor(CurrentTestDescriptor.java:17)
at net.jqwik.engine@1.7.0/net.jqwik.engine.facades.SampleStreamFacade.runInDescriptor(SampleStreamFacade.java:60)
at net.jqwik.engine@1.7.0/net.jqwik.engine.facades.SampleStreamFacade.getGeneratorForSampling(SampleStreamFacade.java:33)
at net.jqwik.engine@1.7.0/net.jqwik.engine.facades.SampleStreamFacade.sampleStream(SampleStreamFacade.java:67)
at net.jqwik.engine@1.7.0/net.jqwik.engine.facades.ArbitraryFacadeImpl.sampleStream(ArbitraryFacadeImpl.java:64)
at net.jqwik.api@1.7.0/net.jqwik.api.Arbitrary.sampleStream(Arbitrary.java:387)
at net.jqwik.api@1.7.0/net.jqwik.api.Arbitrary.sample(Arbitrary.java:419)
In the newly introduced LambdaSupport
class, field values of incoming objects are read using reflection. This is a practice JPMS blocks to prevent unwanted access to a class’s internals.
In this particular case, the JDK does not allow to inspect classes in the java.util.function
package.
Suggested Solution
From a technical point of view, there are some ideas to solve this issue. However, I am not sure what the implications are for the jqwik functionalities that use the LambdaSupport
class:
1. Avoid reflection
Avoid using reflection at all, rewrite the functionality using other techniques. This solution might break some use cases of the new jqwik functionality or requires to make some breaking changes, which you probably would like to avoid.
2. MethodHandles
Replace some reflection calls with their newer alternatives: MethodHandles
and VarHandle
. Although I have little to no experience with these alternatives, I quickly tried this approach:
private static boolean fieldIsEqualIn(Field field, Object left, Object right) {
MethodHandles.Lookup lookup = MethodHandles.lookup();
try {
VarHandle handle = lookup.unreflectVarHandle(field);
// If field is a functional type use LambdaSupport.areEqual().
// TODO: Could there be circular references among functional types?
if (isFunctionalType(field.getType())) {
return areEqual(handle.get(left), handle.get(right));
}
return handle.get(left).equals(handle.get(right));
} catch (IllegalAccessException e) {
e.printStackTrace();
return false;
}
}
This resulted in a IllegalAccessException
, but much closer to my own code:
java.lang.IllegalAccessException: class is not public:
a.b.c.MyOwnProvider$$Lambda$718/0x00000007c02d64e8.arg$1/int/getField,
from class net.jqwik.api.support.LambdaSupport (module net.jqwik.api)
With some additional experimenting, I am hopeful to make it work properly.
3. Update current implementation
Keep the current implementation, but prevent it from crawling into the internals of the JDK.
Discussion
Until now, I only performed some superficial research of this issue. If time permits, I’ll try to provide a reproducible test setup.
Also, I will try to dig a little deeper into the second solution (MethodHandles
).
For now, we will stay on version 1.6.5.
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 21 (5 by maintainers)
Commits related to this issue
- Fixed reflection bug. See https://github.com/jlink/jqwik/issues/393 — committed to jqwik-team/jqwik by jlink 2 years ago
The fix is present in 1.7.1-SNAPSHOT. Please try it out.
@jlink You might want to consider accepting an instance of java.lang.invoke.MethodHandles.Lookup in
LambdaSupport
’s methods. This way an author of a test module may create and pass an instance with sufficient access rights.