diff --git a/java/java-psi-api/src/com/intellij/util/JavaTypeNullabilityUtil.java b/java/java-psi-api/src/com/intellij/util/JavaTypeNullabilityUtil.java index a8395b29ddf9..8ed16530f215 100644 --- a/java/java-psi-api/src/com/intellij/util/JavaTypeNullabilityUtil.java +++ b/java/java-psi-api/src/com/intellij/util/JavaTypeNullabilityUtil.java @@ -21,6 +21,13 @@ import java.util.Set; */ @ApiStatus.Internal public final class JavaTypeNullabilityUtil { + /** + * Java allows recursive type bounds, e.g. {@code class A> {}}. + * During the nullability conflict it may turn into the endless recursion. + * This field limits the depth of the recursive type parameters. + */ + private static final int MAX_NULLABILITY_CONFLICT_RECURSION_PARAMETER_DEPTH = 100; + /** * Computes the class type nullability * @@ -107,13 +114,17 @@ public final class JavaTypeNullabilityUtil { public static @NotNull NullabilityConflict getNullabilityConflictInAssignment(@Nullable PsiType leftType, @Nullable PsiType rightType, boolean checkNotNullToNull) { - return getNullabilityConflictInAssignment(leftType, rightType, checkNotNullToNull, false); + return getNullabilityConflictInAssignment(leftType, rightType, 0, checkNotNullToNull, false); } private static @NotNull NullabilityConflict getNullabilityConflictInAssignment(@Nullable PsiType leftType, @Nullable PsiType rightType, + int recursionDepth, boolean checkNotNullToNull, boolean checkConflictInInitialType) { + if (recursionDepth >= MAX_NULLABILITY_CONFLICT_RECURSION_PARAMETER_DEPTH) { + return NullabilityConflict.UNKNOWN; + } if (checkConflictInInitialType) { NullabilityConflict nullabilityConflict = getNullabilityConflictType(leftType, rightType); if (isAllowedNullabilityConflictType(checkNotNullToNull, nullabilityConflict)) return nullabilityConflict; @@ -126,34 +137,34 @@ public final class JavaTypeNullabilityUtil { } if (rightType instanceof PsiIntersectionType) { - return getNullabilityConflictInTypeArguments(leftType, rightType, checkNotNullToNull); + return getNullabilityConflictInTypeArguments(leftType, rightType, recursionDepth, checkNotNullToNull); } if (rightType instanceof PsiCapturedWildcardType) { - return getNullabilityConflictInAssignment(leftType, ((PsiCapturedWildcardType)rightType).getUpperBound(true), checkNotNullToNull, + return getNullabilityConflictInAssignment(leftType, ((PsiCapturedWildcardType)rightType).getUpperBound(true), recursionDepth + 1, checkNotNullToNull, false); } if (leftType instanceof PsiCapturedWildcardType) { - return getNullabilityConflictInAssignment(((PsiCapturedWildcardType)leftType).getLowerBound(), rightType, checkNotNullToNull, false); + return getNullabilityConflictInAssignment(((PsiCapturedWildcardType)leftType).getLowerBound(), rightType, recursionDepth + 1, checkNotNullToNull, false); } if (leftType instanceof PsiWildcardType) { - return getNullabilityConflictInAssignment(GenericsUtil.getWildcardBound(leftType), rightType, checkNotNullToNull, false); + return getNullabilityConflictInAssignment(GenericsUtil.getWildcardBound(leftType), rightType, recursionDepth + 1, checkNotNullToNull, false); } if (rightType instanceof PsiWildcardType) { - return getNullabilityConflictInAssignment(leftType, GenericsUtil.getWildcardBound(rightType), checkNotNullToNull, false); + return getNullabilityConflictInAssignment(leftType, GenericsUtil.getWildcardBound(rightType), recursionDepth + 1, checkNotNullToNull, false); } if (leftType instanceof PsiArrayType && rightType instanceof PsiArrayType) { return getNullabilityConflictInAssignment(((PsiArrayType)leftType).getComponentType(), - ((PsiArrayType)rightType).getComponentType(), checkNotNullToNull, true); + ((PsiArrayType)rightType).getComponentType(), recursionDepth + 1, checkNotNullToNull, true); } if (!(leftType instanceof PsiClassType) || !(rightType instanceof PsiClassType)) { return NullabilityConflict.UNKNOWN; } - return getNullabilityConflictInTypeArguments(leftType, rightType, checkNotNullToNull); + return getNullabilityConflictInTypeArguments(leftType, rightType, recursionDepth, checkNotNullToNull); } /** @@ -167,6 +178,7 @@ public final class JavaTypeNullabilityUtil { */ private static @NotNull NullabilityConflict getNullabilityConflictInTypeArguments(@NotNull PsiType leftType, @NotNull PsiType rightType, + int recursionDepth, boolean checkNotNullToNull) { PsiClass leftClass = PsiTypesUtil.getPsiClass(leftType); if (leftClass == null) return NullabilityConflict.UNKNOWN; @@ -185,6 +197,7 @@ public final class JavaTypeNullabilityUtil { NullabilityConflict nullabilityConflict = getNullabilityConflictInAssignment( leftParameterType, rightParameterType, + recursionDepth + 1, checkNotNullToNull, true ); diff --git a/java/java-tests/testData/inspection/nullableProblems/ReturnIncompatibilitiesWithGeneric.java b/java/java-tests/testData/inspection/nullableProblems/ReturnIncompatibilitiesWithGeneric.java index f93dbe3a80f2..6295ec287413 100644 --- a/java/java-tests/testData/inspection/nullableProblems/ReturnIncompatibilitiesWithGeneric.java +++ b/java/java-tests/testData/inspection/nullableProblems/ReturnIncompatibilitiesWithGeneric.java @@ -84,4 +84,16 @@ class B { List> nestedIntersection(Object arg) { return (List> & I) arg; } + + static class NoStackOverflow { + public abstract static class Parent + >{} + + public static class Child extends + Parent {} + + public Parent rawType(Child p) { + return p; + } + } } \ No newline at end of file