diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractReturnValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractReturnValue.java index 26dd61d66178..c04e3cf627bb 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractReturnValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractReturnValue.java @@ -3,10 +3,7 @@ package com.intellij.codeInspection.dataFlow; import com.intellij.codeInspection.dataFlow.types.DfType; import com.intellij.codeInspection.dataFlow.types.DfTypes; -import com.intellij.codeInspection.dataFlow.value.DfaTypeValue; -import com.intellij.codeInspection.dataFlow.value.DfaValue; -import com.intellij.codeInspection.dataFlow.value.DfaValueFactory; -import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; +import com.intellij.codeInspection.dataFlow.value.*; import com.intellij.codeInspection.util.InspectionMessage; import com.intellij.java.analysis.JavaAnalysisBundle; import com.intellij.psi.*; @@ -121,6 +118,9 @@ public abstract class ContractReturnValue { memState.meetDfType(newValue, result); return newValue; } + if (defaultValue instanceof DfaBoxedValue && newType.isSuperType(defaultValue.getDfType())) { + return defaultValue; + } if (defaultValue instanceof DfaVariableValue) { memState.meetDfType(defaultValue, result); return defaultValue; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java index 915ef311fc5e..9a7d16ea76c2 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java @@ -6,6 +6,7 @@ import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet; import com.intellij.codeInspection.dataFlow.types.DfIntType; import com.intellij.codeInspection.dataFlow.types.DfLongType; import com.intellij.codeInspection.dataFlow.types.DfType; +import com.intellij.codeInspection.dataFlow.value.DfaBinOpValue; import com.intellij.codeInspection.dataFlow.value.DfaValue; import com.intellij.codeInspection.dataFlow.value.DfaValueFactory; import com.intellij.codeInspection.dataFlow.value.RelationType; @@ -66,57 +67,79 @@ public final class CustomMethodHandlers { interface CustomMethodHandler { + @Nullable + DfaValue getMethodResultValue(DfaCallArguments callArguments, + DfaMemoryState memState, + DfaValueFactory factory, + PsiMethod method); + + default CustomMethodHandler compose(CustomMethodHandler other) { + if (other == null) return this; + return (args, memState, factory, method) -> { + DfaValue result = this.getMethodResultValue(args, memState, factory, method); + return result == null ? other.getMethodResultValue(args, memState, factory, method) : result; + }; + } + } + + interface DfTypeCustomMethodHandler extends CustomMethodHandler { @NotNull DfType getMethodResult(DfaCallArguments callArguments, DfaMemoryState memState, DfaValueFactory factory, PsiMethod method); - default CustomMethodHandler compose(CustomMethodHandler other) { - if (other == null) return this; - return (args, memState, factory, method) -> { - DfType result = this.getMethodResult(args, memState, factory, method); - return result == TOP ? other.getMethodResult(args, memState, factory, method) : result; - }; + @Override + @Nullable + default DfaValue getMethodResultValue(DfaCallArguments callArguments, + DfaMemoryState memState, + DfaValueFactory factory, + PsiMethod method) { + DfType dfType = getMethodResult(callArguments, memState, factory, method); + return dfType == TOP ? null : factory.fromDfType(dfType); } } + + private static CustomMethodHandler toValue(DfTypeCustomMethodHandler handler) { + return handler; + } private static final CallMapper CUSTOM_METHOD_HANDLERS = new CallMapper() .register(instanceCall(JAVA_LANG_STRING, "indexOf", "lastIndexOf"), - (args, memState, factory, method) -> indexOf(args.myQualifier, memState, factory, STRING_LENGTH)) + toValue((args, memState, factory, method) -> indexOf(args.myQualifier, memState, factory, STRING_LENGTH))) .register(instanceCall(JAVA_UTIL_LIST, "indexOf", "lastIndexOf"), - (args, memState, factory, method) -> indexOf(args.myQualifier, memState, factory, COLLECTION_SIZE)) + toValue((args, memState, factory, method) -> indexOf(args.myQualifier, memState, factory, COLLECTION_SIZE))) .register(staticCall(JAVA_LANG_MATH, "abs").parameterTypes("int"), - (args, memState, factory, method) -> mathAbs(args.myArguments, memState, false)) + toValue((args, memState, factory, method) -> mathAbs(args.myArguments, memState, false))) .register(staticCall(JAVA_LANG_MATH, "abs").parameterTypes("long"), - (args, memState, factory, method) -> mathAbs(args.myArguments, memState, true)) + toValue((args, memState, factory, method) -> mathAbs(args.myArguments, memState, true))) .register(exactInstanceCall(JAVA_LANG_STRING, "substring"), (args, memState, factory, method) -> substring(args, memState, factory, method.getReturnType())) .register(OptionalUtil.OPTIONAL_OF_NULLABLE, - (args, memState, factory, method) -> OPTIONAL_VALUE.asDfType(memState.getDfType(args.myArguments[0]), method.getReturnType())) + toValue((args, memState, factory, method) -> OPTIONAL_VALUE.asDfType(memState.getDfType(args.myArguments[0])))) .register(instanceCall(JAVA_UTIL_CALENDAR, "get").parameterTypes("int"), - (args, memState, factory, method) -> calendarGet(args.myArguments, memState)) + toValue((args, memState, factory, method) -> calendarGet(args.myArguments, memState))) .register(anyOf(instanceCall("java.io.InputStream", "skip").parameterTypes("long"), instanceCall("java.io.Reader", "skip").parameterTypes("long")), - (args, memState, factory, method) -> skip(args.myArguments, memState)) + toValue((args, memState, factory, method) -> skip(args.myArguments, memState))) .register(staticCall(JAVA_LANG_INTEGER, "toHexString").parameterCount(1), - (args, memState, factory, method) -> numberAsString(args, memState, 4, Integer.SIZE)) + toValue((args, memState, factory, method) -> numberAsString(args, memState, 4, Integer.SIZE))) .register(staticCall(JAVA_LANG_INTEGER, "toOctalString").parameterCount(1), - (args, memState, factory, method) -> numberAsString(args, memState, 3, Integer.SIZE)) + toValue((args, memState, factory, method) -> numberAsString(args, memState, 3, Integer.SIZE))) .register(staticCall(JAVA_LANG_INTEGER, "toBinaryString").parameterCount(1), - (args, memState, factory, method) -> numberAsString(args, memState, 1, Integer.SIZE)) + toValue((args, memState, factory, method) -> numberAsString(args, memState, 1, Integer.SIZE))) .register(staticCall(JAVA_LANG_LONG, "toHexString").parameterCount(1), - (args, memState, factory, method) -> numberAsString(args, memState, 4, Long.SIZE)) + toValue((args, memState, factory, method) -> numberAsString(args, memState, 4, Long.SIZE))) .register(staticCall(JAVA_LANG_LONG, "toOctalString").parameterCount(1), - (args, memState, factory, method) -> numberAsString(args, memState, 3, Long.SIZE)) + toValue((args, memState, factory, method) -> numberAsString(args, memState, 3, Long.SIZE))) .register(staticCall(JAVA_LANG_LONG, "toBinaryString").parameterCount(1), - (args, memState, factory, method) -> numberAsString(args, memState, 1, Long.SIZE)) + toValue((args, memState, factory, method) -> numberAsString(args, memState, 1, Long.SIZE))) .register(instanceCall(JAVA_LANG_ENUM, "name").parameterCount(0), - (args, memState, factory, method) -> enumName(args.myQualifier, memState, method.getReturnType())) + toValue((args, memState, factory, method) -> enumName(args.myQualifier, memState, method.getReturnType()))) .register(staticCall(JAVA_UTIL_COLLECTIONS, "emptyList", "emptySet", "emptyMap").parameterCount(0), - (args, memState, factory, method) -> getEmptyCollectionConstant(method)) + toValue((args, memState, factory, method) -> getEmptyCollectionConstant(method))) .register(exactInstanceCall(JAVA_LANG_CLASS, "getName", "getSimpleName", "getCanonicalName").parameterCount(0), - (args, memState, factory, method) -> className(memState, args.myQualifier, method.getName(), method.getReturnType())) + toValue((args, memState, factory, method) -> className(memState, args.myQualifier, method.getName(), method.getReturnType()))) .register(anyOf( staticCall(JAVA_UTIL_COLLECTIONS, "singleton", "singletonList", "singletonMap"), staticCall(JAVA_UTIL_LIST, "of"), @@ -128,11 +151,11 @@ public final class CustomMethodHandlers { staticCall(JAVA_LANG_LONG, "compare").parameterTypes("long", "long"), staticCall(JAVA_LANG_BYTE, "compare").parameterTypes("byte", "byte"), staticCall(JAVA_LANG_SHORT, "compare").parameterTypes("short", "short")), - (args, state, factory, method) -> compareInteger(args, state)) + toValue((args, state, factory, method) -> compareInteger(args, state))) .register(anyOf( instanceCall("java.util.Random", "nextInt").parameterTypes("int"), instanceCall("java.util.SplittableRandom", "nextInt").parameterTypes("int"), - instanceCall("java.util.SplittableRandom", "nextInt").parameterTypes("int", "int")), CustomMethodHandlers::randomNextInt) + instanceCall("java.util.SplittableRandom", "nextInt").parameterTypes("int", "int")), toValue(CustomMethodHandlers::randomNextInt)) .register(staticCall(JAVA_UTIL_ARRAYS, "copyOf"), CustomMethodHandlers::copyOfArray) .register(instanceCall(JAVA_UTIL_COLLECTION, "toArray").parameterTypes("T[]"), CustomMethodHandlers::collectionToArray) .register(instanceCall(JAVA_UTIL_COLLECTION, "toArray").parameterCount(0), CustomMethodHandlers::collectionToArray) @@ -141,7 +164,7 @@ public final class CustomMethodHandlers { public static CustomMethodHandler find(PsiMethod method) { CustomMethodHandler handler = null; if (isConstantCall(method)) { - handler = (arguments, state, factory, m) -> handleConstantCall(arguments, state, m); + handler = toValue((arguments, state, factory, m) -> handleConstantCall(arguments, state, m)); } CustomMethodHandler handler2 = CUSTOM_METHOD_HANDLERS.mapFirst(method); return handler == null ? handler2 : handler.compose(handler2); @@ -256,32 +279,32 @@ public final class CustomMethodHandlers { return intRange(LongRangeSet.range(-1, range.max() - 1)); } - private static @NotNull DfType copyOfArray(DfaCallArguments arguments, DfaMemoryState state, DfaValueFactory factory, PsiMethod method) { - if (arguments.myArguments.length < 2) return TOP; - DfType size = state.getDfType(arguments.myArguments[1]).meet(intRange(LongRangeSet.indexRange())); - if (size == BOTTOM) return FAIL; - return typedObject(method.getReturnType(), Nullability.NOT_NULL).meet(ARRAY_LENGTH.asDfType(size)).meet(LOCAL_OBJECT); + private static @Nullable DfaValue copyOfArray(DfaCallArguments arguments, DfaMemoryState state, DfaValueFactory factory, PsiMethod method) { + if (arguments.myArguments.length < 2) return null; + return factory.getBoxedFactory().createBoxed(typedObject(method.getReturnType(), Nullability.NOT_NULL).meet(LOCAL_OBJECT), + ARRAY_LENGTH, arguments.myArguments[1]); } - private static @NotNull DfType collectionFactory(DfaCallArguments args, + private static @Nullable DfaValue collectionFactory(DfaCallArguments args, DfaMemoryState memState, DfaValueFactory factory, PsiMethod method) { PsiType type = method.getReturnType(); - if (!(type instanceof PsiClassType)) return TOP; + if (!(type instanceof PsiClassType)) return null; int factor = PsiTypesUtil.classNameEquals(type, JAVA_UTIL_MAP) ? 2 : 1; - DfType size; + DfaValue size; if (method.isVarArgs()) { - size = memState.getDfType(ARRAY_LENGTH.createValue(factory, args.myArguments[0])); + size = ARRAY_LENGTH.createValue(factory, args.myArguments[0]); } else { - size = intValue(args.myArguments.length / factor); + size = factory.fromDfType(intValue(args.myArguments.length / factor)); } boolean asList = method.getName().equals("asList"); Mutability mutability = asList ? Mutability.MUTABLE : Mutability.UNMODIFIABLE; - DfType result = typedObject(type, Nullability.NOT_NULL) - .meet(COLLECTION_SIZE.asDfType(size)) - .meet(mutability.asDfType()); - return asList ? result.meet(LOCAL_OBJECT) : result; + DfType result = typedObject(type, Nullability.NOT_NULL).meet(mutability.asDfType()); + if (asList) { + result = result.meet(LOCAL_OBJECT); + } + return factory.getBoxedFactory().createBoxed(result, COLLECTION_SIZE, size); } private static DfType getEmptyCollectionConstant(PsiMethod method) { @@ -293,21 +316,19 @@ public final class CustomMethodHandlers { return constant(field, field.getType()); } - private static @NotNull DfType substring(DfaCallArguments args, DfaMemoryState state, DfaValueFactory factory, PsiType stringType) { - if (stringType == null || !stringType.equalsToText(JAVA_LANG_STRING)) return TOP; + private static @Nullable DfaValue substring(DfaCallArguments args, DfaMemoryState state, DfaValueFactory factory, PsiType stringType) { + if (stringType == null || !stringType.equalsToText(JAVA_LANG_STRING)) return null; DfaValue qualifier = args.myQualifier; DfaValue[] arguments = args.myArguments; - if (arguments.length < 1 || arguments.length > 2 || arguments[0] == null) return TOP; + if (arguments.length < 1 || arguments.length > 2 || arguments[0] == null) return null; DfaValue from = arguments[0]; DfaValue lenVal = STRING_LENGTH.createValue(factory, qualifier); DfaValue to = arguments.length == 1 ? lenVal : arguments[1]; - DfaValue resultLenVal = factory.getBinOpFactory().create(to, from, state, false, JavaTokenType.MINUS); - DfType resultLen = state.getDfType(resultLenVal); - if (!(resultLen instanceof DfIntType)) return FAIL; - resultLen = ((DfIntType)resultLen).meetRelation(RelationType.GE, intValue(0)); - if (!(resultLen instanceof DfIntType)) return FAIL; - resultLen = ((DfIntType)resultLen).meetRelation(RelationType.LE, state.getDfType(lenVal)); - return STRING_LENGTH.asDfType(resultLen, stringType); + DfaValue resultLen = factory.getBinOpFactory().create(to, from, state, false, JavaTokenType.MINUS); + if (resultLen instanceof DfaBinOpValue) { + resultLen = factory.fromDfType(state.getDfType(resultLen)); + } + return factory.getBoxedFactory().createBoxed(typedObject(stringType, Nullability.NOT_NULL), STRING_LENGTH, resultLen); } private static @NotNull DfType mathAbs(DfaValue[] args, DfaMemoryState memState, boolean isLong) { @@ -446,11 +467,12 @@ public final class CustomMethodHandlers { return TOP; } - private static @NotNull DfType collectionToArray(DfaCallArguments arguments, DfaMemoryState state, DfaValueFactory factory, PsiMethod method) { + private static @NotNull DfaValue collectionToArray(DfaCallArguments arguments, DfaMemoryState state, DfaValueFactory factory, PsiMethod method) { DfType result = NOT_NULL_OBJECT; - LongRangeSet finalRange; DfaValue collection = arguments.myQualifier; - LongRangeSet collectionSizeRange = DfIntType.extractRange(state.getDfType(COLLECTION_SIZE.createValue(factory, collection))); + DfaValue collectionSize = COLLECTION_SIZE.createValue(factory, collection); + LongRangeSet collectionSizeRange = DfIntType.extractRange(state.getDfType(collectionSize)); + DfaValue finalSize = collectionSize; if (arguments.myArguments.length == 1) { DfaValue array = arguments.myArguments[0]; DfType arrType = state.getDfType(array); @@ -460,20 +482,20 @@ public final class CustomMethodHandlers { } // Array size is max of collection size and argument array size LongRangeSet arraySizeRange = DfIntType.extractRange(state.getDfType(ARRAY_LENGTH.createValue(factory, array))); - LongRangeSet biggerArrays = collectionSizeRange.fromRelation(RelationType.GE).intersect(arraySizeRange); + LongRangeSet biggerArrays = collectionSizeRange.fromRelation(RelationType.GT).intersect(arraySizeRange); LongRangeSet biggerCollections = arraySizeRange.fromRelation(RelationType.GE).intersect(collectionSizeRange); - finalRange = biggerArrays.unite(biggerCollections); - } else { - finalRange = collectionSizeRange; + if (!biggerArrays.isEmpty()) { + finalSize = factory.fromDfType(intRange(biggerArrays.unite(biggerCollections))); + } } - return result.meet(ARRAY_LENGTH.asDfType(intRange(finalRange))); + return factory.getBoxedFactory().createBoxed(result, ARRAY_LENGTH, finalSize); } - private static @NotNull DfType stringToCharArray(DfaCallArguments arguments, DfaMemoryState state, DfaValueFactory factory, + private static @NotNull DfaValue stringToCharArray(DfaCallArguments arguments, DfaMemoryState state, DfaValueFactory factory, PsiMethod method) { DfaValue string = arguments.myQualifier; - DfType stringSizeRange = state.getDfType(STRING_LENGTH.createValue(factory, string)); - return typedObject(PsiType.CHAR.createArrayType(), Nullability.NOT_NULL) - .meet(LOCAL_OBJECT).meet(ARRAY_LENGTH.asDfType(stringSizeRange)); + DfaValue stringLength = STRING_LENGTH.createValue(factory, string); + return factory.getBoxedFactory().createBoxed(typedObject(PsiType.CHAR.createArrayType(), Nullability.NOT_NULL) + .meet(LOCAL_OBJECT), ARRAY_LENGTH, stringLength); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java index fb9ad517ef68..d8ca4ceff01f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java @@ -1219,7 +1219,8 @@ public class DfaMemoryStateImpl implements DfaMemoryState { if (value instanceof DfaBoxedValue) { DfaBoxedValue boxedValue = (DfaBoxedValue)value; DfaValue canonicalized = canonicalize(boxedValue.getWrappedValue()); - return Objects.requireNonNull(myFactory.getBoxedFactory().createBoxed(canonicalized, boxedValue.getDfType())); + if (canonicalized == boxedValue.getWrappedValue()) return boxedValue; + return myFactory.getBoxedFactory().createBoxed(boxedValue.getDfType(), boxedValue.getSpecialField(), canonicalized); } return value; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java index 44b01d8f29da..285bdee40326 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java @@ -359,7 +359,7 @@ public final class DfaUtil { public static DfaValue boxUnbox(DfaValue value, @Nullable PsiType type) { if (TypeConversionUtil.isPrimitiveWrapper(type)) { if (TypeConversionUtil.isPrimitiveAndNotNull(value.getType())) { - return value.getFactory().getBoxedFactory().createBoxed(value, DfTypes.typedObject(type, Nullability.NOT_NULL)); + return value.getFactory().getBoxedFactory().createBoxed(DfTypes.typedObject(type, Nullability.NOT_NULL), SpecialField.UNBOX, value); } } if (TypeConversionUtil.isPrimitiveAndNotNull(type)) { 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 b82de56fff0b..3e472e2a19e5 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 @@ -159,7 +159,7 @@ public abstract class InstructionVisitor { if (value instanceof DfaBinOpValue) { value = factory.fromDfType(state.getDfType(value)); } - state.push(factory.getBoxedFactory().createBoxed(value, instruction.getTargetType())); + state.push(factory.getBoxedFactory().createBoxed(instruction.getTargetType(), SpecialField.UNBOX, value)); return nextInstruction(instruction, runner, state); } 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 54dc2a77c0b2..3188979dea56 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 @@ -175,15 +175,6 @@ public enum SpecialField implements VariableDescriptor { return DfTypes.TOP; } - @NotNull - @Override - public DfaValue createValue(@NotNull DfaValueFactory factory, @Nullable DfaValue qualifier, boolean forAccessor) { - if (qualifier instanceof DfaBoxedValue) { - return ((DfaBoxedValue)qualifier).getWrappedValue(); - } - return super.createValue(factory, qualifier, forAccessor); - } - @Override boolean isMyQualifierType(PsiType type) { return TypeConversionUtil.isPrimitiveWrapper(type); @@ -300,6 +291,9 @@ public enum SpecialField implements VariableDescriptor { @NotNull @Override public DfaValue createValue(@NotNull DfaValueFactory factory, @Nullable DfaValue qualifier, boolean forAccessor) { + if (qualifier instanceof DfaBoxedValue && ((DfaBoxedValue)qualifier).getSpecialField() == this) { + return ((DfaBoxedValue)qualifier).getWrappedValue(); + } if (qualifier instanceof DfaVariableValue) { DfaVariableValue variableValue = (DfaVariableValue)qualifier; PsiModifierListOwner psiVariable = variableValue.getPsiVariable(); 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 787d41caca90..ed33f50b53b8 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 @@ -660,9 +660,9 @@ public class StandardInstructionVisitor extends InstructionVisitor { if (method != null) { CustomMethodHandlers.CustomMethodHandler handler = CustomMethodHandlers.find(method); if (handler != null) { - DfType dfType = handler.getMethodResult(callArguments, state, factory, method); - if (dfType != TOP) { - return factory.fromDfType(dfType); + DfaValue value = handler.getMethodResultValue(callArguments, state, factory, method); + if (value != null) { + return value; } } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaBoxedValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaBoxedValue.java index 606c050e5b77..e1076e5995e4 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaBoxedValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaBoxedValue.java @@ -3,25 +3,35 @@ package com.intellij.codeInspection.dataFlow.value; import com.intellij.codeInspection.dataFlow.SpecialField; import com.intellij.codeInspection.dataFlow.types.DfType; +import com.intellij.codeInspection.dataFlow.types.DfTypes; +import com.intellij.psi.JavaPsiFacade; import com.intellij.psi.PsiType; -import gnu.trove.TIntObjectHashMap; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Arrays; +import java.util.HashMap; + +import static com.intellij.psi.CommonClassNames.JAVA_LANG_STRING; + public final class DfaBoxedValue extends DfaValue { private final @NotNull DfaVariableValue myWrappedValue; + private final @NotNull SpecialField mySpecialField; private final @NotNull DfType myType; - private DfaBoxedValue(@NotNull DfaVariableValue valueToWrap, @NotNull DfaValueFactory factory, @NotNull DfType type) { - super(factory); + private DfaBoxedValue(@NotNull DfaVariableValue valueToWrap, + @NotNull SpecialField field, + @NotNull DfType type) { + super(valueToWrap.getFactory()); myWrappedValue = valueToWrap; + mySpecialField = field; myType = type; } @NonNls public String toString() { - return "Boxed "+myWrappedValue.toString(); + return myType + ";" + mySpecialField + "=" + myWrappedValue; } @NotNull @@ -29,6 +39,11 @@ public final class DfaBoxedValue extends DfaValue { return myWrappedValue; } + @NotNull + public SpecialField getSpecialField() { + return mySpecialField; + } + @Nullable @Override public PsiType getType() { @@ -42,7 +57,7 @@ public final class DfaBoxedValue extends DfaValue { } public static class Factory { - private final TIntObjectHashMap cachedValues = new TIntObjectHashMap<>(); + private final HashMap cachedValues = new HashMap<>(); private final DfaValueFactory myFactory; @@ -51,26 +66,31 @@ public final class DfaBoxedValue extends DfaValue { } @NotNull - public DfaValue createBoxed(@NotNull DfaValue valueToWrap, @NotNull DfType type) { - if (valueToWrap instanceof DfaVariableValue && ((DfaVariableValue)valueToWrap).getDescriptor() == SpecialField.UNBOX) { - DfaVariableValue qualifier = ((DfaVariableValue)valueToWrap).getQualifier(); - if (qualifier != null && type.equals(qualifier.getDfType())) { + public DfaValue createBoxed(@NotNull DfType qualifierType, + @NotNull SpecialField specialField, @NotNull DfaValue specialFieldValue) { + if (specialFieldValue instanceof DfaVariableValue && ((DfaVariableValue)specialFieldValue).getDescriptor() == specialField) { + DfaVariableValue qualifier = ((DfaVariableValue)specialFieldValue).getQualifier(); + if (qualifier != null && qualifierType.isSuperType(qualifier.getDfType())) { return qualifier; } } - if (valueToWrap instanceof DfaTypeValue) { - DfType dfType = SpecialField.UNBOX.asDfType(valueToWrap.getDfType()).meet(type); + if (specialFieldValue instanceof DfaTypeValue) { + DfType dfType; + DfType fieldValue = specialFieldValue.getDfType(); + if (specialField == SpecialField.STRING_LENGTH && fieldValue.isConst(0)) { + dfType = DfTypes.constant("", JavaPsiFacade.getElementFactory(specialFieldValue.getFactory().getProject()) + .createTypeByFQClassName(JAVA_LANG_STRING)); + } + else { + dfType = qualifierType.meet(specialField.asDfType(fieldValue)); + } return myFactory.fromDfType(dfType); } - if (valueToWrap instanceof DfaVariableValue) { - int id = valueToWrap.getID(); - DfaBoxedValue boxedValue = cachedValues.get(id); - if (boxedValue == null) { - cachedValues.put(id, boxedValue = new DfaBoxedValue((DfaVariableValue)valueToWrap, myFactory, type)); - } - return boxedValue; + if (specialFieldValue instanceof DfaVariableValue) { + return cachedValues.computeIfAbsent(Arrays.asList(specialFieldValue, specialField, qualifierType), + k -> new DfaBoxedValue((DfaVariableValue)specialFieldValue, specialField, qualifierType)); } - return myFactory.fromDfType(type); + return myFactory.fromDfType(qualifierType); } } } 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 c76a6b586aad..70a05f5d724e 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 @@ -234,7 +234,8 @@ public class DfaExpressionFactory { PsiType type = expression.getType(); if (expression instanceof PsiArrayInitializerExpression) { int length = ((PsiArrayInitializerExpression)expression).getInitializers().length; - return myFactory.fromDfType(SpecialField.ARRAY_LENGTH.asDfType(DfTypes.intValue(length), type)); + return myFactory.fromDfType(SpecialField.ARRAY_LENGTH.asDfType(DfTypes.intValue(length)) + .meet(DfTypes.typedObject(type, Nullability.NOT_NULL))); } DfType dfType = DfTypes.typedObject(type, NullabilityUtil.getExpressionNullability(expression)); if (type instanceof PsiPrimitiveType && targetType instanceof PsiPrimitiveType && !type.equals(targetType)) { 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 403e69c4b628..01ada68d5c18 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 @@ -144,7 +144,7 @@ public class DfaValueFactory { Boolean boo = computeJavaLangBooleanFieldReference(variable); if (boo != null) { DfaValue unboxed = getConstant(boo, PsiType.BOOLEAN); - return getBoxedFactory().createBoxed(unboxed, DfTypes.typedObject(type, Nullability.NOT_NULL)); + return getBoxedFactory().createBoxed(DfTypes.typedObject(type, Nullability.NOT_NULL), SpecialField.UNBOX, unboxed); } if (DfaUtil.isEmptyCollectionConstantField(variable)) { return getConstant(variable, type); diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/ArraysAsList.java b/java/java-tests/testData/inspection/dataFlow/fixture/ArraysAsList.java index 35c82e0ea1bc..e3289dbd9e92 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/ArraysAsList.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/ArraysAsList.java @@ -12,6 +12,11 @@ class Foo { if (list2.size() == 1) {} if (list3.size() == 3) {} } + + void test2(String[] arr) { + List list = Arrays.asList(arr); + if (list.size() == arr.length) {} + } native void unknown(); } \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/ArraysCopyOf.java b/java/java-tests/testData/inspection/dataFlow/fixture/ArraysCopyOf.java index 195b9e68ff8c..887f3865cb02 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/ArraysCopyOf.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/ArraysCopyOf.java @@ -31,4 +31,9 @@ public class ArraysCopyOf { if (copy3.length == 0) {} int[] copy4 = Arrays.copyOf(arr, copy3.length - 1); } + + void test2(int[] arr, int size) { + int[] copy = Arrays.copyOf(arr, size); + if (copy.length == size) {} + } } \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/CollectionToArray.java b/java/java-tests/testData/inspection/dataFlow/fixture/CollectionToArray.java index 0008e7863d4c..e54ec500f02f 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/CollectionToArray.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/CollectionToArray.java @@ -75,4 +75,14 @@ public class CollectionToArray { assert arr.length == 0; return list.toArray(arr); } + + void testSizeEquality(List list, int x) { + String[] arr = list.toArray(new String[0]); + if (x == 1 && list.get(arr.length).isEmpty()) { + } + if (x == 2 && arr[list.size()].isEmpty()) { + } + if (list.isEmpty()) return; + if (arr.length == 0) return; + } } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/StringSubstring.java b/java/java-tests/testData/inspection/dataFlow/fixture/StringSubstring.java index 94a426de374a..4a7961b49af8 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/StringSubstring.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/StringSubstring.java @@ -88,4 +88,11 @@ class StringSubstring { if (s2.length() != 4) {} } } + + void testSubstringVarRef(String s, int from, int length) { + String s1 = s.substring(0, length); + if (s1.length() == length) {} + String s2 = s.substring(from, from + length); + if (s2.length() == length) {} + } } \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/StringToCharArray.java b/java/java-tests/testData/inspection/dataFlow/fixture/StringToCharArray.java index 08d85a808536..655513d06c59 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/StringToCharArray.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/StringToCharArray.java @@ -9,6 +9,7 @@ public class StringToCharArray { if (s.startsWith("--")) { char[] arr = s.toCharArray(); if (arr.length > 1) {} + if (arr.length == s.length()) {} } } }