From ee1c23f88b744b0abc0213cff2f57f3d240d3879 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 10 Jun 2025 09:30:17 +0200 Subject: [PATCH] [java-dfa] Native support of ObjectUtils.{all,any}[not]Null methods Fixes IDEA-373254 Recognized ObjectUtils.allNotNull method in data flow inspection (at least, my understanding of it) GitOrigin-RevId: 5bf63072a84b22a3a5de58d53c8521f1296a0fd1 --- .../dataFlow/java/CFGBuilder.java | 14 ++++ .../dataFlow/java/ControlFlowAnalyzer.java | 2 +- .../java/inliner/AllNotNullInliner.java | 53 +++++++++++++++ .../fixture/ObjectUtilsNullMethods.java | 65 +++++++++++++++++++ .../DataFlowInspection21Test.java | 4 ++ 5 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inliner/AllNotNullInliner.java create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/ObjectUtilsNullMethods.java diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/CFGBuilder.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/CFGBuilder.java index 68e697c2a865..4e1142fbd21c 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/CFGBuilder.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/CFGBuilder.java @@ -298,6 +298,20 @@ public class CFGBuilder { return add(new ResultOfInstruction(new JavaExpressionAnchor(expression))); } + /** + * Generate instructions to apply a boolean 'not' operation to top stack value + *

+ * Stack before: ... value + *

+ * Stack after: ... not(value) + * + * @return this builder + */ + public CFGBuilder not() { + add(new NotInstruction(null)); + return this; + } + /** * Generate instructions to perform an Class.isInstance operation *

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 bff25635cf48..15dd93a4335f 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 @@ -2752,7 +2752,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { private static final CallInliner[] INLINERS = { new AssertJInliner(), new OptionalChainInliner(), new LambdaInliner(), new CollectionUpdateInliner(), new StreamChainInliner(), new MapUpdateInliner(), new AssumeInliner(), new ClassMethodsInliner(), - new AssertAllInliner(), new BoxingInliner(), new SimpleMethodInliner(), new AccessorInliner(), + new AssertAllInliner(), new AllNotNullInliner(), new BoxingInliner(), new SimpleMethodInliner(), new AccessorInliner(), new TransformInliner(), new EnumCompareInliner(), new IndexOfInliner(), new AssertInstanceOfInliner() }; } \ No newline at end of file diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inliner/AllNotNullInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inliner/AllNotNullInliner.java new file mode 100644 index 000000000000..dd4448198a09 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inliner/AllNotNullInliner.java @@ -0,0 +1,53 @@ +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.codeInspection.dataFlow.java.inliner; + +import com.intellij.codeInspection.dataFlow.java.CFGBuilder; +import com.intellij.codeInspection.dataFlow.types.DfTypes; +import com.intellij.psi.PsiClassType; +import com.intellij.psi.PsiExpression; +import com.intellij.psi.PsiMethodCallExpression; +import com.intellij.psi.PsiType; +import com.siyeh.ig.callMatcher.CallMatcher; +import com.siyeh.ig.psiutils.MethodCallUtils; +import org.jetbrains.annotations.NotNull; + +import static com.siyeh.ig.callMatcher.CallMatcher.anyOf; +import static com.siyeh.ig.callMatcher.CallMatcher.staticCall; + +/** + * JUnit5 Assertions.assertAll + */ +public class AllNotNullInliner implements CallInliner { + private static final CallMatcher NULL_TESTS = + anyOf( + staticCall("org.apache.commons.lang3.ObjectUtils", "allNull").parameterTypes("java.lang.Object..."), + staticCall("org.apache.commons.lang3.ObjectUtils", "allNotNull").parameterTypes("java.lang.Object..."), + staticCall("org.apache.commons.lang3.ObjectUtils", "anyNull").parameterTypes("java.lang.Object..."), + staticCall("org.apache.commons.lang3.ObjectUtils", "anyNotNull").parameterTypes("java.lang.Object...") + ); + + @Override + public boolean tryInlineCall(@NotNull CFGBuilder builder, @NotNull PsiMethodCallExpression call) { + if (!NULL_TESTS.matches(call) || !MethodCallUtils.isVarArgCall(call)) return false; + PsiExpression[] args = call.getArgumentList().getExpressions(); + String methodName = call.getMethodExpression().getReferenceName(); + PsiClassType objectType = PsiType.getJavaLangObject(call.getManager(), call.getResolveScope()); + boolean allMatchResult = "allNotNull".equals(methodName) || "allNull".equals(methodName); + builder.push(DfTypes.booleanValue(!allMatchResult)); + for (PsiExpression arg : args) { + builder.pushExpression(arg) + .boxUnbox(arg, objectType); + if ("allNotNull".equals(methodName) || "anyNull".equals(methodName)) { + builder.ifNotNull(); + } else { + builder.ifNull(); + } + } + builder.not(); + for (int i = 0; i < args.length; i++) { + builder.end(); + } + builder.resultOf(call); + return true; + } +} diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/ObjectUtilsNullMethods.java b/java/java-tests/testData/inspection/dataFlow/fixture/ObjectUtilsNullMethods.java new file mode 100644 index 000000000000..b8ff83e0e276 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/ObjectUtilsNullMethods.java @@ -0,0 +1,65 @@ +package org.apache.commons.lang3; + +class ObjectUtils { + public static native boolean allNotNull(final Object... values); + public static native boolean allNull(final Object... values); + public static native boolean anyNotNull(final Object... values); + public static native boolean anyNull(final Object... values); +} + +class Test { + void use(Object o1, Object o2, Object o3, Object o4) { + if (ObjectUtils.allNull(o1, o2, o3, o4)) { + if (o1 == null) {} + if (o2 == null) {} + if (o3 == null) {} + if (o4 == null) {} + if (ObjectUtils.allNull(o1, o2, o3, o4)) {} + if (ObjectUtils.anyNull(o1, o2, o3, o4)) {} + if (ObjectUtils.anyNotNull(o1, o2, o3, o4)) {} + if (ObjectUtils.allNotNull(o1, o2, o3, o4)) {} + } + if (ObjectUtils.anyNull(o1, o2, o3, o4)) { + if (o1 != null) { + if (o2 != null) { + if (o3 != null) { + if (o4 != null) {} + } + } + } + if (ObjectUtils.allNull(o1, o2, o3, o4)) {} + if (ObjectUtils.anyNull(o1, o2, o3, o4)) {} + if (ObjectUtils.anyNotNull(o1, o2, o3, o4)) {} + if (ObjectUtils.allNotNull(o1, o2, o3, o4)) {} + } + if (ObjectUtils.anyNotNull(o1, o2, o3, o4)) { + if (o1 == null) { + if (o2 == null) { + if (o3 == null) { + if (o4 == null) {} + } + } + } + if (ObjectUtils.allNull(o1, o2, o3, o4)) {} + if (ObjectUtils.anyNull(o1, o2, o3, o4)) {} + if (ObjectUtils.anyNotNull(o1, o2, o3, o4)) {} + if (ObjectUtils.allNotNull(o1, o2, o3, o4)) {} + } + if (ObjectUtils.allNotNull(o1, o2, o3, o4)) { + if (o1 != null) {} + if (o2 != null) {} + if (o3 != null) {} + if (o4 != null) {} + if (ObjectUtils.allNull(o1, o2, o3, o4)) {} + if (ObjectUtils.anyNull(o1, o2, o3, o4)) {} + if (ObjectUtils.anyNotNull(o1, o2, o3, o4)) {} + if (ObjectUtils.allNotNull(o1, o2, o3, o4)) {} + } + } + + void box(int i1, int i2, int i3, int i4) { + if (ObjectUtils.allNotNull(i1, i2, i3, i4)) { + + } + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java index ef879e84a0e2..7c7c2fccd993 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java @@ -188,4 +188,8 @@ public class DataFlowInspection21Test extends DataFlowInspectionTestCase { public void testSwitchNoUnreachableBranchesDueToUnresolvedType() { doTest(); } + + public void testObjectUtilsNullMethods() { + doTest(); + } } \ No newline at end of file