diff --git a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties index 849fa0ae8adf..081f47b9058a 100644 --- a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties +++ b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties @@ -41,6 +41,7 @@ dataflow.message.constant.condition.when.reached=Condition #ref #lo dataflow.message.constant.condition=Condition #ref #loc is always {0, choice, 0#false|1#true} dataflow.message.constant.method.reference=Method reference result is always ''{0}'' dataflow.message.constant.no.ref=Condition is always {0, choice, 0#false|1#true} +dataflow.message.fail=The call to '#ref' always fails with an exception dataflow.message.contract.fail.index=The call to '#ref' always fails as an argument is out of bounds dataflow.message.contract.fail=The call to '#ref' always fails, according to its method contracts dataflow.message.immutable.modified=Immutable object is modified 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 119b128d4719..7ebb3a92c198 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 @@ -45,7 +45,8 @@ public final class CustomMethodHandlers { exactInstanceCall(JAVA_LANG_STRING, "contains", "indexOf", "startsWith", "endsWith", "lastIndexOf", "length", "trim", "substring", "equals", "equalsIgnoreCase", "charAt", "codePointAt", "compareTo", "replace"), staticCall(JAVA_LANG_STRING, "valueOf").parameterCount(1), - staticCall(JAVA_LANG_MATH, "abs", "sqrt", "min", "max"), + staticCall(JAVA_LANG_MATH, "abs", "sqrt", "min", "max", "addExact", "absExact", "subtractExact", "multiplyExact", + "incrementExact", "decrementExact", "toIntExact", "negateExact", "sin", "cos", "tan", "asin", "acos", "atan", "cbrt"), staticCall(JAVA_LANG_INTEGER, "toString", "toBinaryString", "toHexString", "toOctalString", "toUnsignedString").parameterTypes("int"), staticCall(JAVA_LANG_LONG, "toString", "toBinaryString", "toHexString", "toOctalString", "toUnsignedString").parameterTypes("long"), staticCall(JAVA_LANG_DOUBLE, "toString", "toHexString").parameterTypes("double"), @@ -220,8 +221,12 @@ public final class CustomMethodHandlers { return handler == null ? handler2 : handler.compose(handler2); } + /** + * @param method method to check + * @return true if method will be evaluated to constant when all arguments are constant + */ @Contract("null -> false") - private static boolean isConstantCall(PsiMethod method) { + public static boolean isConstantCall(PsiMethod method) { return CONSTANT_CALLS.methodMatches(method); } @@ -251,7 +256,13 @@ public final class CustomMethodHandlers { try { result = jvmMethod.invoke(qualifierValue, args.toArray()); } - catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + catch (InvocationTargetException e) { + if (e.getCause() instanceof NumberFormatException || e.getCause() instanceof ArithmeticException) { + return DfType.FAIL; + } + return DfType.TOP; + } + catch (IllegalAccessException | IllegalArgumentException e) { return DfType.TOP; } return constant(result, returnType); @@ -265,7 +276,7 @@ public final class CustomMethodHandlers { return Result.create(reflection, method); } - private Class toJvmType(PsiType type) { + private static Class toJvmType(PsiType type) { if (TypeUtils.isJavaLangString(type)) { return String.class; } @@ -281,6 +292,12 @@ public final class CustomMethodHandlers { if (PsiTypes.booleanType().equals(type)) { return boolean.class; } + if (PsiTypes.byteType().equals(type)) { + return byte.class; + } + if (PsiTypes.shortType().equals(type)) { + return short.class; + } if (PsiTypes.charType().equals(type)) { return char.class; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java index 8e2e068a4015..130e2e47c06b 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java @@ -597,6 +597,11 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec private void reportAlwaysFailingCalls(ProblemReporter reporter, DataFlowInstructionVisitor visitor) { visitor.alwaysFailingCalls().remove(TestUtils::isExceptionExpected).forEach(anchor -> { List contracts = DataFlowInstructionVisitor.getContracts(anchor); + if (contracts != null && contracts.isEmpty()) { + PsiMethod method = anchor instanceof PsiCallExpression call ? call.resolveMethod() : + anchor instanceof PsiMethodReferenceExpression methodRef ? tryCast(methodRef.resolve(), PsiMethod.class) : null; + contracts = DfaUtil.addRangeContracts(method, List.of()); + } if (contracts == null) return; String message = getContractMessage(contracts); LocalQuickFix causeFix = createExplainFix(anchor, new TrackingRunner.FailingCallDfaProblemType()); @@ -605,6 +610,9 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec } private static @NotNull @InspectionMessage String getContractMessage(List contracts) { + if (contracts.isEmpty()) { + return JavaAnalysisBundle.message("dataflow.message.fail"); + } if (ContainerUtil.and(contracts, mc -> ContainerUtil.and(mc.getConditions(), ContractValue::isBoundCheckingCondition))) { return JavaAnalysisBundle.message("dataflow.message.contract.fail.index"); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java index 6f8dbe91c672..4440f82596f8 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java @@ -1950,7 +1950,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { addInstruction(new MethodCallInstruction(expression, JavaDfaValueFactory.getExpressionDfaValue(myFactory, expression), contracts)); anchor = expression; } - processFailResult(contracts, anchor); + processFailResult(method, contracts, anchor); addMethodThrows(method); if (expression != null) { @@ -1958,8 +1958,9 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { } } - private void processFailResult(List contracts, PsiExpression anchor) { - if (ContainerUtil.exists(contracts, c -> c.getReturnValue().isFail())) { + private void processFailResult(@Nullable PsiMethod method, @NotNull List contracts, @NotNull PsiExpression anchor) { + if ((CustomMethodHandlers.isConstantCall(method) && !PsiTypes.booleanType().equals(method.getReturnType())) + || ContainerUtil.exists(contracts, c -> c.getReturnValue().isFail())) { DfaControlTransferValue transfer = createTransfer(JAVA_LANG_THROWABLE); // if a contract resulted in 'fail', handle it addInstruction(new EnsureInstruction(new ContractFailureProblem(anchor), RelationType.NE, DfType.FAIL, transfer)); @@ -2042,7 +2043,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { JavaMethodContractUtil.getMethodCallContracts(constructor, null); contracts = DfaUtil.addRangeContracts(constructor, contracts); addInstruction(new MethodCallInstruction(expression, precalculatedNewValue, contracts)); - processFailResult(contracts, expression); + processFailResult(constructor, contracts, expression); addMethodThrows(constructor); } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/AssertThat.java b/java/java-tests/testData/inspection/dataFlow/fixture/AssertThat.java index 9577da634e5d..747027445fa6 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/AssertThat.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/AssertThat.java @@ -140,7 +140,7 @@ class Contracts { } Assertions.assertThat(array2).isNotEmpty(); if (array2.length == 0) {} - Assertions.assertThat(array2).isEmpty(); + Assertions.assertThat(array2).isEmpty(); } void testString(String str, String str2) { @@ -150,7 +150,7 @@ class Contracts { } Assertions.assertThat(str2).isNotEmpty(); if (str2.length() == 0) {} - Assertions.assertThat(str2).isEmpty(); + Assertions.assertThat(str2).isEmpty(); } void testList(List list, List list2) { @@ -160,7 +160,7 @@ class Contracts { } Assertions.assertThat(list2).isNotEmpty(); if (list2.size() == 0) {} - Assertions.assertThat(list2).isEmpty(); + Assertions.assertThat(list2).isEmpty(); } void testAtomicBoolean() { diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeKnownMethods.java b/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeKnownMethods.java index b0960179b3f5..6063d3bbfd7e 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeKnownMethods.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeKnownMethods.java @@ -405,4 +405,20 @@ public class LongRangeKnownMethods { int val = Integer.parseInt(s); if (val != 1234) return; } + + void testParseIncorrect(String s) { + if (!s.equals("1234L")) return; + int val = Integer.parseInt(s); + if (val != 1234) return; + } + + void testByteTooBig() { + byte b; + if (Math.random() > 0.5) { + b = Byte.parseByte("128"); + } else { + b = Byte.parseByte("127"); + } + if (b == 127) {} + } }