diff --git a/src/main/java/nl/jqno/equalsverifier/internal/checkers/HierarchyChecker.java b/src/main/java/nl/jqno/equalsverifier/internal/checkers/HierarchyChecker.java index 0b4755ebf..c2d9028c3 100644 --- a/src/main/java/nl/jqno/equalsverifier/internal/checkers/HierarchyChecker.java +++ b/src/main/java/nl/jqno/equalsverifier/internal/checkers/HierarchyChecker.java @@ -85,8 +85,8 @@ private void safelyCheckSuperProperties(ObjectAccessor referenceAccessor) { try { checkSuperProperties(reference, equalSuper, shallowCopy); } - catch (AbstractMethodError ignored) { - // In this case, we'll assume all super properties hold. + catch (AbstractMethodError | NullPointerException ignored) { + // In these cases, we'll assume all super properties hold. // The problems we test for, can never occur anyway if you can't instantiate a super instance. } } diff --git a/src/main/java/nl/jqno/equalsverifier/internal/checkers/NullChecker.java b/src/main/java/nl/jqno/equalsverifier/internal/checkers/NullChecker.java index f79fc7482..ff459e397 100644 --- a/src/main/java/nl/jqno/equalsverifier/internal/checkers/NullChecker.java +++ b/src/main/java/nl/jqno/equalsverifier/internal/checkers/NullChecker.java @@ -91,15 +91,25 @@ private void handle(String testedMethodName, Field field, Runnable r) { catch (NullPointerException e) { npeThrown(testedMethodName, field, e); } + catch (AbstractMethodError e) { + abstractMethodErrorThrown(testedMethodName, field, e); + } catch (Exception e) { exceptionThrown(testedMethodName, field, e); } } - private void npeThrown(String method, Field field, Exception e) { + private void npeThrown(String method, Field field, NullPointerException e) { fail(Formatter.of("Non-nullity: %% throws NullPointerException on field %%.", method, field.getName()), e); } + private void abstractMethodErrorThrown(String method, Field field, AbstractMethodError e) { + fail(Formatter.of( + "Abstract delegation: %% throws AbstractMethodError when field %% is null.\n" + + "Suppress Warning.NULL_FIELDS to disable this check.", + method, field.getName()), e); + } + private void exceptionThrown(String method, Field field, Exception e) { fail(Formatter.of("%% throws %% when field %% is null.", method, e.getClass().getSimpleName(), field.getName()), e); } diff --git a/src/test/java/nl/jqno/equalsverifier/integration/inheritance/AbstractHierarchyTest.java b/src/test/java/nl/jqno/equalsverifier/integration/inheritance/AbstractHierarchyTest.java index 7cadfa51d..f341d08f8 100644 --- a/src/test/java/nl/jqno/equalsverifier/integration/inheritance/AbstractHierarchyTest.java +++ b/src/test/java/nl/jqno/equalsverifier/integration/inheritance/AbstractHierarchyTest.java @@ -6,6 +6,7 @@ import nl.jqno.equalsverifier.testhelpers.types.Color; import org.junit.Test; +import static java.util.Objects.requireNonNull; import static nl.jqno.equalsverifier.testhelpers.Util.defaultHashCode; public class AbstractHierarchyTest extends IntegrationTestBase { @@ -36,6 +37,23 @@ public void succeed_whenEqualsThrowsNull_givenClassIsAbstractAndWarningIsSuppres .verify(); } + @Test + public void fail_whenAbstractImplementationThrowsNpe() { + expectFailure("Abstract delegation: equals throws AbstractMethodError when field object is null"); + EqualsVerifier.forClass(NullThrowingLazyObjectContainer.class) + .suppress(Warning.NONFINAL_FIELDS) + .withIgnoredFields("objectFactory") + .verify(); + } + + @Test + public void succeed_whenAbstractImplementationThrowsNpe_givenWarningIsSuppressed() { + EqualsVerifier.forClass(NullThrowingLazyObjectContainer.class) + .suppress(Warning.NULL_FIELDS, Warning.NONFINAL_FIELDS) + .withIgnoredFields("objectFactory") + .verify(); + } + abstract static class AbstractFinalMethodsPoint { private final int x; private final int y; @@ -113,4 +131,43 @@ public final boolean equals(Object obj) { @Override public final int hashCode() { return defaultHashCode(this); } } + + interface Supplier { + T get(); + } + + abstract static class AbstractLazyObjectContainer { + private Object object; + + private Object getObject() { + if (object == null) { + object = createObject(); + } + return object; + } + + protected abstract Object createObject(); + + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof AbstractLazyObjectContainer)) { + return false; + } + AbstractLazyObjectContainer other = (AbstractLazyObjectContainer)obj; + return getObject().equals(other.getObject()); + } + + @Override public int hashCode() { return getObject().hashCode(); } + } + + static final class NullThrowingLazyObjectContainer extends AbstractLazyObjectContainer { + private final Supplier objectFactory; + + protected NullThrowingLazyObjectContainer(Supplier flourFactory) { this.objectFactory = flourFactory; } + + @Override + protected Object createObject() { + return objectFactory.get(); + } + } }