From 9f6615f6ed154d85e408c6f46191ebb151ec8034 Mon Sep 17 00:00:00 2001 From: Georgii Ustinov Date: Wed, 29 Oct 2025 11:35:05 +0100 Subject: [PATCH] [Java] IDEA-375132 IDEA-381374 Limit recursion depth when checking nullability conflict. Previous implementation for nullability conflict detection didn't perform correct substitution for bounded type parameters. New approach does that, but it may face with endless recursion when facing with recursive type bounds. This commit limits for the type parameters GitOrigin-RevId: 226e2d6f536cdcc943461632a42fc76a1fcc8e01 --- .../util/JavaTypeNullabilityUtil.java | 29 ++++++++++++++----- .../ReturnIncompatibilitiesWithGeneric.java | 12 ++++++++ 2 files changed, 33 insertions(+), 8 deletions(-) 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