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 e7e836009ecd..8ae135db2ca3 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 @@ -606,10 +606,12 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec } private void reportAlwaysFailingCalls(ProblemReporter reporter, DataFlowInstructionVisitor visitor) { - visitor.alwaysFailingCalls().remove(TestUtils::isExceptionExpected).forEach(call -> { - String message = getContractMessage(JavaMethodContractUtil.getMethodCallContracts(call)); - LocalQuickFix causeFix = createExplainFix(call, new TrackingRunner.FailingCallDfaProblemType()); - reporter.registerProblem(getElementToHighlight(call), message, LocalQuickFix.notNullElements(causeFix)); + visitor.alwaysFailingCalls().remove(TestUtils::isExceptionExpected).forEach(anchor -> { + List contracts = DataFlowInstructionVisitor.getContracts(anchor); + if (contracts == null) return; + String message = getContractMessage(contracts); + LocalQuickFix causeFix = createExplainFix(anchor, new TrackingRunner.FailingCallDfaProblemType()); + reporter.registerProblem(getElementToHighlight(anchor), message, LocalQuickFix.notNullElements(causeFix)); }); } @@ -620,22 +622,22 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec return JavaAnalysisBundle.message("dataflow.message.contract.fail"); } - private static @NotNull PsiElement getElementToHighlight(@NotNull PsiCall call) { + private static @NotNull PsiElement getElementToHighlight(@NotNull PsiElement element) { PsiJavaCodeReferenceElement ref; - if (call instanceof PsiNewExpression) { - ref = ((PsiNewExpression)call).getClassReference(); + if (element instanceof PsiNewExpression newExpression) { + ref = newExpression.getClassReference(); } - else if (call instanceof PsiMethodCallExpression) { - ref = ((PsiMethodCallExpression)call).getMethodExpression(); + else if (element instanceof PsiMethodCallExpression callExpression) { + ref = callExpression.getMethodExpression(); } else { - return call; + return element; } if (ref != null) { PsiElement name = ref.getReferenceNameElement(); return name != null ? name : ref; } - return call; + return element; } private void reportNullableArgumentPassedToNonAnnotatedMethodRef(@NotNull ProblemReporter reporter, diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInstructionVisitor.java index 6e3ec5073708..48da05cb1d4f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInstructionVisitor.java @@ -195,8 +195,8 @@ final class DataFlowInstructionVisitor implements JavaDfaListener { (element, info) -> info.alwaysFails()); } - StreamEx alwaysFailingCalls() { - return StreamEx.ofKeys(myFailingCalls, v -> v).map(ContractFailureProblem::getAnchor).select(PsiCallExpression.class).distinct(); + StreamEx alwaysFailingCalls() { + return StreamEx.ofKeys(myFailingCalls, v -> v).map(ContractFailureProblem::getAnchor).distinct(); } boolean isAlwaysReturnsNotNull(Instruction[] instructions) { @@ -326,11 +326,9 @@ final class DataFlowInstructionVisitor implements JavaDfaListener { myArrayStoreProblems.put(storeProblem.getAnchor(), Pair.create(storeProblem.getFromType(), storeProblem.getToType())); } else if (problem instanceof ContractFailureProblem contractFailure) { - if (contractFailure.getAnchor() instanceof PsiCallExpression call) { - Boolean isFailing = myFailingCalls.get(problem); - if (isFailing != null || !hasTrivialFailContract(call)) { - myFailingCalls.put(contractFailure, failed == ThreeState.YES && !Boolean.FALSE.equals(isFailing)); - } + Boolean isFailing = myFailingCalls.get(problem); + if (isFailing != null || !hasTrivialFailContract(contractFailure.getAnchor())) { + myFailingCalls.put(contractFailure, failed == ThreeState.YES && !Boolean.FALSE.equals(isFailing)); } } else if (problem instanceof NullabilityProblemKind.NullabilityProblem nullabilityProblem) { @@ -352,9 +350,9 @@ final class DataFlowInstructionVisitor implements JavaDfaListener { myEndOfInitializerStates.add(state.createCopy()); } - private static boolean hasTrivialFailContract(PsiCallExpression call) { - List contracts = JavaMethodContractUtil.getMethodCallContracts(call); - return contracts.size() == 1 && contracts.get(0).isTrivial() && contracts.get(0).getReturnValue().isFail(); + private static boolean hasTrivialFailContract(@NotNull PsiExpression call) { + List contracts = getContracts(call); + return contracts != null && contracts.size() == 1 && contracts.get(0).isTrivial() && contracts.get(0).getReturnValue().isFail(); } private void reportMutabilityViolation(boolean receiver, @NotNull PsiElement anchor) { @@ -374,6 +372,21 @@ final class DataFlowInstructionVisitor implements JavaDfaListener { } } + @Nullable + static List getContracts(@NotNull PsiExpression anchor) { + List contracts; + if (anchor instanceof PsiCallExpression call) { + contracts = JavaMethodContractUtil.getMethodCallContracts(call); + } + else if (anchor instanceof PsiMethodReferenceExpression methodRef && methodRef.resolve() instanceof PsiMethod method) { + contracts = JavaMethodContractUtil.getMethodContracts(method); + } + else { + return null; + } + return contracts; + } + private static class StateInfo { boolean ephemeralException; boolean normalException; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inliner/MapUpdateInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inliner/MapUpdateInliner.java index dae4818d219f..ef5d5e15a201 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inliner/MapUpdateInliner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inliner/MapUpdateInliner.java @@ -1,6 +1,4 @@ -/* - * Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. - */ +// 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.codeInsight.Nullability; @@ -14,6 +12,7 @@ import com.intellij.psi.CommonClassNames; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiMethodCallExpression; import com.intellij.psi.PsiType; +import com.intellij.psi.util.PsiUtil; import com.siyeh.ig.callMatcher.CallMatcher; import com.siyeh.ig.psiutils.ExpectedTypeUtils; import org.jetbrains.annotations.NotNull; @@ -40,6 +39,7 @@ public class MapUpdateInliner implements CallInliner { .pushExpression(qualifier) // stack: .. qualifier .ensure(RelationType.IS, Mutability.MUTABLE.asDfType(), new MutabilityProblem(call, true), null) .pushExpression(key) // stack: .. qualifier; key + .boxUnbox(key, PsiUtil.substituteTypeParameter(qualifier.getType(), CommonClassNames.JAVA_UTIL_MAP, 0, true)) .evaluateFunction(function); String name = Objects.requireNonNull(call.getMethodExpression().getReferenceName()); switch (name) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inliner/StreamChainInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inliner/StreamChainInliner.java index 6ec3b0172a79..031f59b33d56 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inliner/StreamChainInliner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inliner/StreamChainInliner.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// 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.codeInsight.Nullability; @@ -56,6 +42,8 @@ public class StreamChainInliner implements CallInliner { "average", "forEach", "forEachOrdered", "min", "max", "toList", "toSet"}; private static final CallMatcher TERMINAL_CALL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, TERMINALS); + private static final CallMatcher FOR_COLLECTION_TERMINAL = instanceCall(JAVA_LANG_ITERABLE, "forEach").parameterCount(1); + private static final CallMatcher COLLECTION_SOURCE = anyOf(staticCall(JAVA_UTIL_LIST, "of"), staticCall(JAVA_UTIL_ARRAYS, "asList")); private static final CallMatcher FOR_TERMINAL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "forEach", "forEachOrdered").parameterCount(1); private static final CallMatcher MATCH_TERMINAL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "anyMatch", "allMatch", "noneMatch").parameterCount(1); @@ -824,6 +812,14 @@ public class StreamChainInliner implements CallInliner { @Override public boolean tryInlineCall(@NotNull CFGBuilder builder, @NotNull PsiMethodCallExpression call) { + if (FOR_COLLECTION_TERMINAL.test(call)) { + PsiMethodCallExpression qualifierCall = MethodCallUtils.getQualifierMethodCall(call); + if (qualifierCall == null || !InheritanceUtil.isInheritor(qualifierCall.getType(), JAVA_UTIL_COLLECTION)) return false; + Step terminalStep = new LambdaTerminalStep(call); + startStreamFromCollection(builder, terminalStep, qualifierCall); + terminalStep.pushResult(builder); + return true; + } if (TERMINAL_CALL.test(call)) { return inlineCompleteStream(builder, call); } @@ -916,35 +912,68 @@ public class StreamChainInliner implements CallInliner { .chain(firstStep::iteration); return; } - PsiExpression qualifierExpression = null; - SpecialField sizeField = null; if (array) { - qualifierExpression = sourceCall.getArgumentList().getExpressions()[0]; - sizeField = SpecialField.ARRAY_LENGTH; + PsiExpression arrayExpression = sourceCall.getArgumentList().getExpressions()[0]; + startStreamFromContainer(builder, firstStep, arrayExpression, SpecialField.ARRAY_LENGTH, inType); + return; } else if (COLLECTION_STREAM.test(sourceCall)) { - qualifierExpression = sourceCall.getMethodExpression().getQualifierExpression(); - sizeField = SpecialField.COLLECTION_SIZE; - } - if (qualifierExpression != null) { - builder.pushExpression(qualifierExpression) - .chain(firstStep::before) - .unwrap(sizeField) - .push(DfTypes.intValue(0)) - .ifCondition(RelationType.GT); - } else { - if (!originalQualifierAlreadyChecked) { - builder - .pushExpression(originalQualifier) - .chain(b -> checkAndMarkConsumed(b, originalQualifier)) - .pop(); + PsiExpression collectionExpression = sourceCall.getMethodExpression().getQualifierExpression(); + if (collectionExpression != null) { + startStreamFromCollection(builder, firstStep, collectionExpression); + return; } + } + startStreamUnknown(builder, firstStep, originalQualifier, originalQualifierAlreadyChecked, inType); + } + + private static void startStreamFromCollection(CFGBuilder builder, Step firstStep, PsiExpression collectionExpression) { + PsiType elementType = PsiUtil.substituteTypeParameter(collectionExpression.getType(), JAVA_UTIL_COLLECTION, 0, false); + if (collectionExpression instanceof PsiMethodCallExpression call && + COLLECTION_SOURCE.matches(call)) { + PsiExpression[] args = call.getArgumentList().getExpressions(); + if (args.length == 1 && !MethodCallUtils.isVarArgCall(call)) { + startStreamFromContainer(builder, firstStep, args[0], SpecialField.ARRAY_LENGTH, elementType); + } else { + builder + .chain(firstStep::before) + .loopOver(args, builder.createTempVariable(elementType), elementType) + .chain(firstStep::iteration).end(); + } + } else { + startStreamFromContainer(builder, firstStep, collectionExpression, SpecialField.COLLECTION_SIZE, elementType); + } + } + + private static void startStreamUnknown(@NotNull CFGBuilder builder, + @NotNull Step firstStep, + @NotNull PsiExpression originalQualifier, + boolean originalQualifierAlreadyChecked, + @Nullable PsiType inType) { + if (!originalQualifierAlreadyChecked) { builder - .chain(firstStep::before) - .pushUnknown() - .ifConditionIs(true); + .pushExpression(originalQualifier) + .chain(b -> checkAndMarkConsumed(b, originalQualifier)) + .pop(); } builder + .chain(firstStep::before) + .pushUnknown() + .ifConditionIs(true) + .chain(b -> makeMainLoop(b, firstStep, inType)) + .end(); + } + + private static void startStreamFromContainer(@NotNull CFGBuilder builder, + @NotNull Step firstStep, + @NotNull PsiExpression collectionExpression, + @NotNull SpecialField sizeField, + @Nullable PsiType inType) { + builder.pushExpression(collectionExpression) + .chain(firstStep::before) + .unwrap(sizeField) + .push(DfTypes.intValue(0)) + .ifCondition(RelationType.GT) .chain(b -> makeMainLoop(b, firstStep, inType)) .end(); } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/MapUpdateInlining.java b/java/java-tests/testData/inspection/dataFlow/fixture/MapUpdateInlining.java index 66f062b18602..9c3709c8cbb6 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/MapUpdateInlining.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/MapUpdateInlining.java @@ -1,10 +1,21 @@ import foo.*; -import java.util.Map; -import java.util.HashMap; +import java.util.*; +import java.util.stream.*; import java.util.function.Function; class MapUpdateInlining { + void testArrayListCtor(Map> map) { + map.computeIfAbsent(-1, ArrayList::new); + map.computeIfAbsent(-1, x -> new ArrayList<>(x)); + Arrays.asList(-1, -2, -3).forEach(x -> map.computeIfAbsent(x, a -> new ArrayList<>(a))); + Arrays.asList(-1, -2, -3).stream().forEach(x -> map.computeIfAbsent(x, a -> new ArrayList<>(a))); + Stream.of(-1, -2, -3).forEach(x -> map.computeIfAbsent(x, a -> new ArrayList<>(a))); + for (Integer x : Arrays.asList(-1, 0, 1)) { + map.computeIfAbsent(x, a -> new ArrayList<>(a)); + } + } + void testKey(Map map) { System.out.println(map.computeIfAbsent("foo", k -> k.equals("blahblah") ? "bar" : "baz").trim()); diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/StreamInlining.java b/java/java-tests/testData/inspection/dataFlow/fixture/StreamInlining.java index f700dbd857ff..53ded23a3303 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/StreamInlining.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/StreamInlining.java @@ -318,4 +318,10 @@ public class StreamInlining { list.stream().forEach(l -> count[0]++); System.out.println(count[0]); } + + void testFromArray(String[] data) { + if (data.length != 0) return; + long result = Arrays.asList(data).stream().count(); + if (result == 0) {} + } }