From e8b76edbf2e867c7a85aad9f8408127f713ad7a7 Mon Sep 17 00:00:00 2001 From: Mikhail Pyltsin Date: Fri, 3 Oct 2025 16:15:36 +0200 Subject: [PATCH] [java-inspection] IDEA-375813 It seems dataflow join works wrong for branching with division by zero GitOrigin-RevId: c14db3aee067692092d2a96292082d81756c21a5 --- .../InspectionGadgetsBundle.properties | 1 + .../ig/numeric/DivideByZeroInspection.java | 41 +++++++++++++++---- .../numeric/divide_by_zero/DivideByZero.java | 12 ++++++ 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/java/java-analysis-impl/resources/messages/InspectionGadgetsBundle.properties b/java/java-analysis-impl/resources/messages/InspectionGadgetsBundle.properties index 4a35203ee156..1bf2c195962f 100644 --- a/java/java-analysis-impl/resources/messages/InspectionGadgetsBundle.properties +++ b/java/java-analysis-impl/resources/messages/InspectionGadgetsBundle.properties @@ -848,6 +848,7 @@ inspection.lossy.conversion.compound.assignment.display.name=Implicit cast from comparison.of.short.and.char.problem.descriptor=Equality comparison #ref of short and char values #loc big.decimal.equals.problem.descriptor=#ref() between BigDecimal values should probably be 'compareTo()' #loc divide.by.zero.problem.descriptor=Division by zero #loc +divide.by.zero.problem.may.descriptor=Maybe division by zero #loc non.reproducible.math.call.problem.descriptor=Math.#ref() may produce non-reproducible results #loc constant.math.call.problem.descriptor=Constant call to #ref() can be simplified #loc floating.point.equality.problem.descriptor=#ref: floating-point values compared for exact equality #loc diff --git a/java/java-analysis-impl/src/com/siyeh/ig/numeric/DivideByZeroInspection.java b/java/java-analysis-impl/src/com/siyeh/ig/numeric/DivideByZeroInspection.java index 9bd765f608a2..2418b0f36c46 100644 --- a/java/java-analysis-impl/src/com/siyeh/ig/numeric/DivideByZeroInspection.java +++ b/java/java-analysis-impl/src/com/siyeh/ig/numeric/DivideByZeroInspection.java @@ -18,6 +18,8 @@ package com.siyeh.ig.numeric; import com.intellij.codeInspection.CommonQuickFixBundle; import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.dataFlow.CommonDataflow; +import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet; +import com.intellij.codeInspection.dataFlow.types.DfIntType; import com.intellij.codeInspection.dataFlow.types.DfType; import com.intellij.modcommand.ModPsiUpdater; import com.intellij.modcommand.PsiUpdateModCommandQuickFix; @@ -26,6 +28,8 @@ import com.intellij.psi.*; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.ConstantExpressionUtil; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.ThreeState; +import com.intellij.util.containers.ContainerUtil; import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.BaseInspection; import com.siyeh.ig.BaseInspectionVisitor; @@ -35,6 +39,8 @@ import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.List; + public final class DivideByZeroInspection extends BaseInspection { @Pattern(VALID_ID_PATTERN) @@ -45,13 +51,18 @@ public final class DivideByZeroInspection extends BaseInspection { @Override protected @NotNull String buildErrorString(Object... infos) { - return InspectionGadgetsBundle.message("divide.by.zero.problem.descriptor"); + if (infos.length > 1 && infos[1] instanceof ThreeState threeState && threeState == ThreeState.UNSURE) { + return InspectionGadgetsBundle.message("divide.by.zero.problem.may.descriptor"); + } + else { + return InspectionGadgetsBundle.message("divide.by.zero.problem.descriptor"); + } } @Override protected @Nullable LocalQuickFix buildFix(Object... infos) { if (infos.length > 0 && infos[0] instanceof PsiBinaryExpression binOp) { - if (binOp.getOperationTokenType().equals(JavaTokenType.DIV) && isZero(binOp.getLOperand())) { + if (binOp.getOperationTokenType().equals(JavaTokenType.DIV) && isZero(binOp.getLOperand()) == ThreeState.YES) { PsiType type = binOp.getType(); if (PsiTypes.doubleType().equals(type) || PsiTypes.floatType().equals(type)) { return new ReplaceWithNaNFix(); @@ -78,8 +89,9 @@ public final class DivideByZeroInspection extends BaseInspection { final PsiExpression[] operands = expression.getOperands(); for (int i = 1; i < operands.length; i++) { final PsiExpression operand = operands[i]; - if (isZero(operand)) { - registerError(operand, expression); + ThreeState zero = isZero(operand); + if (zero != ThreeState.NO) { + registerError(operand, expression, zero); return; } } @@ -93,22 +105,35 @@ public final class DivideByZeroInspection extends BaseInspection { return; } final IElementType tokenType = expression.getOperationTokenType(); - if (!tokenType.equals(JavaTokenType.DIVEQ) && !tokenType.equals(JavaTokenType.PERCEQ) || !isZero(rhs)) { + if (!tokenType.equals(JavaTokenType.DIVEQ) && !tokenType.equals(JavaTokenType.PERCEQ) || isZero(rhs) != ThreeState.YES) { return; } registerError(expression); } } - private static boolean isZero(PsiExpression expression) { + private static ThreeState isZero(PsiExpression expression) { final Object value = ConstantExpressionUtil.computeCastTo(expression, PsiTypes.doubleType()); if (value instanceof Double) { final double constantValue = ((Double)value).doubleValue(); - return constantValue == 0.0; + return constantValue == 0.0 ? ThreeState.YES : ThreeState.NO; } DfType dfType = CommonDataflow.getDfType(expression); Number val = dfType.getConstantOfType(Number.class); - return val != null && val.doubleValue() == 0.0; + if (val != null) { + return val.doubleValue() == 0.0 ? ThreeState.YES : ThreeState.NO; + } + if (dfType instanceof DfIntType dfIntType) { + LongRangeSet range = dfIntType.getRange(); + List ranges = range.asRanges(); + if (ranges.size() < 4 && + ContainerUtil.exists(ranges, t -> + t.getConstantValue() != null && t.getConstantValue() == 0L || + t.max() - t.min() < 2 && t.contains(0L))) { + return ThreeState.UNSURE; + } + } + return ThreeState.NO; } private static class ReplaceWithNaNFix extends PsiUpdateModCommandQuickFix { diff --git a/java/java-tests/testData/ig/com/siyeh/igtest/numeric/divide_by_zero/DivideByZero.java b/java/java-tests/testData/ig/com/siyeh/igtest/numeric/divide_by_zero/DivideByZero.java index 84effcecfe4a..6bdfcf99521e 100644 --- a/java/java-tests/testData/ig/com/siyeh/igtest/numeric/divide_by_zero/DivideByZero.java +++ b/java/java-tests/testData/ig/com/siyeh/igtest/numeric/divide_by_zero/DivideByZero.java @@ -54,4 +54,16 @@ public class DivideByZero { } } } + + void testMayBe(String[] args) { + int x = 0; + if (args.length==1) x = 100; + System.out.println(10 / x); + } + + void testMayBe2(String[] args) { + int x = 0; + if (args.length==1) x = 1; + System.out.println(10 / x); + } } \ No newline at end of file