[java] Fix #5190 - NPE in type inference caused by null type (#5192)

Merge pull request #5192 from oowekyala:issue5190-npe-infer
This commit is contained in:
Andreas Dangel committed 2024-09-09 20:38:36 +02:00
commit 04b3361dec
8 files changed
+120 -36

No files matched your search

+9
View File
@@ -7754,6 +7754,15 @@
"contributions": [
"bug"
]
},
{
"login": "vedantchokshi",
"name": "Vedant Chokshi",
"avatar_url": "https://avatars.githubusercontent.com/u/22137048?v=4",
"profile": "https://github.com/vedantchokshi",
"contributions": [
"bug"
]
}
],
"contributorsPerLine": 7,
File diff suppressed because it is too large. Load diff
+2
View File
@@ -20,6 +20,8 @@ This is a {{ site.pmd.release_type }} release.
(ApexCRUDViolation, CognitiveComplexity, OperationWithLimitsInLoop)
* [#5163](https://github.com/pmd/pmd/issues/5163): \[apex] Parser error when using toLabel in SOSL query
* [#5182](https://github.com/pmd/pmd/issues/5182): \[apex] Parser error when using GROUPING in a SOQL query
* java
* [#5190](https://github.com/pmd/pmd/issues/5190): \[java] NPE in type inference
### 🚨 API Changes
@@ -798,6 +798,11 @@ public final class TypeSystem {
return null;
}
@Override
public @Nullable JTypeMirror getAsSuper(@NonNull JClassSymbol symbol) {
throw new UnsupportedOperationException("Null type cannot call asSuper, will return null always");
}
@Override
public <T, P> T acceptVisitor(JTypeVisitor<T, P> visitor, P p) {
return visitor.visitNullType(this, p);
@@ -816,7 +816,7 @@ public final class Infer {
private boolean commonSuperWithDiffParameterization(JTypeMirror t, JTypeMirror s) {
JTypeMirror lubResult = ts.lub(listOf(t, s));
if (lubResult.isBottom() || lubResult.isTop()) {
if (lubResult.isBottom() || lubResult.isTop() || t.isBottom() || s.isBottom()) {
return false;
}
for (JTypeMirror sup : asList(lubResult)) {
@@ -824,6 +824,8 @@ public final class Infer {
JClassSymbol sym = ((JClassType) sup).getSymbol();
JTypeMirror asSuperOfT = t.getAsSuper(sym);
JTypeMirror asSuperOfS = s.getAsSuper(sym);
assert asSuperOfS != null : "s <: sup, because sup is part of the LUB of s";
assert asSuperOfT != null : "t <: sup, because sup is part of the LUB of t";
if (!asSuperOfS.equals(asSuperOfT)) {
return true;
}
@@ -97,7 +97,6 @@ public final class InferenceVar implements SubstVar {
* Adds a new bound on this variable.
*/
public void addBound(BoundKind kind, JTypeMirror type) {
this.hasNonTrivialBound = true;
addBound(kind, type, false);
}
@@ -115,9 +114,15 @@ public final class InferenceVar implements SubstVar {
// may occur because of transitive propagation
// alpha <: alpha is always true and not interesting
return;
} else if (kind == BoundKind.LOWER && type.isBottom()) {
// null <: alpha is not interesting and may cause errors because of lub.
return;
}
if (boundSet.bounds.computeIfAbsent(kind, k -> new LinkedHashSet<>()).add(type)) {
if (!isPrimaryBound) {
this.hasNonTrivialBound = true;
}
ctx.onBoundAdded(this, kind, type, isPrimaryBound);
}
}
@@ -74,6 +74,22 @@ class InferenceCtxUnitTests extends BaseTypeInferenceUnitTest {
verify(log).boundAdded(ctx, v2, BoundKind.UPPER, listOfV1, false);
}
@Test
void testNullTypeCannotBeLowerBound() {
TypeInferenceLogger log = spy(TypeInferenceLogger.noop());
InferenceContext ctx = emptyCtx(log);
InferenceVar v1 = newIvar(ctx);
addSubtypeConstraint(ctx, ts.NULL_TYPE, v1);
assertThat(v1, hasBoundsExactly(upper(ts.OBJECT)));
verify(log, never()).boundAdded(ctx, v1, BoundKind.LOWER, ts.NULL_TYPE, false);
}
@Test
void testEqBoundWithGenerics() {
TypeInferenceLogger log = spy(TypeInferenceLogger.noop());
@@ -454,4 +454,48 @@ public class BadIntersection {
info.methodType.formalParameters[0] shouldBe optOutEnum
}
}
parserTest("#5190 NPE in type inf") {
val (acu, spy) = parser.parseWithTypeInferenceSpy(
"""
import java.util.Iterator;
interface Optional<V> {
static <T> Optional<T> ofNullable(T t) {}
}
interface Map<K,V> {}
interface List<V> extends Iterable<V> {}
interface AttributeValue{}
class Main {
private Optional<Map<String, AttributeValue>> loadForKey(final String key) {
return Optional.ofNullable(
getOnlyElement(queryForKey(key), null)
);
}
private List<Map<String, AttributeValue>> queryForKey(final String key) {
return null;
}
public static <T> T getOnlyElement(final Iterable<? extends T> iterable, final T defaultValue) {
return getOnlyElement(iterable.iterator(), defaultValue);
}
public static <T> T getOnlyElement(final Iterator<? extends T> iterator, final T defaultValue) {
return null;
}
}
""".trimIndent()
)
val (_, _, _) = acu.declaredTypeSignatures()
val (ofNullable) = acu.methodDeclarations().toList { it.genericSignature }
spy.shouldBeOk {
val info = acu.firstMethodCall().overloadSelectionInfo
info::isFailed shouldBe false
info.methodType shouldBeSomeInstantiationOf ofNullable
}
}
})