DFA instruction visitor refactoring wave#7

Merge reporting of constant variables and constant conditions
Fix DfaConstValue#createFromValue (for constants like Integer X = new Integer(1))
DfaMemoryState#getConstantValue now accepts DfaValue (and processes unboxed value as well)
Report "will cause NPE", etc. messages when dereferenced value is statically known to be null (not only null literal) IDEA-194676
This commit is contained in:
Tagir Valeev
2018-08-08 16:39:38 +07:00
parent f99951a5ab
commit 5a8e6d64cc
40 changed files with 318 additions and 234 deletions
@@ -507,14 +507,8 @@ public abstract class ContractReturnValue {
@Override
public boolean isValueCompatible(DfaMemoryState state, DfaValue value) {
if (value instanceof DfaVariableValue) {
value = state.getConstantValue((DfaVariableValue)value);
}
if (value instanceof DfaConstValue) {
Object constant = ((DfaConstValue)value).getValue();
return Boolean.valueOf(myValue).equals(constant);
}
return true;
DfaConstValue dfaConst = state.getConstantValue(value);
return dfaConst == null || Boolean.valueOf(myValue).equals(dfaConst.getValue());
}
}
@@ -19,7 +19,6 @@ import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.psi.*;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
@@ -223,11 +222,9 @@ class CustomMethodHandlers {
return fact.min();
}
}
if (value instanceof DfaVariableValue) {
value = memoryState.getConstantValue((DfaVariableValue)value);
}
if (value instanceof DfaConstValue) {
Object constant = ((DfaConstValue)value).getValue();
DfaConstValue dfaConst = memoryState.getConstantValue(value);
if (dfaConst != null) {
Object constant = dfaConst.getValue();
if (constant instanceof String && ((String)constant).length() > MAX_STRING_CONSTANT_LENGTH_TO_TRACK) return null;
return constant;
}
@@ -35,13 +35,17 @@ import com.siyeh.ig.psiutils.*;
import one.util.streamex.IntStreamEx;
import one.util.streamex.StreamEx;
import org.jdom.Element;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.text.MessageFormat;
import java.util.*;
import static com.intellij.util.ObjectUtils.tryCast;
@SuppressWarnings("ConditionalExpressionWithIdenticalBranches")
public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool {
static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.DataFlowInspection");
@@ -272,11 +276,7 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
reportOptionalOfNullableImprovements(holder, reportedAnchors, visitor.getOfNullableCalls());
visitor.getBooleanExpressions().forEach((expression, state) -> {
if (state != ThreeState.UNSURE) {
reportConstantBoolean(holder, expression, reportedAnchors, state.toBoolean());
}
});
reportConstants(holder, visitor, reportedAnchors);
reportMethodReferenceProblems(holder, visitor);
@@ -284,10 +284,6 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
reportArrayStoreProblems(holder, visitor);
if (REPORT_CONSTANT_REFERENCE_VALUES) {
reportConstantReferenceValues(holder, visitor, reportedAnchors);
}
if (REPORT_NULLABLE_METHODS_RETURNING_NOT_NULL && visitor.isAlwaysReturnsNotNull(runner.getInstructions())) {
reportAlwaysReturnsNotNull(holder, scope);
}
@@ -300,6 +296,76 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
reportDuplicateAssignments(holder, reportedAnchors, visitor);
}
private void reportConstants(ProblemsHolder holder, DataFlowInstructionVisitor visitor, HashSet<PsiElement> reportedAnchors) {
visitor.getConstantExpressions().forEach((expression, result) -> {
if (result == DataFlowInstructionVisitor.ConstantResult.UNKNOWN) return;
if (isCondition(expression)) {
if (result.value() instanceof Boolean) {
reportConstantBoolean(holder, expression, reportedAnchors, (Boolean)result.value());
}
}
else {
reportConstantReferenceValue(holder, reportedAnchors, expression, result);
}
});
}
private static boolean isCondition(@NotNull PsiExpression expression) {
PsiType type = expression.getType();
if (type == null || !PsiType.BOOLEAN.isAssignableFrom(type)) return false;
if (!(expression instanceof PsiMethodCallExpression) && !(expression instanceof PsiReferenceExpression)) return true;
PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent());
if (parent instanceof PsiStatement) return !(parent instanceof PsiReturnStatement);
if (parent instanceof PsiPolyadicExpression) {
IElementType tokenType = ((PsiPolyadicExpression)parent).getOperationTokenType();
return tokenType.equals(JavaTokenType.ANDAND) || tokenType.equals(JavaTokenType.OROR);
}
if (parent instanceof PsiConditionalExpression) {
return PsiTreeUtil.isAncestor(((PsiConditionalExpression)parent).getCondition(), expression, false);
}
return false;
}
private void reportConstantReferenceValue(ProblemsHolder holder, Set<PsiElement> reportedAnchors,
PsiExpression ref, DataFlowInstructionVisitor.ConstantResult constant) {
if (!REPORT_CONSTANT_REFERENCE_VALUES && ref instanceof PsiReferenceExpression) return;
if (shouldBeSuppressed(ref)) return;
if (constant == DataFlowInstructionVisitor.ConstantResult.UNKNOWN || !reportedAnchors.add(ref)) return;
List<LocalQuickFix> fixes = new SmartList<>();
String presentableName = constant.toString();
fixes.add(new ReplaceWithConstantValueFix(presentableName, presentableName));
Object value = constant.value();
boolean isAssertion = value instanceof Boolean && isAssertionEffectively(ref, (Boolean)value);
if (isAssertion && DONT_REPORT_TRUE_ASSERT_STATEMENTS) return;
if (value instanceof Boolean) {
ContainerUtil.addIfNotNull(fixes, createReplaceWithNullCheckFix(ref, (Boolean)value));
}
if (holder.isOnTheFly()) {
if (ref instanceof PsiReferenceExpression) {
fixes.add(new SetInspectionOptionFix(this, "REPORT_CONSTANT_REFERENCE_VALUES",
InspectionsBundle.message("inspection.data.flow.turn.off.constant.references.quickfix"),
false));
}
if (isAssertion) {
fixes.add(new SetInspectionOptionFix(this, "DONT_REPORT_TRUE_ASSERT_STATEMENTS",
InspectionsBundle.message("inspection.data.flow.turn.off.true.asserts.quickfix"), true));
}
}
String valueText;
ProblemHighlightType type;
if (ref instanceof PsiMethodCallExpression) {
type = ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
valueText = "Result of";
}
else {
type = ProblemHighlightType.WEAK_WARNING;
valueText = "Value";
}
holder.registerProblem(ref, MessageFormat.format("{0} <code>#ref</code> #loc is always ''{1}''", valueText, presentableName),
type, fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
}
private void reportDuplicateAssignments(ProblemsHolder holder,
HashSet<PsiElement> reportedAnchors,
DataFlowInstructionVisitor visitor) {
@@ -347,6 +413,7 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
private void reportNullabilityProblems(ProblemsHolder holder,
DataFlowInstructionVisitor visitor,
HashSet<PsiElement> reportedAnchors) {
Map<PsiExpression, DataFlowInstructionVisitor.ConstantResult> expressions = visitor.getConstantExpressions();
visitor.problems().forEach(problem -> {
if (NullabilityProblemKind.passingNullableArgumentToNonAnnotatedParameter.isMyProblem(problem) ||
NullabilityProblemKind.assigningNullableValueToNonAnnotatedField.isMyProblem(problem) ||
@@ -357,33 +424,36 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
if (!reportedAnchors.add(problem.getAnchor())) return;
NullabilityProblemKind.innerClassNPE.ifMyProblem(problem, newExpression -> {
List<LocalQuickFix> fixes = createNPEFixes(newExpression.getQualifier(), newExpression, holder.isOnTheFly());
holder.registerProblem(getElementToHighlight(newExpression), problem.getMessage(), fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
holder.registerProblem(getElementToHighlight(newExpression), problem.getMessage(expressions), fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
});
NullabilityProblemKind.callMethodRefNPE.ifMyProblem(problem, methodRef ->
holder.registerProblem(methodRef, InspectionsBundle.message("dataflow.message.npe.methodref.invocation"),
createMethodReferenceNPEFixes(methodRef).toArray(LocalQuickFix.EMPTY_ARRAY)));
NullabilityProblemKind.callNPE.ifMyProblem(problem, call -> reportCallMayProduceNpe(holder, call));
NullabilityProblemKind.callNPE.ifMyProblem(problem, call -> reportCallMayProduceNpe(holder, problem.getMessage(expressions), call));
NullabilityProblemKind.passingNullableToNotNullParameter.ifMyProblem(problem, expr -> reportNullableArgument(holder, expr));
NullabilityProblemKind.arrayAccessNPE.ifMyProblem(problem, expression -> {
LocalQuickFix[] fix =
createNPEFixes(expression.getArrayExpression(), expression, holder.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY);
holder.registerProblem(expression, problem.getMessage(), fix);
holder.registerProblem(expression, problem.getMessage(expressions), fix);
});
NullabilityProblemKind.fieldAccessNPE.ifMyProblem(problem, element -> {
PsiElement parent = element.getParent();
PsiExpression fieldAccess = parent instanceof PsiReferenceExpression ? (PsiExpression)parent : element;
LocalQuickFix[] fix = createNPEFixes(element, fieldAccess, holder.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY);
holder.registerProblem(element, problem.getMessage(), fix);
holder.registerProblem(element, problem.getMessage(expressions), fix);
});
NullabilityProblemKind.unboxingNullable.ifMyProblem(problem, element -> holder.registerProblem(element, problem.getMessage()));
NullabilityProblemKind.nullableFunctionReturn.ifMyProblem(problem, expr -> holder.registerProblem(expr, problem.getMessage()));
NullabilityProblemKind.assigningToNotNull.ifMyProblem(problem, expr -> reportNullabilityProblem(holder, problem, expr));
NullabilityProblemKind.storingToNotNullArray.ifMyProblem(problem, expr -> reportNullabilityProblem(holder, problem, expr));
NullabilityProblemKind.unboxingNullable.ifMyProblem(problem, element -> holder.registerProblem(element, problem.getMessage(expressions)));
NullabilityProblemKind.nullableFunctionReturn.ifMyProblem(problem, expr -> holder.registerProblem(expr, problem.getMessage(expressions)));
NullabilityProblemKind.assigningToNotNull.ifMyProblem(problem, expr -> reportNullabilityProblem(holder, problem, expr, expressions));
NullabilityProblemKind.storingToNotNullArray.ifMyProblem(problem, expr -> reportNullabilityProblem(holder, problem, expr, expressions));
});
}
private void reportNullabilityProblem(ProblemsHolder holder, NullabilityProblem<?> problem, PsiExpression expr) {
holder.registerProblem(expr, problem.getMessage(), createNPEFixes(expr, expr, holder.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY));
private void reportNullabilityProblem(ProblemsHolder holder,
NullabilityProblem<?> problem,
PsiExpression expr,
Map<PsiExpression, DataFlowInstructionVisitor.ConstantResult> expressions) {
holder.registerProblem(expr, problem.getMessage(expressions), createNPEFixes(expr, expr, holder.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY));
}
private static void reportArrayAccessProblems(ProblemsHolder holder, DataFlowInstructionVisitor visitor) {
@@ -490,35 +560,6 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
});
}
private void reportConstantReferenceValues(ProblemsHolder holder, DataFlowInstructionVisitor visitor, Set<PsiElement> reportedAnchors) {
visitor.getConstantReferenceValues().forEach((ref, dfaConst) -> {
if (ref.getParent() instanceof PsiReferenceExpression || DfaConstValue.isSentinel(dfaConst)) return;
if (!reportedAnchors.add(ref)) return;
final Object value = dfaConst.getValue();
PsiVariable constant = dfaConst.getConstant();
final String exprText = String.valueOf(value);
final String presentableName = constant != null ? constant.getName() : exprText;
List<LocalQuickFix> fixes = new SmartList<>();
fixes.add(new ReplaceWithConstantValueFix(presentableName, exprText));
boolean isAssertion = value instanceof Boolean && isAssertionEffectively(ref, (Boolean)value);
if (isAssertion && DONT_REPORT_TRUE_ASSERT_STATEMENTS) return;
if (holder.isOnTheFly()) {
fixes.add(new SetInspectionOptionFix(this, "REPORT_CONSTANT_REFERENCE_VALUES",
InspectionsBundle.message("inspection.data.flow.turn.off.constant.references.quickfix"),
false));
if (isAssertion) {
fixes.add(new SetInspectionOptionFix(this, "DONT_REPORT_TRUE_ASSERT_STATEMENTS",
InspectionsBundle.message("inspection.data.flow.turn.off.true.asserts.quickfix"), true));
}
}
holder.registerProblem(ref, "Value <code>#ref</code> #loc is always '" + presentableName + "'",
ProblemHighlightType.WEAK_WARNING, fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
});
}
private void reportNullableArgumentsPassedToNonAnnotated(DataFlowInstructionVisitor visitor, ProblemsHolder holder, Set<PsiElement> reportedAnchors) {
for (PsiElement expr : visitor.problems()
.map(NullabilityProblemKind.passingNullableArgumentToNonAnnotatedParameter::asMyProblem).nonNull()
@@ -581,20 +622,20 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
if (parent instanceof PsiAssignmentExpression) {
PsiExpression lExpression = ((PsiAssignmentExpression)parent).getLExpression();
PsiElement target = lExpression instanceof PsiReferenceExpression ? ((PsiReferenceExpression)lExpression).resolve() : null;
return ObjectUtils.tryCast(target, PsiField.class);
return tryCast(target, PsiField.class);
}
return null;
}
private void reportCallMayProduceNpe(ProblemsHolder holder, PsiMethodCallExpression callExpression) {
private void reportCallMayProduceNpe(ProblemsHolder holder,
String message,
PsiMethodCallExpression callExpression) {
PsiReferenceExpression methodExpression = callExpression.getMethodExpression();
List<LocalQuickFix> fixes = createNPEFixes(methodExpression.getQualifierExpression(), callExpression, holder.isOnTheFly());
ContainerUtil.addIfNotNull(fixes, ReplaceWithObjectsEqualsFix.createFix(callExpression, methodExpression));
PsiElement toHighlight = getElementToHighlight(callExpression);
holder.registerProblem(toHighlight,
InspectionsBundle.message("dataflow.message.npe.method.invocation"),
fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
holder.registerProblem(toHighlight, message, fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
}
private static void reportCastMayFail(ProblemsHolder holder, TypeCastInstruction instruction) {
@@ -699,17 +740,33 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
holder.registerProblem(psiAnchor, message, fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
}
@Contract("null -> false")
private static boolean shouldBeSuppressed(PsiElement anchor) {
if (!(anchor instanceof PsiExpression)) return false;
// Don't report System.out.println(b = false)
if (anchor instanceof PsiAssignmentExpression) return true;
PsiExpression expression = (PsiExpression)anchor;
// Dereference of null will be covered by other warning
if (ExpressionUtils.isVoidContext(expression) || isDereferenceContext(expression)) return true;
if (isFlagCheck(anchor)) return true;
if (expression instanceof PsiReferenceExpression) {
PsiField field = tryCast(((PsiReferenceExpression)expression).resolve(), PsiField.class);
return field != null && field.hasModifierProperty(PsiModifier.STATIC) && ExpressionUtils.isNullLiteral(field.getInitializer());
}
while (expression != null && BoolUtils.isNegation(expression)) {
expression = BoolUtils.getNegated(expression);
}
PsiMethodCallExpression call = ObjectUtils.tryCast(expression, PsiMethodCallExpression.class);
PsiMethodCallExpression call = tryCast(expression, PsiMethodCallExpression.class);
// Reported by "Equals with itself" inspection; avoid double reporting
return call != null && EqualsWithItselfInspection.isEqualsWithItself(call);
}
private static boolean isDereferenceContext(PsiExpression ref) {
PsiElement parent = PsiUtil.skipParenthesizedExprUp(ref.getParent());
return parent instanceof PsiReferenceExpression || parent instanceof PsiArrayAccessExpression
|| parent instanceof PsiSwitchStatement || parent instanceof PsiSynchronizedStatement;
}
private static LocalQuickFix createReplaceWithNullCheckFix(PsiElement psiAnchor, boolean evaluatesToTrue) {
if (evaluatesToTrue) return null;
if (!(psiAnchor instanceof PsiMethodCallExpression)) return null;
@@ -843,7 +900,7 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
int index = ArrayUtil.indexOf(((PsiExpressionList)parent).getExpressions(), anchor);
if (index >= 0) {
ValueConstraint wantedConstraint = evaluatesToTrue ? ValueConstraint.FALSE_VALUE : ValueConstraint.TRUE_VALUE;
PsiMethodCallExpression call = ObjectUtils.tryCast(parent.getParent(), PsiMethodCallExpression.class);
PsiMethodCallExpression call = tryCast(parent.getParent(), PsiMethodCallExpression.class);
if (call != null) {
PsiMethod method = call.resolveMethod();
if (method != null) {
@@ -875,7 +932,7 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
return false;
}
static boolean isFlagCheck(PsiElement element) {
private static boolean isFlagCheck(PsiElement element) {
PsiElement scope = PsiTreeUtil.getParentOfType(element, PsiStatement.class, PsiVariable.class);
PsiExpression topExpression = scope instanceof PsiIfStatement ? ((PsiIfStatement)scope).getCondition() :
scope instanceof PsiVariable ? ((PsiVariable)scope).getInitializer() :
@@ -9,10 +9,8 @@ import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.ExpressionUtils;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -27,12 +25,11 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
private final Map<NullabilityProblemKind.NullabilityProblem<?>, StateInfo> myStateInfos = new LinkedHashMap<>();
private final Set<Instruction> myCCEInstructions = ContainerUtil.newHashSet();
private final Map<PsiCallExpression, Boolean> myFailingCalls = new HashMap<>();
private final Map<PsiExpression, ThreeState> myBooleanExpressions = new HashMap<>();
private final Map<PsiExpression, ConstantResult> myConstantExpressions = new HashMap<>();
private final Map<PsiElement, ThreeState> myOfNullableCalls = new HashMap<>();
private final Map<PsiAssignmentExpression, Pair<PsiType, PsiType>> myArrayStoreProblems = new HashMap<>();
private final Map<PsiMethodReferenceExpression, DfaValue> myMethodReferenceResults = new HashMap<>();
private final Map<PsiArrayAccessExpression, ThreeState> myOutOfBoundsArrayAccesses = new HashMap<>();
private final Map<PsiReferenceExpression, DfaConstValue> myValues = new HashMap<>();
private final Set<PsiElement> myReceiverMutabilityViolation = new HashSet<>();
private final Set<PsiElement> myArgumentMutabilityViolation = new HashSet<>();
private final Map<PsiExpression, Boolean> mySameValueAssigned = new HashMap<>();
@@ -128,8 +125,8 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
return myOfNullableCalls;
}
Map<PsiExpression, ThreeState> getBooleanExpressions() {
return myBooleanExpressions;
Map<PsiExpression, ConstantResult> getConstantExpressions() {
return myConstantExpressions;
}
Map<PsiMethodReferenceExpression, DfaValue> getMethodReferenceResults() {
@@ -167,7 +164,9 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
@Nullable TextRange range,
@NotNull DfaMemoryState memState) {
expression.accept(new ExpressionVisitor(value, memState));
handleBooleanResults(value, memState, expression);
if (range == null) {
handleBooleanResults(value, memState, expression);
}
}
@Override
@@ -213,10 +212,6 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
return super.visitEndOfInitializer(instruction, runner, state);
}
public Map<PsiReferenceExpression, DfaConstValue> getConstantReferenceValues() {
return myValues;
}
private static boolean hasNonTrivialFailingContracts(PsiCallExpression call) {
List<? extends MethodContract> contracts = JavaMethodContractUtil.getMethodCallContracts(call);
return !contracts.isEmpty() &&
@@ -224,47 +219,18 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
}
private void handleBooleanResults(DfaValue value, DfaMemoryState memState, PsiExpression expression) {
ThreeState curState = myBooleanExpressions.get(expression);
if (curState == ThreeState.UNSURE) return;
ThreeState nextState = ThreeState.UNSURE;
value = value instanceof DfaVariableValue ? memState.getConstantValue((DfaVariableValue)value) : value;
if (value instanceof DfaConstValue) {
Object val = ((DfaConstValue)value).getValue();
if (val instanceof Boolean) {
nextState = ThreeState.fromBoolean((Boolean)val);
if (curState != null && curState != nextState) {
nextState = ThreeState.UNSURE;
}
if (expression instanceof PsiLiteralExpression) return;
ConstantResult curState = myConstantExpressions.get(expression);
if (curState == ConstantResult.UNKNOWN) return;
ConstantResult nextState = ConstantResult.UNKNOWN;
DfaConstValue dfaConst = memState.getConstantValue(value);
if (dfaConst != null) {
nextState = ConstantResult.fromConstValue(dfaConst);
if (curState != null && curState != nextState) {
nextState = ConstantResult.UNKNOWN;
}
}
if (curState != null || shouldCollectBooleanResult(expression)) {
myBooleanExpressions.put(expression, nextState);
}
}
private static boolean shouldCollectBooleanResult(PsiExpression expression) {
if (expression instanceof PsiLiteralExpression) return false;
PsiType type = expression.getType();
if (type == null || !PsiType.BOOLEAN.isAssignableFrom(type)) return false;
if (expression instanceof PsiPrefixExpression || expression instanceof PsiPolyadicExpression) {
return !DataFlowInspectionBase.isFlagCheck(expression);
}
PsiPolyadicExpression polyadic = tryCast(PsiUtil.skipParenthesizedExprUp(expression.getParent()), PsiPolyadicExpression.class);
if (polyadic != null) {
if ((polyadic.getOperationTokenType().equals(JavaTokenType.ANDAND) || polyadic.getOperationTokenType().equals(JavaTokenType.OROR)) &&
!DataFlowInspectionBase.isFlagCheck(expression)) return true;
}
if (expression instanceof PsiMethodCallExpression) {
PsiMethodCallExpression call = (PsiMethodCallExpression)expression;
if (ExpressionUtils.isVoidContext(call)) return false;
PsiMethod method = call.resolveMethod();
if (method == null || !JavaMethodContractUtil.isPure(method)) return false;
List<? extends MethodContract> contracts = JavaMethodContractUtil.getMethodCallContracts(method, call);
return CustomMethodHandlers.find(method) != null ||
!contracts.isEmpty() &&
contracts.stream().anyMatch(contract -> contract.getReturnValue().isBoolean() && !contract.isTrivial());
}
return false;
myConstantExpressions.put(expression, nextState);
}
@Override
@@ -302,10 +268,6 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
}
}
private static boolean shouldReportConstValue(Object value) {
return value == null || value instanceof Boolean;
}
private static class StateInfo {
boolean ephemeralNpe;
boolean normalNpe;
@@ -337,23 +299,37 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
myFailingCalls.put(call, DfaConstValue.isContractFail(myValue) && !Boolean.FALSE.equals(isFailing));
}
}
}
enum ConstantResult {
TRUE, FALSE, NULL, UNKNOWN;
@NotNull
@Override
public void visitReferenceExpression(PsiReferenceExpression expression) {
super.visitReferenceExpression(expression);
DfaConstValue oldValue = myValues.get(expression);
if (DfaConstValue.isSentinel(oldValue)) return;
if (myValue instanceof DfaVariableValue) {
DfaConstValue constValue = myMemState.getConstantValue((DfaVariableValue)myValue);
boolean report = constValue != null && shouldReportConstValue(constValue.getValue());
if (!report) {
constValue = null;
}
DfaConstValue newValue = constValue != null && (oldValue == null || oldValue == constValue)
? constValue
: myValue.getFactory().getConstFactory().getSentinel();
myValues.put(expression, newValue);
public String toString() {
return name().toLowerCase(Locale.ENGLISH);
}
public Object value() {
switch (this) {
case TRUE:
return Boolean.TRUE;
case FALSE:
return Boolean.FALSE;
case NULL:
return null;
default:
throw new UnsupportedOperationException();
}
}
@NotNull
static ConstantResult fromConstValue(@NotNull DfaConstValue constant) {
Object value = constant.getValue();
if (value == null) return NULL;
if (Boolean.TRUE.equals(value)) return TRUE;
if (Boolean.FALSE.equals(value)) return FALSE;
return UNKNOWN;
}
}
}
@@ -19,6 +19,7 @@ import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
import com.intellij.codeInspection.dataFlow.value.DfaPsiType;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -123,8 +124,15 @@ public interface DfaMemoryState {
boolean isNotNull(DfaValue dfaVar);
/**
* Returns a constant value which equals to given value, if such.
*
* @param value a value to find a corresponding constant
* @return found constant or null
*/
@Nullable
DfaConstValue getConstantValue(@NotNull DfaVariableValue value);
@Contract("null -> null")
DfaConstValue getConstantValue(@Nullable DfaValue value);
/**
* Ephemeral means a state that was created when considering a method contract and checking if one of its arguments is null.
@@ -36,6 +36,7 @@ import com.intellij.util.containers.Stack;
import gnu.trove.TIntObjectHashMap;
import gnu.trove.TIntObjectProcedure;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -581,7 +582,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
if (dfaVar instanceof DfaVariableValue) {
if (getVariableState((DfaVariableValue)dfaVar).isNotNull()) return true;
DfaConstValue constantValue = getConstantValue((DfaVariableValue)dfaVar);
DfaConstValue constantValue = getConstantValue(dfaVar);
if (constantValue != null && constantValue.getValue() != null) return true;
}
@@ -597,10 +598,17 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
@Override
@Nullable
public DfaConstValue getConstantValue(@NotNull DfaVariableValue value) {
int index = getEqClassIndex(value);
EqClass ec = index == -1 ? null : myEqClasses.get(index);
return ec == null ? null : (DfaConstValue)unwrap(ec.findConstant(true));
@Contract("null -> null")
public DfaConstValue getConstantValue(@Nullable DfaValue value) {
if (value instanceof DfaConstValue) {
return (DfaConstValue)value;
}
if (value instanceof DfaUnboxedValue || value instanceof DfaVariableValue) {
int index = getEqClassIndex(value);
EqClass ec = index == -1 ? null : myEqClasses.get(index);
return ec == null ? null : (DfaConstValue)unwrap(ec.findConstant(true));
}
return null;
}
@Override
@@ -196,9 +196,7 @@ public abstract class InstructionVisitor {
public DfaInstructionState[] visitObjectOfInstruction(ObjectOfInstruction instruction, DataFlowRunner runner, DfaMemoryState state) {
DfaValue value = state.pop();
DfaConstValue constant = value instanceof DfaConstValue ? (DfaConstValue)value :
value instanceof DfaVariableValue ? state.getConstantValue((DfaVariableValue)value) :
null;
DfaConstValue constant = state.getConstantValue(value);
PsiType type = constant == null ? null : ObjectUtils.tryCast(constant.getValue(), PsiType.class);
state.push(runner.getFactory().createTypeValue(type, Nullability.NOT_NULL));
return nextInstruction(instruction, runner, state);
@@ -2,14 +2,18 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.InspectionsBundle;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.psiutils.ExpressionUtils;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.PropertyKey;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import static com.intellij.codeInspection.InspectionsBundle.BUNDLE;
@@ -18,14 +22,17 @@ import static com.intellij.codeInspection.InspectionsBundle.BUNDLE;
* @param <T> a type of anchor element which could be associated with given nullability problem kind
*/
public class NullabilityProblemKind<T extends PsiElement> {
private static final Function<Object, PsiExpression> DEFAULT_DEREFERENCED_ELEMENT_FN = e -> ObjectUtils.tryCast(e, PsiExpression.class);
private final String myName;
private final String myNullLiteralMessage;
private final String myAlwaysNullMessage;
private final String myNormalMessage;
private final Function<? super T, ? extends PsiExpression> myDereferencedElementFunction;
private NullabilityProblemKind(@NotNull String name) {
myName = name;
myNullLiteralMessage = null;
myAlwaysNullMessage = null;
myNormalMessage = null;
myDereferencedElementFunction = DEFAULT_DEREFERENCED_ELEMENT_FN;
}
private NullabilityProblemKind(@NotNull String name, @NotNull @PropertyKey(resourceBundle = BUNDLE) String message) {
@@ -33,22 +40,34 @@ public class NullabilityProblemKind<T extends PsiElement> {
}
private NullabilityProblemKind(@NotNull String name,
@NotNull @PropertyKey(resourceBundle = BUNDLE) String nullLiteralMessage,
@NotNull @PropertyKey(resourceBundle = BUNDLE) String alwaysNullMessage,
@NotNull @PropertyKey(resourceBundle = BUNDLE) String normalMessage) {
myName = name;
myNullLiteralMessage = InspectionsBundle.message(nullLiteralMessage);
myNormalMessage = InspectionsBundle.message(normalMessage);
this(name, alwaysNullMessage, normalMessage, DEFAULT_DEREFERENCED_ELEMENT_FN);
}
public static final NullabilityProblemKind<PsiMethodCallExpression> callNPE = new NullabilityProblemKind<>("callNPE");
private NullabilityProblemKind(@NotNull String name,
@NotNull @PropertyKey(resourceBundle = BUNDLE) String alwaysNullMessage,
@NotNull @PropertyKey(resourceBundle = BUNDLE) String normalMessage,
Function<? super T, ? extends PsiExpression> dereferencedElementFunction) {
myName = name;
myAlwaysNullMessage = InspectionsBundle.message(alwaysNullMessage);
myNormalMessage = InspectionsBundle.message(normalMessage);
myDereferencedElementFunction = dereferencedElementFunction;
}
public static final NullabilityProblemKind<PsiMethodCallExpression> callNPE =
new NullabilityProblemKind<>("callNPE", "dataflow.message.npe.method.invocation.sure", "dataflow.message.npe.method.invocation",
call -> call.getMethodExpression().getQualifierExpression());
public static final NullabilityProblemKind<PsiMethodReferenceExpression> callMethodRefNPE =
new NullabilityProblemKind<>("callMethodRefNPE", "dataflow.message.npe.methodref.invocation");
public static final NullabilityProblemKind<PsiNewExpression> innerClassNPE =
new NullabilityProblemKind<>("innerClassNPE", "dataflow.message.npe.inner.class.construction");
new NullabilityProblemKind<>("innerClassNPE", "dataflow.message.npe.inner.class.construction.sure",
"dataflow.message.npe.inner.class.construction", PsiNewExpression::getQualifier);
public static final NullabilityProblemKind<PsiExpression> fieldAccessNPE =
new NullabilityProblemKind<>("fieldAccessNPE", "dataflow.message.npe.field.access.sure", "dataflow.message.npe.field.access");
public static final NullabilityProblemKind<PsiArrayAccessExpression> arrayAccessNPE =
new NullabilityProblemKind<>("arrayAccessNPE", "dataflow.message.npe.array.access");
new NullabilityProblemKind<>("arrayAccessNPE", "dataflow.message.npe.array.access.sure", "dataflow.message.npe.array.access",
PsiArrayAccessExpression::getArrayExpression);
public static final NullabilityProblemKind<PsiElement> unboxingNullable =
new NullabilityProblemKind<>("unboxingNullable", "dataflow.message.unboxing");
public static final NullabilityProblemKind<PsiExpression> assigningToNotNull =
@@ -138,13 +157,18 @@ public class NullabilityProblemKind<T extends PsiElement> {
}
@NotNull
public String getMessage() {
if (myKind.myNullLiteralMessage == null || myKind.myNormalMessage == null) {
public String getMessage(Map<PsiExpression, DataFlowInstructionVisitor.ConstantResult> expressions) {
if (myKind.myAlwaysNullMessage == null || myKind.myNormalMessage == null) {
throw new IllegalStateException("This problem kind has no message associated: " + myKind);
}
return myAnchor instanceof PsiExpression && ExpressionUtils.isNullLiteral((PsiExpression)myAnchor)
? myKind.myNullLiteralMessage
: myKind.myNormalMessage;
PsiExpression expression =
PsiUtil.skipParenthesizedExprDown(ObjectUtils.tryCast(myKind.myDereferencedElementFunction.apply(myAnchor), PsiExpression.class));
if (expression != null) {
if (ExpressionUtils.isNullLiteral(expression) || expressions.get(expression) == DataFlowInstructionVisitor.ConstantResult.NULL) {
return myKind.myAlwaysNullMessage;
}
}
return myKind.myNormalMessage;
}
@Override
@@ -702,9 +702,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
boolean unknownTargetType = false;
DfaValue condition = null;
if (instruction.isClassObjectCheck()) {
DfaConstValue constant = dfaRight instanceof DfaConstValue ? (DfaConstValue)dfaRight :
dfaRight instanceof DfaVariableValue ? memState.getConstantValue((DfaVariableValue)dfaRight) :
null;
DfaConstValue constant = memState.getConstantValue(dfaRight);
PsiType type = constant == null ? null : ObjectUtils.tryCast(constant.getValue(), PsiType.class);
if (type == null || type instanceof PsiPrimitiveType) {
// Unknown/primitive class: just execute contract "null -> false"
@@ -113,7 +113,10 @@ public class DfaConstValue extends DfaValue {
if (TypeConversionUtil.isNumericType(type) && !TypeConversionUtil.isFloatOrDoubleType(type)) {
type = PsiType.LONG;
value = TypeConversionUtil.computeCastTo(value, type);
Object numeric = TypeConversionUtil.computeCastTo(value, type);
if (numeric != null) {
value = numeric;
}
}
if (value instanceof Double || value instanceof Float) {
double doubleValue = ((Number)value).doubleValue();
@@ -202,14 +205,4 @@ public class DfaConstValue extends DfaValue {
public static boolean isContractFail(DfaValue value) {
return value instanceof DfaConstValue && ((DfaConstValue)value).getValue() == ourThrowable;
}
/**
* Checks whether given value is a special internal sentinel value returned by {@link Factory#getSentinel()}.
*
* @param value value to check
* @return true if specified value is a sentinel value
*/
public static boolean isSentinel(DfaValue value) {
return value instanceof DfaConstValue && ((DfaConstValue)value).getValue() == SENTINEL;
}
}
@@ -3,6 +3,6 @@
<problem>
<file>Test.java</file>
<line>5</line>
<description>Array access &lt;code&gt;path[0]&lt;/code&gt; may produce &lt;code&gt;java.lang.NullPointerException&lt;/code&gt;</description>
<description>Array access &lt;code&gt;path[0]&lt;/code&gt; will produce &lt;code&gt;java.lang.NullPointerException&lt;/code&gt;</description>
</problem>
</problems>
@@ -3,7 +3,7 @@
<problem>
<file>NullTest.java</file>
<line>6</line>
<description>Dereference of 't' may produce java.lang.NullPointerException</description>
<description>Dereference of 't' will produce java.lang.NullPointerException</description>
</problem>
</problems>
@@ -108,7 +108,7 @@ class AdvancedArrayAccess {
void testMethodQualifier() {
if(getData()[0] == null) {
System.out.println(getData()[0].<warning descr="Method invocation 'trim' may produce 'java.lang.NullPointerException'">trim</warning>());
System.out.println(getData()[0].<warning descr="Method invocation 'trim' will produce 'java.lang.NullPointerException'">trim</warning>());
}
}
@@ -29,7 +29,7 @@ class S {
public void te3(boolean b){
Boolean c = Boolean.FALSE;
boolean o = !c;
boolean o = <warning descr="Condition '!c' is always 'true'">!c</warning>;
if (<warning descr="Condition 'o' is always 'true'">o</warning>) {
}
}
@@ -41,13 +41,13 @@ class S {
}
public void te5(boolean b){
Boolean c = Boolean.TRUE;
boolean o = b||<warning descr="Condition 'c' is always 'true' when reached">c</warning>;
boolean o = <warning descr="Condition 'b||c' is always 'true'">b||<warning descr="Condition 'c' is always 'true' when reached">c</warning></warning>;
if (<warning descr="Condition 'o' is always 'true'">o</warning>) {
}
}
public void te6(boolean b){
Boolean c = Boolean.TRUE;
boolean o = !c;
boolean o = <warning descr="Condition '!c' is always 'false'">!c</warning>;
<warning descr="Condition 'o' at the left side of assignment expression is always 'false'. Can be simplified">o</warning> |= c&b;
if (o) {
}
@@ -57,7 +57,7 @@ class S {
boolean o;
{
Boolean c = Boolean.FALSE;
o = !c;
o = <warning descr="Condition '!c' is always 'true'">!c</warning>;
}
if (<warning descr="Condition 'o' is always 'true'">o</warning>) {
}
@@ -40,7 +40,7 @@ class Bar3 {
System.out.println(first.hashCode());
}
if (first == null) {
System.out.println(first.<warning descr="Method invocation 'hashCode' may produce 'java.lang.NullPointerException'">hashCode</warning>());
System.out.println(first.<warning descr="Method invocation 'hashCode' will produce 'java.lang.NullPointerException'">hashCode</warning>());
}
}
}
@@ -13,7 +13,7 @@ class InitializerTest {
z = "foo";
}
boolean b = <warning descr="Condition 'z.startsWith(\"bar\")' is always 'false'">z.startsWith("bar")</warning>;
boolean b = <warning descr="Result of 'z.startsWith(\"bar\")' is always 'false'">z.startsWith("bar")</warning>;
static final String ABC;
static {
@@ -7,7 +7,7 @@ import java.lang.IllegalArgumentException;
class AssertIsNotNull {
void bar(String s, String s1) {
if (<warning descr="Condition 's == null && trimIfNotNull(s) != null' is always 'false'">s == null && <warning descr="Condition 'trimIfNotNull(s) != null' is always 'false' when reached">trimIfNotNull(s) != null</warning></warning>) {
if (<warning descr="Condition 's == null && trimIfNotNull(s) != null' is always 'false'">s == null && <warning descr="Condition 'trimIfNotNull(s) != null' is always 'false' when reached"><warning descr="Result of 'trimIfNotNull(s)' is always 'null'">trimIfNotNull(s)</warning> != null</warning></warning>) {
throw new AssertionError();
}
@@ -8,7 +8,7 @@ class TestCase {
String s = normalizeSpace(unknown()).trim();
String s3 = normalizeSpaceInverted(unknown()).trim();
String s4 = normalizeSpace(null).<warning descr="Method invocation 'trim' may produce 'java.lang.NullPointerException'">trim</warning>();
String s4 = normalizeSpace(null).<warning descr="Method invocation 'trim' will produce 'java.lang.NullPointerException'">trim</warning>();
}
public static native String unknown();
@@ -1,6 +1,6 @@
class Foo {
String field;
String field2 = field.<warning descr="Method invocation 'substring' may produce 'java.lang.NullPointerException'">substring</warning>(1);
String field2 = field.<warning descr="Method invocation 'substring' will produce 'java.lang.NullPointerException'">substring</warning>(1);
int field3 = field2.length();
Runnable r = new Runnable() {
public void run() {
@@ -74,7 +74,7 @@ class FieldInitNoLoop {
class NonFinalNotInitialized {
String x;
String y = x.<warning descr="Method invocation 'trim' may produce 'java.lang.NullPointerException'">trim</warning>();
String y = x.<warning descr="Method invocation 'trim' will produce 'java.lang.NullPointerException'">trim</warning>();
}
class NonFinalInitialized {
@@ -87,7 +87,7 @@ class NonFinalInitialized {
class NonFinalAssignedInside {
String x;
String y = x.<warning descr="Method invocation 'trim' may produce 'java.lang.NullPointerException'">trim</warning>()+(x = " foo ").trim();
String y = x.<warning descr="Method invocation 'trim' will produce 'java.lang.NullPointerException'">trim</warning>()+(x = " foo ").trim();
String z = x.trim();
}
@@ -1,7 +1,7 @@
class Foo {
String field;
String field2;
int hash = field.<warning descr="Method invocation 'hashCode' may produce 'java.lang.NullPointerException'">hashCode</warning>();
int hash = field.<warning descr="Method invocation 'hashCode' will produce 'java.lang.NullPointerException'">hashCode</warning>();
Foo(String f2) {
field2 = f2;
@@ -23,7 +23,7 @@ class Foo {
Instrumented() {
System.out.println(s1.length()
+s2.<warning descr="Method invocation 'length' may produce 'java.lang.NullPointerException'">length</warning>());
+s2.<warning descr="Method invocation 'length' will produce 'java.lang.NullPointerException'">length</warning>());
}
}
@@ -32,8 +32,8 @@ class Foo {
String s2 = null;
NotInstrumented() {
System.out.println(s1.<warning descr="Method invocation 'length' may produce 'java.lang.NullPointerException'">length</warning>()
+s2.<warning descr="Method invocation 'length' may produce 'java.lang.NullPointerException'">length</warning>());
System.out.println(s1.<warning descr="Method invocation 'length' will produce 'java.lang.NullPointerException'">length</warning>()
+s2.<warning descr="Method invocation 'length' will produce 'java.lang.NullPointerException'">length</warning>());
}
}
}
@@ -37,7 +37,7 @@ class LessThanRelations {
void list(List<String> list, int index) {
if(index >= list.size()) {
System.out.println("Big index");
} else if(index > 0 && <warning descr="Condition '!list.isEmpty()' is always 'true' when reached">!<warning descr="Condition 'list.isEmpty()' is always 'false' when reached">list.isEmpty()</warning></warning>) {
} else if(index > 0 && <warning descr="Condition '!list.isEmpty()' is always 'true' when reached">!<warning descr="Result of 'list.isEmpty()' is always 'false'">list.isEmpty()</warning></warning>) {
System.out.println("ok");
}
}
@@ -198,8 +198,8 @@ public class LongRangeBasics {
System.out.println(s2.trim());
}
if(code == 0) {
System.out.println(s1.<warning descr="Method invocation 'trim' may produce 'java.lang.NullPointerException'">trim</warning>());
System.out.println(s2.<warning descr="Method invocation 'trim' may produce 'java.lang.NullPointerException'">trim</warning>());
System.out.println(s1.<warning descr="Method invocation 'trim' will produce 'java.lang.NullPointerException'">trim</warning>());
System.out.println(s2.<warning descr="Method invocation 'trim' will produce 'java.lang.NullPointerException'">trim</warning>());
}
}
@@ -162,8 +162,8 @@ public class LongRangeKnownMethods {
void testStringComparison(String name) {
// Parentheses misplaced -- found in AndroidStudio
if (!(name.equals("layout_width") && <warning descr="Condition '!(name.equals(\"layout_height\"))' is always 'true'">!(<warning descr="Condition 'name.equals(\"layout_height\")' is always 'false'">name.equals("layout_height")</warning>)</warning> &&
<warning descr="Condition '!(name.equals(\"id\"))' is always 'true'">!(<warning descr="Condition 'name.equals(\"id\")' is always 'false'">name.equals("id")</warning>)</warning>)) {
if (!(name.equals("layout_width") && <warning descr="Condition '!(name.equals(\"layout_height\"))' is always 'true'">!(<warning descr="Result of 'name.equals(\"layout_height\")' is always 'false'">name.equals("layout_height")</warning>)</warning> &&
<warning descr="Condition '!(name.equals(\"id\"))' is always 'true'">!(<warning descr="Result of 'name.equals(\"id\")' is always 'false'">name.equals("id")</warning>)</warning>)) {
System.out.println("ok");
}
}
@@ -6,7 +6,7 @@ class MergedInitializerAndConstructor {
private Collection<Object> collection2 = null;
public Test1() {
collection2.<warning descr="Method invocation 'add' may produce 'java.lang.NullPointerException'">add</warning>("");
collection2.<warning descr="Method invocation 'add' will produce 'java.lang.NullPointerException'">add</warning>("");
}
}
@@ -18,7 +18,7 @@ class MergedInitializerAndConstructor {
}
{
collection2.<warning descr="Method invocation 'add' may produce 'java.lang.NullPointerException'">add</warning>(""); //<- warning here
collection2.<warning descr="Method invocation 'add' will produce 'java.lang.NullPointerException'">add</warning>(""); //<- warning here
}
}
@@ -27,7 +27,7 @@ class MergedInitializerAndConstructor {
public Test3(String s) {
super();
collection2.<warning descr="Method invocation 'add' may produce 'java.lang.NullPointerException'">add</warning>(s);
collection2.<warning descr="Method invocation 'add' will produce 'java.lang.NullPointerException'">add</warning>(s);
}
}
@@ -71,7 +71,7 @@ public class MutabilityJdk {
List<String> list3 = Collections.unmodifiableList(list2);
if(<warning descr="Condition 'list1.isEmpty()' is always 'true'">list1.isEmpty()</warning>) System.out.println("ok");
if(!list3.isEmpty()) return;
if(<warning descr="Condition '!list3.isEmpty()' is always 'false'">!<warning descr="Condition 'list3.isEmpty()' is always 'true'">list3.isEmpty()</warning></warning>) return;
if(<warning descr="Condition '!list3.isEmpty()' is always 'false'">!<warning descr="Result of 'list3.isEmpty()' is always 'true'">list3.isEmpty()</warning></warning>) return;
list2.add("foo");
// list1 size is not flushed (UNMODIFIABLE)
if(<warning descr="Condition 'list1.isEmpty()' is always 'true'">list1.isEmpty()</warning>) System.out.println("ok");
@@ -20,7 +20,7 @@ class Foo {
if (data != null) {
return;
}
System.out.println(data.<warning descr="Method invocation 'hashCode' may produce 'java.lang.NullPointerException'">hashCode</warning>());
System.out.println(data.<warning descr="Method invocation 'hashCode' will produce 'java.lang.NullPointerException'">hashCode</warning>());
System.out.println(data.hashCode());
}
@@ -20,7 +20,7 @@ class Foo {
if (data != null) {
return;
}
System.out.println(data.<warning descr="Method invocation 'hashCode' may produce 'java.lang.NullPointerException'">hashCode</warning>());
System.out.println(data.<warning descr="Method invocation 'hashCode' will produce 'java.lang.NullPointerException'">hashCode</warning>());
System.out.println(data.hashCode());
}
@@ -56,7 +56,7 @@ public class OptionalInlining {
void testDeref(Optional<String> opt) {
if (opt == null) {
System.out.println(opt.<warning descr="Method invocation 'orElse' may produce 'java.lang.NullPointerException'">orElse</warning>("qq"));
System.out.println(opt.<warning descr="Method invocation 'orElse' will produce 'java.lang.NullPointerException'">orElse</warning>("qq"));
}
}
@@ -68,7 +68,7 @@ public class OptionalInlining {
}
return "baz";
});
if (<warning descr="Condition 's.equals(\"bar\") && !opt.isPresent()' is always 'false'">s.equals("bar") && <warning descr="Condition '!opt.isPresent()' is always 'false' when reached">!<warning descr="Condition 'opt.isPresent()' is always 'true' when reached">opt.isPresent()</warning></warning></warning>) {
if (<warning descr="Condition 's.equals(\"bar\") && !opt.isPresent()' is always 'false'">s.equals("bar") && <warning descr="Condition '!opt.isPresent()' is always 'false' when reached">!<warning descr="Result of 'opt.isPresent()' is always 'true'">opt.isPresent()</warning></warning></warning>) {
System.out.println("Impossible");
}
}
@@ -87,7 +87,7 @@ public class OptionalInlining {
if (abc.equals("xyz") && <warning descr="Condition 'opt.isPresent()' is always 'true' when reached">opt.isPresent()</warning>) {
System.out.println("always");
}
opt.filter(x -> x.length() > 5).filter(x -> <warning descr="Condition 'x.isEmpty()' is always 'false'">x.isEmpty()</warning>).ifPresent(x -> System.out.println(x));
opt.filter(x -> x.length() > 5).filter(x -> <warning descr="Result of 'x.isEmpty()' is always 'false'">x.isEmpty()</warning>).ifPresent(x -> System.out.println(x));
}
@Nullable
@@ -111,7 +111,7 @@ public class OptionalInlining {
void testMap(Optional<String> opt) {
opt.map(<warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>);
String res = opt.<String>map(s -> null).orElse("abc");
if (<warning descr="Condition '!res.equals(\"abc\")' is always 'false'">!<warning descr="Condition 'res.equals(\"abc\")' is always 'true'">res.equals("abc")</warning></warning>) {
if (<warning descr="Condition '!res.equals(\"abc\")' is always 'false'">!<warning descr="Result of 'res.equals(\"abc\")' is always 'true'">res.equals("abc")</warning></warning>) {
System.out.println("Never");
}
String trimmed = Optional.ofNullable(nullableMethod()).map(xx -> xx.trim()).orElse("");
@@ -170,7 +170,7 @@ public class OptionalInlining {
if (<warning descr="Condition 's.equals(\"qux\")' is always 'false'">s.equals("qux")</warning>) {
System.out.println("Never");
}
boolean res = <warning descr="Condition 'opt.filter(x -> x.isEmpty()).flatMap(x -> x.length() <= 2 ? Optional.empty() : Optional.of(\"foo\")) ...' is always 'false'">opt.filter(x -> x.isEmpty()).flatMap(x -> <warning descr="Condition 'x.length() <= 2' is always 'true'">x.length() <= 2</warning> ? Optional.empty() : Optional.of("foo"))
boolean res = <warning descr="Result of 'opt.filter(x -> x.isEmpty()).flatMap(x -> x.length() <= 2 ? Optional.empty() : Optional.of(\"foo\")) ...' is always 'false'">opt.filter(x -> x.isEmpty()).flatMap(x -> <warning descr="Condition 'x.length() <= 2' is always 'true'">x.length() <= 2</warning> ? Optional.empty() : Optional.of("foo"))
.isPresent()</warning>;
}
@@ -214,14 +214,14 @@ public class OptionalInlining {
}
void testFilterChain(Optional<Holder> opt) {
boolean present = <warning descr="Condition 'opt .filter(h -> h.x < 5) .filter(h -> h.x > 6) .map(h -> h.x).isPresent()' is always 'false'">opt
boolean present = <warning descr="Result of 'opt .filter(h -> h.x < 5) .filter(h -> h.x > 6) .map(h -> h.x).isPresent()' is always 'false'">opt
.filter(h -> h.x < 5)
.filter(h -> <warning descr="Condition 'h.x > 6' is always 'false'">h.x > 6</warning>)
.map(h -> h.x).isPresent()</warning>;
}
void testFilterMap(Optional<Holder> opt) {
boolean present = <warning descr="Condition 'opt .filter(h -> h.s == null) .map(h -> h.s) .isPresent()' is always 'false'">opt
boolean present = <warning descr="Result of 'opt .filter(h -> h.s == null) .map(h -> h.s) .isPresent()' is always 'false'">opt
.filter(h -> h.s == null)
.map(h -> h.s)
.isPresent()</warning>;
@@ -7,7 +7,7 @@ class Test {
test = Optional.of("x");
} else {
test = Optional.empty();
if(<warning descr="Condition '!test.isPresent()' is always 'true'">!<warning descr="Condition 'test.isPresent()' is always 'false'">test.isPresent()</warning></warning>) {
if(<warning descr="Condition '!test.isPresent()' is always 'true'">!<warning descr="Result of 'test.isPresent()' is always 'false'">test.isPresent()</warning></warning>) {
System.out.println("Always");
}
}
@@ -30,8 +30,8 @@ class Test {
System.out.println(maybe.<warning descr="The call to 'get' always fails, according to its method contracts">get</warning>());
}
boolean b = <warning descr="Condition '((maybe.isPresent())) && maybe.get() == 1' is always 'false'">((<warning descr="Condition 'maybe.isPresent()' is always 'false'">maybe.isPresent()</warning>)) && maybe.get() == 1</warning>;
boolean c = <warning descr="Condition '(!maybe.isPresent()) || maybe.get() == 1' is always 'true'">(<warning descr="Condition '!maybe.isPresent()' is always 'true'">!<warning descr="Condition 'maybe.isPresent()' is always 'false'">maybe.isPresent()</warning></warning>) || maybe.get() == 1</warning>;
Integer value = <warning descr="Condition '!maybe.isPresent()' is always 'true'">!<warning descr="Condition 'maybe.isPresent()' is always 'false'">maybe.isPresent()</warning></warning> ? 0 : maybe.get();
boolean c = <warning descr="Condition '(!maybe.isPresent()) || maybe.get() == 1' is always 'true'">(<warning descr="Condition '!maybe.isPresent()' is always 'true'">!<warning descr="Result of 'maybe.isPresent()' is always 'false'">maybe.isPresent()</warning></warning>) || maybe.get() == 1</warning>;
Integer value = <warning descr="Condition '!maybe.isPresent()' is always 'true'">!<warning descr="Result of 'maybe.isPresent()' is always 'false'">maybe.isPresent()</warning></warning> ? 0 : maybe.get();
}
Optional<Integer> getIntegerOptional() {
@@ -40,7 +40,7 @@ class Test {
private static void a() {
Optional<String> optional = Optional.empty();
final boolean present = <warning descr="Condition 'optional.isPresent()' is always 'false'">optional.isPresent()</warning>;
final boolean present = <warning descr="Result of 'optional.isPresent()' is always 'false'">optional.isPresent()</warning>;
// optional = Optional.empty();
if (<warning descr="Condition 'present' is always 'false'">present</warning>) {
final String string = optional.get();
@@ -50,7 +50,7 @@ class Test {
private static void b() {
Optional<String> optional = Optional.empty();
final boolean present = <warning descr="Condition 'optional.isPresent()' is always 'false'">optional.isPresent()</warning>;
final boolean present = <warning descr="Result of 'optional.isPresent()' is always 'false'">optional.isPresent()</warning>;
optional = Optional.empty();
if (<warning descr="Condition 'present' is always 'false'">present</warning>) {
final String string = optional.get();
@@ -81,7 +81,7 @@ class Test {
private void checkAsserts2() {
Optional<String> o3 = Optional.empty();
org.testng.Assert.<warning descr="The call to 'assertTrue' always fails, according to its method contracts">assertTrue</warning>(<warning descr="Condition 'o3.isPresent()' is always 'false'">o3.isPresent()</warning>);
org.testng.Assert.<warning descr="The call to 'assertTrue' always fails, according to its method contracts">assertTrue</warning>(<warning descr="Result of 'o3.isPresent()' is always 'false'">o3.isPresent()</warning>);
System.out.println(o3.get());
}
@@ -100,9 +100,9 @@ class Test {
public static String demo() {
Optional<String> holder = Optional.empty();
if (<warning descr="Condition '! holder.isPresent()' is always 'true'">! <warning descr="Condition 'holder.isPresent()' is always 'false'">holder.isPresent()</warning></warning>) {
if (<warning descr="Condition '! holder.isPresent()' is always 'true'">! <warning descr="Result of 'holder.isPresent()' is always 'false'">holder.isPresent()</warning></warning>) {
holder = Optional.of("hello world");
if (<warning descr="Condition '!holder.isPresent()' is always 'false'">!<warning descr="Condition 'holder.isPresent()' is always 'true'">holder.isPresent()</warning></warning>) {
if (<warning descr="Condition '!holder.isPresent()' is always 'false'">!<warning descr="Result of 'holder.isPresent()' is always 'true'">holder.isPresent()</warning></warning>) {
return null;
}
}
@@ -0,0 +1,22 @@
import org.jetbrains.annotations.*;
import java.util.List;
class Test {
static final Object RES = null;
final Object xyz = null;
static final Integer TEST = new Integer(0);
void test() {
doSmth(RES);
doSmth(<weak_warning descr="Value 'xyz' is always 'null'">xyz</weak_warning>);
System.out.println(process(<weak_warning descr="Value 'xyz' is always 'null'">xyz</weak_warning>).<warning descr="Method invocation 'hashCode' will produce 'java.lang.NullPointerException'">hashCode</warning>());
Integer x = TEST;
System.out.println(TEST);
System.out.println(x);
}
@Contract("null -> null")
native Object process(@Nullable Object obj);
native void doSmth(@Nullable Object obj);
}
@@ -20,7 +20,7 @@ class Test {
public void testDontReplaceQualifierWithNull(Object bar) {
if (bar == null) {
bar.<warning descr="Method invocation 'hashCode' may produce 'java.lang.NullPointerException'">hashCode</warning>();
bar.<warning descr="Method invocation 'hashCode' will produce 'java.lang.NullPointerException'">hashCode</warning>();
}
}
@@ -12,11 +12,11 @@ public class StreamInlining {
list.stream().flatMap(<warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>).forEach(System.out::println);
list.stream().filter(x -> x != null).forEach(<warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>);
List<String> l = null;
l.<warning descr="Method invocation 'stream' may produce 'java.lang.NullPointerException'">stream</warning>().count();
l.<warning descr="Method invocation 'stream' will produce 'java.lang.NullPointerException'">stream</warning>().count();
int[] arr = null;
Arrays.stream(<warning descr="Argument 'arr' might be null">arr</warning>).count();
Stream<String> stream = null;
stream.<warning descr="Method invocation 'filter' may produce 'java.lang.NullPointerException'">filter</warning>(x -> x != null).forEach(System.out::println);
stream.<warning descr="Method invocation 'filter' will produce 'java.lang.NullPointerException'">filter</warning>(x -> x != null).forEach(System.out::println);
}
void testMethodRef(List<String> list, int[] data) {
@@ -125,10 +125,10 @@ public class StreamInlining {
boolean flatMap(List<String> list, List<List<String>> ll) {
System.out.println(ll.stream().flatMap(l -> l.stream()).count());
return list.stream().map(s -> s.isEmpty() ? null : s)
return <warning descr="Result of 'list.stream().map(s -> s.isEmpty() ? null : s) .flatMap(s -> Stream.of(s, s.trim()) ...' is always 'false'">list.stream().map(s -> s.isEmpty() ? null : s)
.flatMap(s -> Stream.of(s, s.<warning descr="Method invocation 'trim' may produce 'java.lang.NullPointerException'">trim</warning>())
.filter(r -> <warning descr="Condition 'r != null' is always 'true'">r != null</warning>))
.anyMatch(x -> <warning descr="Condition 'x == null' is always 'false'">x == null</warning>);
.anyMatch(x -> <warning descr="Condition 'x == null' is always 'false'">x == null</warning>)</warning>;
}
String blockLambda(List<String> list) {
@@ -169,7 +169,7 @@ public class StreamInlining {
void testGenerate() {
List<String> list1 = Stream.generate(() -> Math.random() > 0.5 ? "foo" : "baz")
.limit(10).filter((xyz -> <warning descr="Condition '\"bar\".equals(xyz)' is always 'false'">"bar".equals(xyz)</warning>)).collect(Collectors.toList());
.limit(10).filter((xyz -> <warning descr="Result of '\"bar\".equals(xyz)' is always 'false'">"bar".equals(xyz)</warning>)).collect(Collectors.toList());
List<String> list2 = Stream.generate(() -> "xyz").limit(20).filter(<warning descr="Method reference result is always 'false'">"bar"::equals</warning>).collect(Collectors.toList());
Stream.generate(() -> Optional.of("xyz")).filter(<warning descr="Method reference result is always 'true'">Optional::isPresent</warning>).forEach(System.out::println);
LongStream.generate(() -> 5).limit(10).filter(x -> <warning descr="Condition 'x > 6' is always 'false'">x > 6</warning>).forEach(s -> System.out.println(s));
@@ -38,15 +38,15 @@ public class StreamKnownSource {
System.out.println("Probably");
}
boolean emptyAll = Stream.empty().allMatch(Objects::nonNull);
boolean emptyAll = <warning descr="Result of 'Stream.empty().allMatch(Objects::nonNull)' is always 'true'">Stream.empty().allMatch(Objects::nonNull)</warning>;
if (<warning descr="Condition 'emptyAll' is always 'true'">emptyAll</warning>) {
System.out.println("True");
}
boolean emptyAny = Stream.empty().anyMatch(Objects::nonNull);
boolean emptyAny = <warning descr="Result of 'Stream.empty().anyMatch(Objects::nonNull)' is always 'false'">Stream.empty().anyMatch(Objects::nonNull)</warning>;
if (<warning descr="Condition 'emptyAny' is always 'false'">emptyAny</warning>) {
System.out.println("False");
}
boolean emptyNone = Stream.empty().noneMatch(Objects::nonNull);
boolean emptyNone = <warning descr="Result of 'Stream.empty().noneMatch(Objects::nonNull)' is always 'true'">Stream.empty().noneMatch(Objects::nonNull)</warning>;
if (<warning descr="Condition 'emptyNone' is always 'true'">emptyNone</warning>) {
System.out.println("True");
}
@@ -119,7 +119,7 @@ public class StreamKnownSource {
return;
}
boolean hasNoNulls = list.stream().allMatch(Objects::nonNull);
boolean hasNoNulls = <warning descr="Result of 'list.stream().allMatch(Objects::nonNull)' is always 'true'">list.stream().allMatch(Objects::nonNull)</warning>;
if(<warning descr="Condition 'hasNoNulls' is always 'true'">hasNoNulls</warning>) {
System.out.println("Always");
@@ -93,7 +93,7 @@ class ThisAsVariable {
} else {
Runnable r = new Runnable() {
public void run() {
System.out.println(s.<warning descr="Method invocation 'trim' may produce 'java.lang.NullPointerException'">trim</warning>());
System.out.println(s.<warning descr="Method invocation 'trim' will produce 'java.lang.NullPointerException'">trim</warning>());
}
};
r.run();
@@ -9,7 +9,7 @@ class Test {
void m1() throws Exception {
MyResourceProvider provider = null;
try (MyResource r = provider.<warning descr="Method invocation 'getResource' may produce 'java.lang.NullPointerException'">getResource</warning>()) {
try (MyResource r = provider.<warning descr="Method invocation 'getResource' will produce 'java.lang.NullPointerException'">getResource</warning>()) {
System.out.println(r);
}
}
@@ -7,7 +7,7 @@ class Test {
private void method() {
if (timeStamp == null) {
timeStamp.<warning descr="Method invocation 'set' may produce 'java.lang.NullPointerException'">set</warning>(Calendar.getInstance().getTimeInMillis()); // not reported
timeStamp.<warning descr="Method invocation 'set' will produce 'java.lang.NullPointerException'">set</warning>(Calendar.getInstance().getTimeInMillis()); // not reported
}
}
}
@@ -8,7 +8,7 @@ class Some {
while (parent != null) {
parent = parent.getParentFile();
}
System.out.println(parent.<warning descr="Method invocation 'getName' may produce 'java.lang.NullPointerException'">getName</warning>());
System.out.println(parent.<warning descr="Method invocation 'getName' will produce 'java.lang.NullPointerException'">getName</warning>());
}
System.out.println(file.getName());
}
@@ -3,7 +3,7 @@
<problem>
<file>Test.java</file>
<line>6</line>
<description>'equals' may produce NullPointerException</description>
<description>'equals' will produce NullPointerException</description>
</problem>
</problems>
@@ -625,6 +625,12 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
public void testBoxingBoolean() { doTest(); }
public void testOrWithAssignment() { doTest(); }
public void testAndAndLastOperand() { doTest(); }
public void testReportAlwaysNull() {
DataFlowInspection inspection = new DataFlowInspection();
inspection.REPORT_CONSTANT_REFERENCE_VALUES = true;
myFixture.enableInspections(inspection);
myFixture.testHighlighting(true, false, true, getTestName(false) + ".java");
}
public void testAndAndWithOr() { doTest(); }
public void testBoxUnboxArrayElement() { doTest(); }
public void testExactInstanceOf() { doTest(); }
@@ -56,9 +56,12 @@ configure.annotations.option=Configure annotations
#messages from dataflow inspection
dataflow.message.npe.method.invocation=Method invocation <code>#ref</code> #loc may produce <code>java.lang.NullPointerException</code>
dataflow.message.npe.method.invocation.sure=Method invocation <code>#ref</code> #loc will produce <code>java.lang.NullPointerException</code>
dataflow.message.npe.inner.class.construction=Inner class construction may produce <code>java.lang.NullPointerException</code>
dataflow.message.npe.inner.class.construction.sure=Inner class construction will produce <code>java.lang.NullPointerException</code>
dataflow.message.npe.methodref.invocation=Method reference invocation <code>#ref</code> #loc may produce <code>java.lang.NullPointerException</code>
dataflow.message.npe.array.access=Array access <code>#ref</code> #loc may produce <code>java.lang.NullPointerException</code>
dataflow.message.npe.array.access.sure=Array access <code>#ref</code> #loc will produce <code>java.lang.NullPointerException</code>
dataflow.message.npe.field.access.sure=Dereference of <code>#ref</code> #loc will produce <code>java.lang.NullPointerException</code>
dataflow.message.npe.field.access=Dereference of <code>#ref</code> #loc may produce <code>java.lang.NullPointerException</code>
dataflow.message.cce=Casting <code>{0}</code> to <code>#ref</code> #loc may produce <code>java.lang.ClassCastException</code>