mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-dfa] Support failure handling in constant evaluator
Part of IDEA-326613 Support constant evaluation of Integer.parseInt and friends Required for IDEA-324389 Inspection: Integer.parseInt() with a numeric String literal could be simplified Also: added more Math methods GitOrigin-RevId: 199a4bc5eaea9e8c2f26b6d4dfa5292ca8dd7795
This commit is contained in:
committed by
intellij-monorepo-bot
parent
7df312a1cd
commit
76aa489d1d
@@ -41,6 +41,7 @@ dataflow.message.constant.condition.when.reached=Condition <code>#ref</code> #lo
|
||||
dataflow.message.constant.condition=Condition <code>#ref</code> #loc is always <code>{0, choice, 0#false|1#true}</code>
|
||||
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
|
||||
|
||||
+21
-4
@@ -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;
|
||||
}
|
||||
|
||||
+8
@@ -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");
|
||||
}
|
||||
|
||||
+5
-4
@@ -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);
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ class Contracts {
|
||||
}
|
||||
Assertions.assertThat(array2).isNotEmpty();
|
||||
if (<warning descr="Condition 'array2.length == 0' is always 'false'">array2.length == 0</warning>) {}
|
||||
Assertions.assertThat(array2).<warning descr="The call to 'isEmpty' always fails as an argument is out of bounds">isEmpty</warning>();
|
||||
Assertions.assertThat(array2).<warning descr="The call to 'isEmpty' always fails with an exception">isEmpty</warning>();
|
||||
}
|
||||
|
||||
void testString(String str, String str2) {
|
||||
@@ -150,7 +150,7 @@ class Contracts {
|
||||
}
|
||||
Assertions.assertThat(str2).isNotEmpty();
|
||||
if (<warning descr="Condition 'str2.length() == 0' is always 'false'">str2.length() == 0</warning>) {}
|
||||
Assertions.assertThat(str2).<warning descr="The call to 'isEmpty' always fails as an argument is out of bounds">isEmpty</warning>();
|
||||
Assertions.assertThat(str2).<warning descr="The call to 'isEmpty' always fails with an exception">isEmpty</warning>();
|
||||
}
|
||||
|
||||
void testList(List<String> list, List<String> list2) {
|
||||
@@ -160,7 +160,7 @@ class Contracts {
|
||||
}
|
||||
Assertions.assertThat(list2).isNotEmpty();
|
||||
if (<warning descr="Condition 'list2.size() == 0' is always 'false'">list2.size() == 0</warning>) {}
|
||||
Assertions.assertThat(list2).<warning descr="The call to 'isEmpty' always fails as an argument is out of bounds">isEmpty</warning>();
|
||||
Assertions.assertThat(list2).<warning descr="The call to 'isEmpty' always fails with an exception">isEmpty</warning>();
|
||||
}
|
||||
|
||||
void testAtomicBoolean() {
|
||||
|
||||
@@ -405,4 +405,20 @@ public class LongRangeKnownMethods {
|
||||
int val = Integer.parseInt(s);
|
||||
if (<warning descr="Condition 'val != 1234' is always 'false'">val != 1234</warning>) return;
|
||||
}
|
||||
|
||||
void testParseIncorrect(String s) {
|
||||
if (!s.equals("1234L")) return;
|
||||
int val = Integer.<warning descr="The call to 'parseInt' always fails with an exception">parseInt</warning>(s);
|
||||
if (val != 1234) return;
|
||||
}
|
||||
|
||||
void testByteTooBig() {
|
||||
byte b;
|
||||
if (Math.random() > 0.5) {
|
||||
b = Byte.<warning descr="The call to 'parseByte' always fails with an exception">parseByte</warning>("128");
|
||||
} else {
|
||||
b = Byte.parseByte("127");
|
||||
}
|
||||
if (<warning descr="Condition 'b == 127' is always 'true'">b == 127</warning>) {}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user