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 extends MethodContract> 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 extends MethodContract> 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 extends MethodContract> contracts, PsiExpression anchor) {
- if (ContainerUtil.exists(contracts, c -> c.getReturnValue().isFail())) {
+ private void processFailResult(@Nullable PsiMethod method, @NotNull List extends MethodContract> 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) {}
+ }
}