Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IncompatibleClassChangeError on sealed type fields #643

Closed
Stephan202 opened this issue May 8, 2022 · 5 comments
Closed

IncompatibleClassChangeError on sealed type fields #643

Stephan202 opened this issue May 8, 2022 · 5 comments

Comments

@Stephan202
Copy link
Contributor

Stephan202 commented May 8, 2022

(This issue relates to #564 and possibly #638.)

If one applies EqualsVerifier to a class with a field of some sealed type, then an exception is thrown:

	...
Caused by: java.lang.IllegalStateException: java.lang.IllegalStateException: Failed to invoke proxy for public abstract java.lang.Class net.bytebuddy.dynamic.loading.ClassInjector$UsingLookup$MethodHandles$Lookup.defineClass(java.lang.Object,byte[]) throws java.lang.IllegalAccessException
	at net.bytebuddy.dynamic.loading.ClassInjector$UsingLookup.injectRaw(ClassInjector.java:1640)
	at net.bytebuddy.dynamic.loading.ClassInjector$AbstractBase.inject(ClassInjector.java:118)
	at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$UsingLookup.load(ClassLoadingStrategy.java:519)
	at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:101)
	at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:6166)
	at nl.jqno.equalsverifier.internal.reflection.Instantiator.giveDynamicSubclass(Instantiator.java:98)
	at nl.jqno.equalsverifier.internal.reflection.Instantiator.of(Instantiator.java:47)
	at nl.jqno.equalsverifier.internal.reflection.ClassAccessor.buildObjectAccessor(ClassAccessor.java:261)
	at nl.jqno.equalsverifier.internal.reflection.ClassAccessor.getRedAccessor(ClassAccessor.java:179)
	at nl.jqno.equalsverifier.internal.reflection.ClassAccessor.getRedObject(ClassAccessor.java:168)
	at nl.jqno.equalsverifier.internal.prefabvalues.factories.FallbackFactory.giveInstances(FallbackFactory.java:95)
	at nl.jqno.equalsverifier.internal.prefabvalues.factories.FallbackFactory.createValues(FallbackFactory.java:40)
	at nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues.createTuple(PrefabValues.java:157)
	at nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues.realizeCacheFor(PrefabValues.java:140)
	at nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues.giveTuple(PrefabValues.java:81)
	at nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues.giveOther(PrefabValues.java:104)
	at nl.jqno.equalsverifier.internal.reflection.FieldModifier.lambda$changeField$2(FieldModifier.java:97)
	at nl.jqno.equalsverifier.internal.reflection.FieldModifier.wrappedChange(FieldModifier.java:118)
	at nl.jqno.equalsverifier.internal.reflection.FieldModifier.lambda$change$3(FieldModifier.java:113)
	at nl.jqno.equalsverifier.internal.util.Rethrow.lambda$rethrow$0(Rethrow.java:47)
	at nl.jqno.equalsverifier.internal.util.Rethrow.rethrow(Rethrow.java:30)
	at nl.jqno.equalsverifier.internal.util.Rethrow.rethrow(Rethrow.java:45)
	at nl.jqno.equalsverifier.internal.util.Rethrow.rethrow(Rethrow.java:55)
	at nl.jqno.equalsverifier.internal.reflection.FieldModifier.change(FieldModifier.java:113)
	at nl.jqno.equalsverifier.internal.reflection.FieldModifier.changeField(FieldModifier.java:100)
	at nl.jqno.equalsverifier.internal.reflection.InPlaceObjectAccessor.scramble(InPlaceObjectAccessor.java:52)
	at nl.jqno.equalsverifier.internal.reflection.ClassAccessor.getRedAccessor(ClassAccessor.java:179)
	at nl.jqno.equalsverifier.internal.reflection.ClassAccessor.getRedObject(ClassAccessor.java:168)
	at nl.jqno.equalsverifier.internal.util.Configuration.ensureUnequalExamples(Configuration.java:199)
	at nl.jqno.equalsverifier.internal.util.Configuration.build(Configuration.java:99)
	at nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.buildConfig(SingleTypeEqualsVerifierApi.java:381)
	at nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.performVerification(SingleTypeEqualsVerifierApi.java:367)
	at nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.verify(SingleTypeEqualsVerifierApi.java:312)
	... 66 more
Caused by: java.lang.IllegalStateException: Failed to invoke proxy for public abstract java.lang.Class net.bytebuddy.dynamic.loading.ClassInjector$UsingLookup$MethodHandles$Lookup.defineClass(java.lang.Object,byte[]) throws java.lang.IllegalAccessException
	at net.bytebuddy.utility.dispatcher.JavaDispatcher$ProxiedInvocationHandler.invoke(JavaDispatcher.java:1173)
	at jdk.proxy2/jdk.proxy2.$Proxy60.defineClass(Unknown Source)
	at net.bytebuddy.dynamic.loading.ClassInjector$UsingLookup.injectRaw(ClassInjector.java:1638)
	... 98 more
Caused by: java.lang.IncompatibleClassChangeError: class com.picnic.testing.Super$$DynamicSubclass$768795248 cannot implement sealed interface com.picnic.testing.TestTest$Super
	at java.base/java.lang.ClassLoader.defineClass0(Native Method)
	at java.base/java.lang.System$2.defineClass(System.java:2307)
	at java.base/java.lang.invoke.MethodHandles$Lookup$ClassDefiner.defineClass(MethodHandles.java:2439)
	at java.base/java.lang.invoke.MethodHandles$Lookup$ClassDefiner.defineClass(MethodHandles.java:2416)
	at java.base/java.lang.invoke.MethodHandles$Lookup.defineClass(MethodHandles.java:1843)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at net.bytebuddy.utility.Invoker$Dispatcher.invoke(Unknown Source)
	at net.bytebuddy.utility.dispatcher.JavaDispatcher$Dispatcher$ForNonStaticMethod.invoke(JavaDispatcher.java:1028)
	at net.bytebuddy.utility.dispatcher.JavaDispatcher$ProxiedInvocationHandler.invoke(JavaDispatcher.java:1158)
	... 100 more

Code to reproduce:

import nl.jqno.equalsverifier.EqualsVerifier;
import org.junit.jupiter.api.Test;

public final class SealedFieldTypeTest {
  private sealed interface Super {}

  private non-sealed interface Sub extends Super {}

  static final class Container<T extends Super> {
    private final T element;

    Container(T element) {
      this.element = element;
    }

    @Override
    public boolean equals(Object o) {
      return o instanceof Container && ((Container<?>) o).element.equals(element);
    }

    @Override
    public int hashCode() {
      return element.hashCode();
    }
  }

  @Test
  void equality() {
    EqualsVerifier.forClass(Container.class).verify();
  }
}

Tested against EqualsVerifier 3.10. The issue can be worked around by adding .withPrefabValues(Super.class, mock(Sub.class), mock(Sub.class)).

@jqno
Copy link
Owner

jqno commented May 9, 2022

Thanks for reporting this, I can reproduce it. It's indeed related to #564. Not sure about #638, since Kotlin (probably) doesn't compile its sealed classes down to Java's sealed classes (yet?), although the solutions to both problems might be similar.

I want to make some smart logic to find final or non-sealed implementations of the sealed class/interface to use as prefab values, but until then, adding prefab values yourself is indeed the way to go. Maybe I could make the error message a bit more helpful in the mean time though.

@dsubelman
Copy link

dsubelman commented Jun 10, 2022

I think you can leverage the new methods isSealed() and getPermittedSubclasses() added to Class<?> in JDK 17:

assertTrue(Super.class.isSealed());

assertFalse(Sub.class.isSealed());

assertTrue(Sub.class.getSuperclass().isSealed());

assertArrayEquals(Super.class.getPermittedSubclasses(), new Class<?>[]{Sub.class});

@jqno
Copy link
Owner

jqno commented Jun 13, 2022

Indeed, it shouldn't be too hard...just haven't found time to look into it yet 😅

@cliffred
Copy link

Thanks for reporting this, I can reproduce it. It's indeed related to #564. Not sure about #638, since Kotlin (probably) doesn't compile its sealed classes down to Java's sealed classes (yet?), although the solutions to both problems might be similar.

Kotlin 1.7 combined with Java 17 does compile sealed classes down to Java's sealed classes.

@jqno
Copy link
Owner

jqno commented Feb 27, 2023

It finally happened! EqualsVerifier 3.14 is now syncing with Maven Central. It fixes this issue and also some other related to sealed types.

@jqno jqno closed this as completed Feb 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants