diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java index a98a63696648..322b0a5552c4 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java @@ -221,22 +221,9 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { addInstruction(new AssignInstruction(rExpr, myFactory.createValue(lExpr))); - flushArrayElementsOnUnknownIndexAssignment(lExpr); - finishElement(expression); } - private void flushArrayElementsOnUnknownIndexAssignment(PsiExpression lExpr) { - if (lExpr instanceof PsiArrayAccessExpression && - !(myFactory.createValue(lExpr) instanceof DfaVariableValue) // check for unknown index, otherwise AssignInstruction will flush only that element - ) { - DfaValue arrayVar = myFactory.createValue(((PsiArrayAccessExpression)lExpr).getArrayExpression()); - if (arrayVar instanceof DfaVariableValue) { - addInstruction(new FlushVariableInstruction((DfaVariableValue)arrayVar, true)); - } - } - } - private void generateDefaultAssignmentBinOp(PsiExpression lExpr, PsiExpression rExpr, final PsiType exprType) { lExpr.accept(this); addInstruction(new DupInstruction()); @@ -555,8 +542,6 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { }); } - addCountingLoopBound(statement); - PsiExpression condition = statement.getCondition(); if (condition != null) { condition.accept(this); @@ -577,6 +562,8 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { update.accept(this); } + addCountingLoopBound(statement); + ControlFlow.ControlFlowOffset offset = initialization != null ? getEndOffset(initialization) : getStartOffset(statement); @@ -592,7 +579,8 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { /** * Add known-to-be-true condition inside counting loop, effectively converting - * {@code for(int i=origin; i=origin && i * Does nothing if the statement is not a counting loop. @@ -626,7 +614,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { if (origin == null || VariableAccessUtils.variableIsAssigned(counter, statement.getBody())) return; addInstruction(new PushInstruction(myFactory.getVarFactory().createVariableValue(counter, false), null)); addInstruction(new PushInstruction(origin, null)); - addInstruction(new BinopInstruction(JavaTokenType.LT, null, myProject)); + addInstruction(new BinopInstruction(JavaTokenType.LE, null, myProject)); addInstruction(new ConditionalGotoInstruction(getEndOffset(statement), false, null)); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactMap.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactMap.java index dbfd8b385d9c..4f27f68e50f7 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactMap.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactMap.java @@ -22,6 +22,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.Objects; /** * An immutable collection of facts which are known for some value. Each fact is identified by {@link DfaFactType} and fact value. @@ -91,24 +92,6 @@ public final class DfaFactMap { return true; } - /** - * Checks whether the passed fact map is always distinct from this map (i.e. any exact value - * which conforms the passed fact map does not conform this fact map). - * - * @param otherMap a fact map to check - * @return true if this fact map is always distinct from other map. - */ - public boolean isDistinct(DfaFactMap otherMap) { - for (DfaFactType key : DfaFactType.getTypes()) { - @SuppressWarnings("unchecked") - DfaFactType type = (DfaFactType)key; - Object thisValue = myMap.get(type); - Object other = otherMap.get(type); - if(thisValue != null && other != null && type.isDistinct(thisValue, other)) return true; - } - return false; - } - /** * Returns a fact map which is additionally restricted by supplied fact. * The returned map is a sub-state of this map. @@ -186,7 +169,9 @@ public final class DfaFactMap { @SuppressWarnings("unchecked") @Override public String toString() { - return StreamEx.of(myMap.getKeys()).map(key -> ((DfaFactType)key).toString(myMap.get(key))).joining(", "); + return StreamEx.of(myMap.getKeys()) + .map(key -> ((DfaFactType)key).toString(Objects.requireNonNull(myMap.get(key)))) + .joining(", "); } /** diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactType.java index a526f6de4f48..1063dd43084b 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactType.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactType.java @@ -94,10 +94,6 @@ public abstract class DfaFactType extends Key { * When its value is false, then optional is known to be empty (absent). */ public static final DfaFactType OPTIONAL_PRESENCE = new DfaFactType("Optional presense") { - @Override - public boolean isDistinct(@NotNull Boolean fact, @NotNull Boolean otherFact) { - return fact != otherFact; - } @Override String toString(@NotNull Boolean fact) { @@ -219,10 +215,6 @@ public abstract class DfaFactType extends Key { return Objects.equals(superFact, subFact); } - boolean isDistinct(@NotNull T fact, @NotNull T otherFact) { - return false; - } - boolean isUnknown(@NotNull T fact) { return false; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java index 2e5250d657d8..66325aa36133 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java @@ -18,7 +18,12 @@ package com.intellij.codeInspection.dataFlow; import com.intellij.codeInspection.dataFlow.instructions.*; import com.intellij.codeInspection.dataFlow.value.DfaUnknownValue; import com.intellij.codeInspection.dataFlow.value.DfaValue; +import com.intellij.codeInspection.dataFlow.value.DfaValueFactory; import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; +import com.intellij.psi.PsiArrayAccessExpression; +import com.intellij.psi.PsiExpression; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.ObjectUtils; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -30,10 +35,32 @@ public abstract class InstructionVisitor { public DfaInstructionState[] visitAssign(AssignInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { memState.pop(); - memState.push(memState.pop()); + DfaValue dest = memState.pop(); + memState.push(dest); + flushArrayOnUnknownAssignment(instruction, runner.getFactory(), dest, memState); return nextInstruction(instruction, runner, memState); } + protected void flushArrayOnUnknownAssignment(AssignInstruction instruction, + DfaValueFactory factory, + DfaValue dest, + DfaMemoryState memState) { + if (dest instanceof DfaVariableValue) return; + PsiArrayAccessExpression arrayAccess = + ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(instruction.getLExpression()), PsiArrayAccessExpression.class); + if (arrayAccess != null) { + PsiExpression array = arrayAccess.getArrayExpression(); + DfaValue value = factory.createValue(array); + if (value instanceof DfaVariableValue) { + for (DfaVariableValue qualified : factory.getVarFactory().getAllQualifiedBy((DfaVariableValue)value)) { + if (qualified.isFlushableByCalls()) { + memState.flushVariable(qualified); + } + } + } + } + } + public DfaInstructionState[] visitCheckNotNull(CheckNotNullInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { return nextInstruction(instruction, runner, memState); } @@ -137,16 +164,7 @@ public abstract class InstructionVisitor { public DfaInstructionState[] visitFlushVariable(FlushVariableInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { final DfaVariableValue variable = instruction.getVariable(); if (variable != null) { - if (instruction.isDependentsOnly()) { - for (DfaVariableValue qualified : runner.getFactory().getVarFactory().getAllQualifiedBy(variable)) { - if (qualified.isFlushableByCalls()) { - memState.flushVariable(qualified); - } - } - } - else { - memState.flushVariable(variable); - } + memState.flushVariable(variable); } else { memState.flushFields(); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/SpecialField.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/SpecialField.java index 24801ee5d682..854a1c990451 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/SpecialField.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/SpecialField.java @@ -21,6 +21,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.PsiUtil; +import com.siyeh.ig.psiutils.ExpressionUtils; import com.siyeh.ig.psiutils.MethodUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -44,9 +45,30 @@ public enum SpecialField { ((PsiField)accessor).getContainingClass(); } + @Override + DfaValue fromInitializer(DfaValueFactory factory, PsiExpression initializer) { + if (initializer instanceof PsiArrayInitializerExpression) { + return factory.getInt(((PsiArrayInitializerExpression)initializer).getInitializers().length); + } + if (initializer instanceof PsiNewExpression) { + PsiArrayInitializerExpression arrayInitializer = ((PsiNewExpression)initializer).getArrayInitializer(); + if (arrayInitializer != null) { + return factory.getInt(arrayInitializer.getInitializers().length); + } + PsiExpression[] dimensions = ((PsiNewExpression)initializer).getArrayDimensions(); + if (dimensions.length > 0) { + Object length = ExpressionUtils.computeConstantExpression(dimensions[0]); + if (length instanceof Integer) { + return factory.getInt(((Integer)length).intValue()); + } + } + } + return null; + } + @Nullable @Override - public PsiModifierListOwner getCanonicalOwner(@Nullable PsiModifierListOwner qualifier, @Nullable PsiClass psiClass) { + PsiModifierListOwner getCanonicalOwner(@Nullable PsiModifierListOwner qualifier, @Nullable PsiClass psiClass) { if (qualifier == null) return null; PsiClass arrayClass = JavaPsiFacade.getElementFactory(qualifier.getProject()) .getArrayClass(PsiUtil.getLanguageLevel(qualifier)); @@ -60,7 +82,16 @@ public enum SpecialField { }, STRING_LENGTH(CommonClassNames.JAVA_LANG_STRING, "length", true, LongRangeSet.indexRange()) { @Override - public DfaValue createFromConstant(DfaValueFactory factory, @NotNull Object obj) { + DfaValue fromInitializer(DfaValueFactory factory, PsiExpression initializer) { + Object value = ExpressionUtils.computeConstantExpression(initializer); + if(value instanceof String) { + return factory.getInt(((String)value).length()); + } + return null; + } + + @Override + public DfaValue fromConstant(DfaValueFactory factory, @NotNull Object obj) { return obj instanceof String ? factory.getInt(((String)obj).length()) : null; } }, @@ -109,7 +140,7 @@ public enum SpecialField { * @return a canonical accessor representing this special field or null if cannot be determined. */ @Nullable - public PsiModifierListOwner getCanonicalOwner(@Nullable PsiModifierListOwner qualifier, @Nullable PsiClass psiClass) { + PsiModifierListOwner getCanonicalOwner(@Nullable PsiModifierListOwner qualifier, @Nullable PsiClass psiClass) { if (psiClass == null) return null; if (!myClassName.equals(psiClass.getQualifiedName())) { PsiClass myClass = JavaPsiFacade.getInstance(psiClass.getProject()).findClass(myClassName, psiClass.getResolveScope()); @@ -130,8 +161,19 @@ public enum SpecialField { public DfaValue createValue(DfaValueFactory factory, DfaValue qualifier) { if (qualifier instanceof DfaVariableValue) { DfaVariableValue variableValue = (DfaVariableValue)qualifier; + PsiModifierListOwner psiVariable = variableValue.getPsiVariable(); + if (factory.isHonorFieldInitializers() && psiVariable instanceof PsiField && psiVariable.hasModifierProperty(PsiModifier.STATIC) + && psiVariable.hasModifierProperty(PsiModifier.FINAL)) { + PsiExpression initializer = ((PsiField)psiVariable).getInitializer(); + if (initializer != null) { + DfaValue value = fromInitializer(factory, initializer); + if (value != null) { + return value; + } + } + } PsiModifierListOwner owner = - getCanonicalOwner(variableValue.getPsiVariable(), PsiUtil.resolveClassInClassTypeOnly(variableValue.getVariableType())); + getCanonicalOwner(psiVariable, PsiUtil.resolveClassInClassTypeOnly(variableValue.getVariableType())); if (owner != null) { return factory.getVarFactory().createVariableValue(owner, PsiType.INT, false, variableValue); } @@ -139,7 +181,7 @@ public enum SpecialField { if(qualifier instanceof DfaConstValue) { Object obj = ((DfaConstValue)qualifier).getValue(); if(obj != null) { - DfaValue value = createFromConstant(factory, obj); + DfaValue value = fromConstant(factory, obj); if(value != null) { return value; } @@ -148,7 +190,11 @@ public enum SpecialField { return factory.getFactValue(DfaFactType.RANGE, myRange); } - public DfaValue createFromConstant(DfaValueFactory factory, @NotNull Object obj) { + DfaValue fromInitializer(DfaValueFactory factory, PsiExpression initializer) { + return null; + } + + DfaValue fromConstant(DfaValueFactory factory, @NotNull Object obj) { return null; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java index 7773102170fe..3b0f9e2d5030 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java @@ -51,8 +51,8 @@ public class StandardInstructionVisitor extends InstructionVisitor { DfaValue dfaSource = memState.pop(); DfaValue dfaDest = memState.pop(); - if (instruction.getAssignedValue() != null) { - // It's possible that dfaDest on the stack is cleared to DfaTypeValue due to variable flush + if (!(dfaDest instanceof DfaVariableValue) && instruction.getAssignedValue() != null) { + // It's possible that dfaDest on the stack is cleared to DfaFactMapValue due to variable flush // (e.g. during StateMerger#mergeByFacts), so we try to restore the original destination. dfaDest = instruction.getAssignedValue(); } @@ -88,6 +88,7 @@ public class StandardInstructionVisitor extends InstructionVisitor { } memState.push(dfaDest); + flushArrayOnUnknownAssignment(instruction, runner.getFactory(), dfaDest, memState); return nextInstruction(instruction, runner, memState); } @@ -140,8 +141,8 @@ public class StandardInstructionVisitor extends InstructionVisitor { DfaValue index = memState.pop(); DfaValue array = dereference(memState, memState.pop(), NullabilityProblemKind.arrayAccessNPE.problem(arrayExpression)); boolean alwaysOutOfBounds = false; + DfaValueFactory factory = runner.getFactory(); if (index != DfaUnknownValue.getInstance()) { - DfaValueFactory factory = runner.getFactory(); DfaValue indexNonNegative = factory.createCondition(index, RelationType.GE, factory.getInt(0)); if (!memState.applyCondition(indexNonNegative)) { alwaysOutOfBounds = true; @@ -155,7 +156,20 @@ public class StandardInstructionVisitor extends InstructionVisitor { } } processArrayAccess(arrayExpression, alwaysOutOfBounds); - memState.push(instruction.getValue()); + + DfaValue result = instruction.getValue(); + LongRangeSet rangeSet = memState.getValueFact(index, DfaFactType.RANGE); + if (rangeSet != null && !rangeSet.isEmpty() && rangeSet.min() == rangeSet.max()) { + long longIdx = rangeSet.min(); + if(longIdx >= 0 && longIdx <= Integer.MAX_VALUE) { + int intIdx = (int)longIdx; + DfaValue arrayElementValue = runner.getFactory().getExpressionFactory().getArrayElementValue(array, intIdx); + if (arrayElementValue != null) { + result = arrayElementValue; + } + } + } + memState.push(result); return nextInstruction(instruction, runner, memState); } @@ -483,6 +497,11 @@ public class StandardInstructionVisitor extends InstructionVisitor { if (methodType == MethodCallInstruction.MethodType.METHOD_REFERENCE_CALL && qualifierValue instanceof DfaVariableValue) { PsiMethod method = instruction.getTargetMethod(); + for (SpecialField sf : SpecialField.values()) { + if (sf.isMyAccessor(method)) { + return sf.createValue(factory, qualifierValue); + } + } PsiModifierListOwner modifierListOwner = DfaExpressionFactory.getAccessedVariableOrGetter(method); if (modifierListOwner != null) { return factory.getVarFactory().createVariableValue(modifierListOwner, instruction.getResultType(), false, diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/FlushVariableInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/FlushVariableInstruction.java index ad44621bd91d..3aa2c88bc6c0 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/FlushVariableInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/FlushVariableInstruction.java @@ -16,24 +16,17 @@ package com.intellij.codeInspection.dataFlow.instructions; -import com.intellij.codeInspection.dataFlow.*; +import com.intellij.codeInspection.dataFlow.DataFlowRunner; +import com.intellij.codeInspection.dataFlow.DfaInstructionState; +import com.intellij.codeInspection.dataFlow.DfaMemoryState; +import com.intellij.codeInspection.dataFlow.InstructionVisitor; import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; public class FlushVariableInstruction extends Instruction { private final DfaVariableValue myVariable; - private final boolean myDependentsOnly; public FlushVariableInstruction(DfaVariableValue expr) { - this(expr, false); - } - - public FlushVariableInstruction(DfaVariableValue variable, boolean dependentsOnly) { - myVariable = variable; - myDependentsOnly = dependentsOnly; - } - - public boolean isDependentsOnly() { - return myDependentsOnly; + myVariable = expr; } public DfaVariableValue getVariable() { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java index 233b7f557b58..60160475373e 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java @@ -61,7 +61,7 @@ public class DfaExpressionFactory { private final DfaValueFactory myFactory; private final Map myMockIndices = ContainerUtil.newHashMap(); - public DfaExpressionFactory(DfaValueFactory factory) { + DfaExpressionFactory(DfaValueFactory factory) { myFactory = factory; } @@ -77,9 +77,12 @@ public class DfaExpressionFactory { PsiExpression arrayExpression = ((PsiArrayAccessExpression)expression).getArrayExpression(); DfaVariableValue qualifier = getQualifierVariable(arrayExpression); if (qualifier != null) { - PsiVariable indexVar = getArrayIndexVariable(((PsiArrayAccessExpression)expression).getIndexExpression()); - if (indexVar != null) { - return myFactory.getVarFactory().createVariableValue(indexVar, expression.getType(), false, qualifier); + Object index = ExpressionUtils.computeConstantExpression(((PsiArrayAccessExpression)expression).getIndexExpression()); + if (index instanceof Integer) { + DfaValue arrayElementValue = getArrayElementValue(qualifier, (Integer)index); + if (arrayElementValue != null) { + return arrayElementValue; + } } } PsiType type = expression.getType(); @@ -124,6 +127,10 @@ public class DfaExpressionFactory { } private DfaValue createReferenceValue(@NotNull PsiReferenceExpression refExpr) { + DfaValue specialValue = createFromSpecialField(refExpr); + if (specialValue != null) { + return specialValue; + } PsiModifierListOwner var = getAccessedVariableOrGetter(refExpr.resolve()); if (var == null) { return null; @@ -181,6 +188,23 @@ public class DfaExpressionFactory { !DfaUtil.hasInitializationHacks((PsiField)var); } + @Nullable + private DfaValue createFromSpecialField(PsiReferenceExpression refExpr) { + PsiElement target = refExpr.resolve(); + if (!(target instanceof PsiModifierListOwner)) { + return null; + } + for (SpecialField sf : SpecialField.values()) { + if (sf.isMyAccessor((PsiModifierListOwner)target)) { + DfaVariableValue qualifier = getQualifierVariable(refExpr.getQualifierExpression()); + if (qualifier != null) { + return sf.createValue(myFactory, qualifier); + } + } + } + return null; + } + @Contract("null -> null") @Nullable public static PsiModifierListOwner getAccessedVariableOrGetter(final PsiElement target) { @@ -195,11 +219,6 @@ public class DfaExpressionFactory { return method; } } - for (SpecialField sf : SpecialField.values()) { - if (sf.isMyAccessor(method)) { - return sf.getCanonicalOwner(null, ((PsiMethod)target).getContainingClass()); - } - } if (method.getParameterList().getParametersCount() == 0) { if ((ControlFlowAnalyzer.isPure(method) || AnnotationUtil.findAnnotation(method.getContainingClass(), "javax.annotation.concurrent.Immutable") != null) && @@ -211,12 +230,28 @@ public class DfaExpressionFactory { return null; } + public DfaValue getArrayElementValue(DfaValue array, int index) { + if (!(array instanceof DfaVariableValue)) return null; + DfaVariableValue arrayDfaVar = (DfaVariableValue)array; + PsiType type = arrayDfaVar.getVariableType(); + if (!(type instanceof PsiArrayType)) return null; + PsiType componentType = ((PsiArrayType)type).getComponentType(); + PsiModifierListOwner arrayPsiVar = arrayDfaVar.getPsiVariable(); + if (arrayPsiVar instanceof PsiVariable) { + PsiExpression constantArrayElement = ExpressionUtils.getConstantArrayElement((PsiVariable)arrayPsiVar, index); + if (constantArrayElement != null) { + return getExpressionDfaValue(constantArrayElement); + } + } + PsiVariable indexVariable = getArrayIndexVariable(arrayPsiVar, index); + if (indexVariable == null) return null; + return myFactory.getVarFactory().createVariableValue(indexVariable, componentType, false, arrayDfaVar); + } + @Nullable - private PsiVariable getArrayIndexVariable(@Nullable PsiExpression indexExpression) { - Object constant = JavaConstantExpressionEvaluator.computeConstantExpression(indexExpression, false); - if (constant instanceof Integer && ((Integer)constant).intValue() >= 0) { - return myMockIndices - .computeIfAbsent((Integer)constant, k -> new LightVariableBuilder<>("$array$index$" + k, PsiType.INT, indexExpression)); + private PsiVariable getArrayIndexVariable(@NotNull PsiElement anchor, int index) { + if (index >= 0) { + return myMockIndices.computeIfAbsent(index, k -> new LightVariableBuilder<>("$array$index$" + k, PsiType.INT, anchor)); } return null; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java index 7d252ecddfcc..5ddeee448247 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java @@ -177,11 +177,13 @@ public class DfaValueFactory { if(dfaLeft instanceof DfaFactMapValue && dfaRight instanceof DfaFactMapValue) { if(relationType == RelationType.IS || relationType == RelationType.IS_NOT) { - boolean isSuperState = ((DfaFactMapValue)dfaRight).getFacts().isSuperStateOf(((DfaFactMapValue)dfaLeft).getFacts()); + DfaFactMap leftFacts = ((DfaFactMapValue)dfaLeft).getFacts(); + DfaFactMap rightFacts = ((DfaFactMapValue)dfaRight).getFacts(); + boolean isSuperState = rightFacts.isSuperStateOf(leftFacts); if (isSuperState) { return getBoolean(relationType == RelationType.IS); } - boolean isDistinct = ((DfaFactMapValue)dfaRight).getFacts().isDistinct(((DfaFactMapValue)dfaLeft).getFacts()); + boolean isDistinct = rightFacts.intersect(leftFacts) == null; if (isDistinct) { return getBoolean(relationType == RelationType.IS_NOT); } @@ -261,4 +263,7 @@ public class DfaValueFactory { public DfaFactMapValue.Factory getFactFactory() { return myFactFactory; } + + @NotNull + public DfaExpressionFactory getExpressionFactory() { return myExpressionFactory;} } diff --git a/java/java-tests/testData/codeInsight/completion/normal/CastInstanceofedThisQualifier.java b/java/java-tests/testData/codeInsight/completion/normal/CastInstanceofedThisQualifier.java index 672a5f273e3a..6f19cc54ce6e 100644 --- a/java/java-tests/testData/codeInsight/completion/normal/CastInstanceofedThisQualifier.java +++ b/java/java-tests/testData/codeInsight/completion/normal/CastInstanceofedThisQualifier.java @@ -9,7 +9,7 @@ public abstract class Super { } -class Sub extends Zzza { +interface Sub extends Zzza { void subMethod() {} } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/normal/CastInstanceofedThisQualifier_after.java b/java/java-tests/testData/codeInsight/completion/normal/CastInstanceofedThisQualifier_after.java index 0e75d7d6b614..045d5799af05 100644 --- a/java/java-tests/testData/codeInsight/completion/normal/CastInstanceofedThisQualifier_after.java +++ b/java/java-tests/testData/codeInsight/completion/normal/CastInstanceofedThisQualifier_after.java @@ -9,7 +9,7 @@ public abstract class Super { } -class Sub extends Zzza { +interface Sub extends Zzza { void subMethod() {} } \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/AdvancedArrayAccess.java b/java/java-tests/testData/inspection/dataFlow/fixture/AdvancedArrayAccess.java new file mode 100644 index 000000000000..e8031adef2b2 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/AdvancedArrayAccess.java @@ -0,0 +1,108 @@ +import java.util.*; + +class AdvancedArrayAccess { + private static final int[] LENGTH = {0, 10, 20, 30}; + private static final int[] LENGTH2 = {0, 10, 20, 30}; + + void testInstanceOf() { + Object[] arr = {new String("foo"), new Integer(0)}; + if(arr[0] instanceof Number) { + System.out.println(((Number)arr[0]).intValue()); + } + } + + void testStaticFinal() { + if(LENGTH[2] == 20) { + System.out.println("ok"); + } + } + + int[] getLengths() { + // array is still read-only + return LENGTH.clone(); + } + + int getLengthsCount() { + // array is still read-only + return LENGTH.length; + } + + int[] getLength2() { + // Reference leaks: do not consider an array as read-only + return LENGTH2; + } + + void testStaticFinal(int idx) { + if(idx == 2 && LENGTH[idx] == 20) { + System.out.println("ok"); + } + } + + void testStaticFinalRW(int idx) { + if(idx == 2 && LENGTH2[idx] == 20) { + System.out.println("ok"); + } + } + + void testWrite(int[] arr, int idx) { + if(idx != 0) return; + arr[idx] = 10; + if(arr[0] == 11) { + System.out.println("impossible"); + } + } + + void testSplitState(boolean b, String[] data) { + data[1] = "A"; + data[2] = "B"; + int idx = b ? 0 : 1; + data[idx] = "C"; + if(data[2].equals("D")) { + System.out.println("never"); + } + if(data[1].equals("E")) { + System.out.println("never"); + } + if(data[1].equals("A") && b) { + System.out.println("Only if b is true"); + } + } + + // Should not be too complex + static int max(float[] array) { + int max = 0; + float val = array[0]; + for (int i = 1; i < array.length; i++) { + if (val < array[i]) { + max = i; + val = array[i]; + } + } + return max; + } + + void processArray(String[] arr) { + for(int i=0; itrim()); + } + } + + final String[] getData() { + return new String[] {"a", "b", "c"}; + } + + void testConditional(String[] arr, int idx) { + if(idx == 0) { + arr[idx] = "foo"; + } + if(arr[0].equals("bar") && idx == 0) { + System.out.println("never"); + } + } +} diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/ArrayLength.java b/java/java-tests/testData/inspection/dataFlow/fixture/ArrayLength.java index 56469da8e6a5..5a714185b703 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/ArrayLength.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/ArrayLength.java @@ -68,4 +68,14 @@ public final class ArrayLength { Arrays.fill(x, -1); Arrays.fill(x, -1, -1, -1); } + + static final String[] ARR1 = {}; + static final String[] ARR2 = new String[10]; + static final String[] ARR3 = new String[] {"foo", "bar", "baz"}; + + void testFieldInitializers() { + if(ARR1.length == 0) System.out.println("yes"); + if(ARR2.length == 10) System.out.println("yes"); + if(ARR3.length == 3) System.out.println("yes"); + } } \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeBasics.java b/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeBasics.java index 7260e8c60124..77d8a71d1228 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeBasics.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeBasics.java @@ -99,6 +99,11 @@ public class LongRangeBasics { } } + static final String FOO = "bar"; + + void testStaticLength() { + if(FOO.isEmpty()) System.out.println("nope"); + } void testWrongMerge(boolean a, boolean b, int c) { int currentLevel = 0; diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java index c1fcf7f76ffc..76cf4cd7774b 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java @@ -581,4 +581,5 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase { public void testXorNullity() { doTest(); } public void testPrimitiveNull() { doTest(); } public void testLessThanRelations() { doTest(); } + public void testAdvancedArrayAccess() { doTest(); } } diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ExpressionUtils.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ExpressionUtils.java index 7e11a3ec9000..131811ed7ee5 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ExpressionUtils.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ExpressionUtils.java @@ -1153,4 +1153,49 @@ public class ExpressionUtils { if (diffConstant == null) return false; return diffConstant == toConstant - fromConstant; } + + /** + * Returns an expression which represents an array element with given index if array is known to be never modified + * after initialization. + * + * @param array an array variable + * @param index an element index + * @return an expression or null if index is out of bounds or array could be modified after initialization + */ + @Nullable + public static PsiExpression getConstantArrayElement(PsiVariable array, int index) { + if (index < 0) return null; + PsiExpression initializer = array.getInitializer(); + if (initializer instanceof PsiNewExpression) initializer = ((PsiNewExpression)initializer).getArrayInitializer(); + if (!(initializer instanceof PsiArrayInitializerExpression)) return null; + PsiExpression[] initializers = ((PsiArrayInitializerExpression)initializer).getInitializers(); + if (index >= initializers.length) return null; + if (array instanceof PsiField && !(array.hasModifierProperty(PsiModifier.PRIVATE) && array.hasModifierProperty(PsiModifier.STATIC))) { + return null; + } + Boolean isConstantArray = CachedValuesManager.getCachedValue(array, () -> CachedValueProvider.Result + .create(isConstantArray(array), PsiModificationTracker.MODIFICATION_COUNT)); + return Boolean.TRUE.equals(isConstantArray) ? initializers[index] : null; + } + + private static boolean isConstantArray(PsiVariable array) { + PsiElement scope = PsiTreeUtil.getParentOfType(array, array instanceof PsiField ? PsiClass.class : PsiCodeBlock.class); + if (scope == null) return false; + return PsiTreeUtil.processElements(scope, e -> { + if (!(e instanceof PsiReferenceExpression)) return true; + PsiReferenceExpression ref = (PsiReferenceExpression)e; + if (!ref.isReferenceTo(array)) return true; + PsiExpression parent = tryCast(PsiUtil.skipParenthesizedExprUp(ref.getParent()), PsiExpression.class); + if (parent == null) return false; + if (parent instanceof PsiReferenceExpression) { + if (isReferenceTo(getArrayFromLengthExpression(parent), array)) return true; + if (parent.getParent() instanceof PsiMethodCallExpression && + MethodCallUtils.isCallToMethod((PsiMethodCallExpression)parent.getParent(), CommonClassNames.JAVA_LANG_OBJECT, + null, "clone", PsiType.EMPTY_ARRAY)) { + return true; + } + } + return parent instanceof PsiArrayAccessExpression && !PsiUtil.isAccessedForWriting(parent); + }); + } } \ No newline at end of file