[java-dfa] Support for-each patterns in Java DFA

Fixes IDEA-312232 Incorrect "Condition is always true" in presence of pattern foreach

GitOrigin-RevId: f25124ba8c0db15fc8c614aca7afec98cf78a29c
This commit is contained in:
Tagir Valeev
2023-02-02 13:00:06 +01:00
committed by intellij-monorepo-bot
parent 880d2ef474
commit 0484c455d1
5 changed files with 136 additions and 67 deletions

View File

@@ -507,9 +507,18 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
return expressions == null ? getFactory().getUnknown() : JavaDfaValueFactory.createCommonValue(getFactory(), expressions, type);
}
@Override public void visitForeachStatement(@NotNull PsiForeachStatement statement) {
@Override
public void visitForeachPatternStatement(@NotNull PsiForeachPatternStatement statement) {
processForeach(statement);
}
@Override
public void visitForeachStatement(@NotNull PsiForeachStatement statement) {
processForeach(statement);
}
private void processForeach(@NotNull PsiForeachStatementBase statement) {
startElement(statement);
final PsiParameter parameter = statement.getIterationParameter();
final PsiExpression iteratedValue = PsiUtil.skipParenthesizedExprDown(statement.getIteratedValue());
ControlFlowOffset loopEndOffset = getEndOffset(statement);
@@ -536,12 +545,29 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
ControlFlowOffset offset = myCurrentFlow.getNextOffset();
DfaVariableValue dfaVariable = PlainDescriptor.createVariableValue(myFactory, parameter);
DfaValue commonValue = getIteratedElement(parameter.getType(), iteratedValue);
if (DfaTypeValue.isUnknown(commonValue)) {
addInstruction(new FlushVariableInstruction(dfaVariable));
} else {
new CFGBuilder(this).pushForWrite(dfaVariable).push(commonValue).assign().pop();
if (statement instanceof PsiForeachStatement normalForeach) {
PsiParameter parameter = normalForeach.getIterationParameter();
DfaVariableValue dfaVariable = PlainDescriptor.createVariableValue(myFactory, parameter);
DfaValue commonValue = getIteratedElement(parameter.getType(), iteratedValue);
if (DfaTypeValue.isUnknown(commonValue)) {
addInstruction(new FlushVariableInstruction(dfaVariable));
} else {
new CFGBuilder(this).pushForWrite(dfaVariable).push(commonValue).assign().pop();
}
}
else if (statement instanceof PsiForeachPatternStatement patternForeach &&
patternForeach.getIterationPattern() instanceof PsiDeconstructionPattern pattern) {
PsiType contextType = JavaPsiPatternUtil.getContextType(pattern);
if (contextType == null) {
contextType = TypeUtils.getObjectType(statement);
}
DfaValue commonValue = getIteratedElement(contextType, iteratedValue);
addInstruction(new PushInstruction(commonValue, null));
DeferredOffset endPattern = new DeferredOffset();
processPattern(pattern, pattern, contextType,null, endPattern);
endPattern.setOffset(getInstructionCount());
addInstruction(new EnsureInstruction(null, RelationType.EQ, DfTypes.TRUE, null));
addInstruction(new PopInstruction());
}
if (!hasSizeCheck) {
@@ -562,7 +588,6 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
addInstruction(new GotoInstruction(offset));
finishElement(statement);
removeVariable(parameter);
}
@Override public void visitForStatement(@NotNull PsiForStatement statement) {

View File

@@ -1,7 +1,6 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.impl.source.resolve;
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
import com.intellij.openapi.projectRoots.JavaVersionService;
@@ -16,11 +15,7 @@ import com.intellij.psi.impl.source.tree.java.PsiExpressionListImpl;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.psi.util.*;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -273,7 +268,7 @@ public final class JavaResolveUtil {
if (resultClass.hasTypeParameters()) {
PsiSubstitutor substitutor = resolveResult.getSubstitutor();
result[i] = pattern != null && ref.getTypeParameterCount() == 0
? PatternInference.inferPatternGenerics(resolveResult, pattern, resultClass, getContextType(pattern))
? PatternInference.inferPatternGenerics(resolveResult, pattern, resultClass, JavaPsiPatternUtil.getContextType(pattern))
: new CandidateInfo(resolveResult, substitutor) {
@NotNull
@Override
@@ -326,54 +321,4 @@ public final class JavaResolveUtil {
return JavaDirectoryService.getInstance().getPackage(directory);
}
/**
* @param pattern deconstruction pattern to find a context type for
* @return a context type for the pattern; null, if cannot be determined. This method can perform
* the inference for outer patterns if necessary.
*/
private static @Nullable PsiType getContextType(@NotNull PsiDeconstructionPattern pattern) {
PsiElement parent = pattern.getParent();
while (parent instanceof PsiParenthesizedPattern) {
parent = parent.getParent();
}
if (parent instanceof PsiInstanceOfExpression) {
return ((PsiInstanceOfExpression)parent).getOperand().getType();
}
if (parent instanceof PsiForeachPatternStatement) {
PsiExpression iteratedValue = ((PsiForeachPatternStatement)parent).getIteratedValue();
if (iteratedValue == null) {
return null;
}
return JavaGenericsUtil.getCollectionItemType(iteratedValue);
}
if (parent instanceof PsiCaseLabelElementList) {
PsiSwitchLabelStatementBase label = ObjectUtils.tryCast(parent.getParent(), PsiSwitchLabelStatementBase.class);
if (label != null) {
PsiSwitchBlock block = label.getEnclosingSwitchBlock();
if (block != null) {
PsiExpression expression = block.getExpression();
if (expression != null) {
return expression.getType();
}
}
}
}
if (parent instanceof PsiDeconstructionList) {
PsiDeconstructionPattern parentPattern = ObjectUtils.tryCast(parent.getParent(), PsiDeconstructionPattern.class);
if (parentPattern != null) {
int index = ArrayUtil.indexOf(((PsiDeconstructionList)parent).getDeconstructionComponents(), pattern);
if (index < 0) return null;
PsiType patternType = parentPattern.getTypeElement().getType();
if (!(patternType instanceof PsiClassType)) return null;
PsiSubstitutor parentSubstitutor = ((PsiClassType)patternType).resolveGenerics().getSubstitutor();
PsiClass parentRecord = PsiUtil.resolveClassInClassTypeOnly(parentPattern.getTypeElement().getType());
if (parentRecord == null) return null;
PsiRecordComponent[] components = parentRecord.getRecordComponents();
if (index >= components.length) return null;
return parentSubstitutor.substitute(components[index].getType());
}
}
return null;
}
}

View File

@@ -1,6 +1,7 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.util;
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
@@ -485,6 +486,56 @@ public final class JavaPsiPatternUtil {
return variable;
}
/**
* @param pattern deconstruction pattern to find a context type for
* @return a context type for the pattern; null, if it cannot be determined. This method can perform
* the inference for outer patterns if necessary.
*/
public static @Nullable PsiType getContextType(@NotNull PsiDeconstructionPattern pattern) {
PsiElement parent = pattern.getParent();
while (parent instanceof PsiParenthesizedPattern) {
parent = parent.getParent();
}
if (parent instanceof PsiInstanceOfExpression) {
return ((PsiInstanceOfExpression)parent).getOperand().getType();
}
if (parent instanceof PsiForeachPatternStatement) {
PsiExpression iteratedValue = ((PsiForeachPatternStatement)parent).getIteratedValue();
if (iteratedValue == null) {
return null;
}
return JavaGenericsUtil.getCollectionItemType(iteratedValue);
}
if (parent instanceof PsiCaseLabelElementList) {
PsiSwitchLabelStatementBase label = ObjectUtils.tryCast(parent.getParent(), PsiSwitchLabelStatementBase.class);
if (label != null) {
PsiSwitchBlock block = label.getEnclosingSwitchBlock();
if (block != null) {
PsiExpression expression = block.getExpression();
if (expression != null) {
return expression.getType();
}
}
}
}
if (parent instanceof PsiDeconstructionList) {
PsiDeconstructionPattern parentPattern = ObjectUtils.tryCast(parent.getParent(), PsiDeconstructionPattern.class);
if (parentPattern != null) {
int index = ArrayUtil.indexOf(((PsiDeconstructionList)parent).getDeconstructionComponents(), pattern);
if (index < 0) return null;
PsiType patternType = parentPattern.getTypeElement().getType();
if (!(patternType instanceof PsiClassType)) return null;
PsiSubstitutor parentSubstitutor = ((PsiClassType)patternType).resolveGenerics().getSubstitutor();
PsiClass parentRecord = PsiUtil.resolveClassInClassTypeOnly(parentPattern.getTypeElement().getType());
if (parentRecord == null) return null;
PsiRecordComponent[] components = parentRecord.getRecordComponents();
if (index >= components.length) return null;
return parentSubstitutor.substitute(components[index].getType());
}
}
return null;
}
public static class PatternVariableWrapper {
private final @NotNull PsiPatternVariable myVariable;
private final boolean myIsFake;

View File

@@ -0,0 +1,39 @@
import java.util.*;
import org.jetbrains.annotations.Range;
public class ForEachPattern {
record IntBox(int i) {}
void bar1(Iterable<IntBox> i) {
int a = 1;
for (IntBox(int d) : i) {
a = 2;
}
System.out.println(a == 1);
}
record Point(int x, @Range(from = 1, to = 10) int y) {}
public static void main(String[] args) {
List<Point> points = new ArrayList<>();
use(points);
}
private static void use(List<Point> points) {
int a = 0, b = 0;
for (Point(int x, int y) : points) {
if (x == 1) {
a = 1;
}
if (x == 2) {
b = 1;
}
if (x == 2 && <warning descr="Condition 'b == 1' is always 'true' when reached">b == 1</warning>) {
b = y;
}
if (<warning descr="Condition 'y == 12' is always 'false'">y == 12</warning>) {}
}
if (a == 1 && b == 1) {
}
}
}

View File

@@ -38,4 +38,13 @@ public class DataFlowInspection20Test extends DataFlowInspectionTestCase {
public void testSuspiciousLabelElementsJava19() {
doTest();
}
public void testForEachPattern() {
myFixture.addClass("""
package org.jetbrains.annotations;
public @interface Range {
long from();
long to();
}""");
doTest();
}
}