mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-13 21:55:01 +07:00
[java] JSpecify: initial strict mode support
GitOrigin-RevId: a387d37ac8924b8e63847eb54458424f5f690d9a
This commit is contained in:
committed by
intellij-monorepo-bot
parent
0b0c0b1bd5
commit
ba33ae8bcf
@@ -85,6 +85,7 @@ dataflow.message.unreachable.switch.label=Switch label <code>#ref</code> #loc is
|
||||
dataflow.message.constant.expression=Result of <code>#ref</code> #loc is always ''{0}''
|
||||
dataflow.message.constant.value=Value <code>#ref</code> #loc is always ''{0}''
|
||||
dataflow.method.fails.with.null.argument=Method will throw an exception when parameter is null
|
||||
dataflow.message.unknown.nullability=\ (unknown nullability)
|
||||
dataflow.not.precise={0} is complex: data flow results could be imprecise
|
||||
dataflow.too.complex={0} is too complex to analyze by data flow algorithm
|
||||
|
||||
|
||||
+1
-1
@@ -153,7 +153,7 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
|
||||
}
|
||||
|
||||
StreamEx<NullabilityProblemKind.NullabilityProblem<?>> problems() {
|
||||
return StreamEx.ofKeys(myStateInfos, StateInfo::shouldReport);
|
||||
return EntryStream.of(myStateInfos).filterValues(StateInfo::shouldReport).mapKeyValue((np, si) -> si.unknown ? np.makeUnknown() : np);
|
||||
}
|
||||
|
||||
public Map<PsiAssignmentExpression, Pair<PsiType, PsiType>> getArrayStoreProblems() {
|
||||
|
||||
+33
-6
@@ -117,7 +117,7 @@ public final class NullabilityProblemKind<T extends PsiElement> {
|
||||
@Contract("null, _ -> null")
|
||||
@Nullable
|
||||
public final NullabilityProblem<T> problem(@Nullable T anchor, @Nullable PsiExpression expression) {
|
||||
return anchor == null || this == noProblem ? null : new NullabilityProblem<>(this, anchor, expression);
|
||||
return anchor == null || this == noProblem ? null : new NullabilityProblem<>(this, anchor, expression, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -430,7 +430,12 @@ public final class NullabilityProblemKind<T extends PsiElement> {
|
||||
if (innerClassNPE == kind || callNPE == kind || arrayAccessNPE == kind || fieldAccessNPE == kind) {
|
||||
// Qualifier-problems are reported on top-expression level for now as it's rare case to have
|
||||
// something complex in qualifier and we highlight not the qualifier itself, but something else (e.g. called method name)
|
||||
unchanged.add(problem.withExpression(findTopExpression(expression)));
|
||||
boolean unknown = problem.hasUnknownNullability();
|
||||
problem = problem.withExpression(findTopExpression(expression));
|
||||
if (unknown) {
|
||||
problem = problem.makeUnknown();
|
||||
}
|
||||
unchanged.add(problem);
|
||||
continue;
|
||||
}
|
||||
// Merge ternary problems reported for both branches into single problem
|
||||
@@ -450,7 +455,11 @@ public final class NullabilityProblemKind<T extends PsiElement> {
|
||||
NullabilityProblem<?> otherBranchProblem = expressionToProblem.remove(otherBranch);
|
||||
if (otherBranchProblem != null) {
|
||||
expression = ternary;
|
||||
boolean unknown = problem.hasUnknownNullability() && otherBranchProblem.hasUnknownNullability();
|
||||
problem = problem.withExpression(ternary);
|
||||
if (unknown) {
|
||||
problem = problem.makeUnknown();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -499,11 +508,16 @@ public final class NullabilityProblemKind<T extends PsiElement> {
|
||||
private final @NotNull NullabilityProblemKind<T> myKind;
|
||||
private final @NotNull T myAnchor;
|
||||
private final @Nullable PsiExpression myDereferencedExpression;
|
||||
private final boolean myFromUnknown;
|
||||
|
||||
NullabilityProblem(@NotNull NullabilityProblemKind<T> kind, @NotNull T anchor, @Nullable PsiExpression dereferencedExpression) {
|
||||
NullabilityProblem(@NotNull NullabilityProblemKind<T> kind,
|
||||
@NotNull T anchor,
|
||||
@Nullable PsiExpression dereferencedExpression,
|
||||
boolean unknown) {
|
||||
myKind = kind;
|
||||
myAnchor = anchor;
|
||||
myDereferencedExpression = dereferencedExpression;
|
||||
myFromUnknown = unknown;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -528,18 +542,27 @@ public final class NullabilityProblemKind<T extends PsiElement> {
|
||||
return myDereferencedExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if dereferenced expression has unknown nullability
|
||||
* (reported in {@link DataFlowInspectionBase#TREAT_UNKNOWN_MEMBERS_AS_NULLABLE} mode).
|
||||
*/
|
||||
public boolean hasUnknownNullability() {
|
||||
return myFromUnknown;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public @InspectionMessage String getMessage(Map<PsiExpression, DataFlowInspectionBase.ConstantResult> expressions) {
|
||||
if (myKind.myAlwaysNullMessage == null || myKind.myNormalMessage == null) {
|
||||
throw new IllegalStateException("This problem kind has no message associated: " + myKind);
|
||||
}
|
||||
String suffix = myFromUnknown ? JavaAnalysisBundle.message("dataflow.message.unknown.nullability") : "";
|
||||
PsiExpression expression = PsiUtil.skipParenthesizedExprDown(getDereferencedExpression());
|
||||
if (expression != null) {
|
||||
if (ExpressionUtils.isNullLiteral(expression) || expressions.get(expression) == DataFlowInspectionBase.ConstantResult.NULL) {
|
||||
return myKind.myAlwaysNullMessage.get();
|
||||
return myKind.myAlwaysNullMessage.get() + suffix;
|
||||
}
|
||||
}
|
||||
return myKind.myNormalMessage.get();
|
||||
return myKind.myNormalMessage.get() + suffix;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -567,7 +590,11 @@ public final class NullabilityProblemKind<T extends PsiElement> {
|
||||
}
|
||||
|
||||
public NullabilityProblem<T> withExpression(PsiExpression expression) {
|
||||
return expression == myDereferencedExpression ? this : new NullabilityProblem<>(myKind, myAnchor, expression);
|
||||
return expression == myDereferencedExpression ? this : new NullabilityProblem<>(myKind, myAnchor, expression, false);
|
||||
}
|
||||
|
||||
public NullabilityProblem<T> makeUnknown() {
|
||||
return new NullabilityProblem<>(myKind, myAnchor, myDereferencedExpression, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -95,7 +95,7 @@ class Test6 extends BadSuper {
|
||||
}
|
||||
|
||||
public Integer someLength() {
|
||||
return something.<warning descr="Method invocation 'length' may produce 'NullPointerException'">length</warning>();
|
||||
return something.<warning descr="Method invocation 'length' may produce 'NullPointerException' (unknown nullability)">length</warning>();
|
||||
}
|
||||
|
||||
protected void overrideableMethod() {
|
||||
@@ -107,7 +107,7 @@ class Test7 extends BadSuper {
|
||||
private final String something = new String("something");
|
||||
|
||||
protected void overrideableMethod() {
|
||||
something.<warning descr="Method invocation 'length' may produce 'NullPointerException'">length</warning>();
|
||||
something.<warning descr="Method invocation 'length' may produce 'NullPointerException' (unknown nullability)">length</warning>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +124,6 @@ class Test8 {
|
||||
|
||||
void other() {
|
||||
System.out.println(s.hashCode());
|
||||
System.out.println(s2.<warning descr="Method invocation 'hashCode' may produce 'NullPointerException'">hashCode</warning>());
|
||||
System.out.println(s2.<warning descr="Method invocation 'hashCode' may produce 'NullPointerException' (unknown nullability)">hashCode</warning>());
|
||||
}
|
||||
}
|
||||
@@ -4,15 +4,15 @@ class Test {
|
||||
Object o;
|
||||
|
||||
void field() {
|
||||
o.<warning descr="Method invocation 'hashCode' may produce 'NullPointerException'">hashCode</warning>();
|
||||
o.<warning descr="Method invocation 'hashCode' may produce 'NullPointerException' (unknown nullability)">hashCode</warning>();
|
||||
}
|
||||
|
||||
void parameter(Object o) {
|
||||
o.<warning descr="Method invocation 'hashCode' may produce 'NullPointerException'">hashCode</warning>();
|
||||
o.<warning descr="Method invocation 'hashCode' may produce 'NullPointerException' (unknown nullability)">hashCode</warning>();
|
||||
}
|
||||
|
||||
void callUnknownMethod() {
|
||||
unknownObject().<warning descr="Method invocation 'hashCode' may produce 'NullPointerException'">hashCode</warning>();
|
||||
unknownObject().<warning descr="Method invocation 'hashCode' may produce 'NullPointerException' (unknown nullability)">hashCode</warning>();
|
||||
}
|
||||
|
||||
void callNotNullMethod() {
|
||||
|
||||
+18
-16
@@ -130,6 +130,7 @@ public class JSpecifyAnnotationTest extends LightJavaCodeInsightFixtureTestCase
|
||||
|
||||
Map<PsiElement, String> actual = new LinkedHashMap<>();
|
||||
var dfaInspection = new JSpecifyDataFlowInspection(actual);
|
||||
dfaInspection.TREAT_UNKNOWN_MEMBERS_AS_NULLABLE = true;
|
||||
var nullableStuffInspection = new JSpecifyNullableStuffInspection(actual);
|
||||
var notNullFieldNotInitializedInspection = new JSpecifyNotNullFieldNotInitializedInspection(actual);
|
||||
List<LocalInspectionTool> inspections = List.of(dfaInspection, nullableStuffInspection, notNullFieldNotInitializedInspection);
|
||||
@@ -208,24 +209,25 @@ public class JSpecifyAnnotationTest extends LightJavaCodeInsightFixtureTestCase
|
||||
List<NullabilityProblemKind.NullabilityProblem<?>> problems,
|
||||
Map<PsiExpression, DataFlowInspectionBase.ConstantResult> expressions) {
|
||||
for (NullabilityProblemKind.NullabilityProblem<?> problem : problems) {
|
||||
PsiExpression expression = problem.getDereferencedExpression();
|
||||
if (expression != null) {
|
||||
if (problem.getKind() == NullabilityProblemKind.nullableReturn) {
|
||||
PsiType returnType = PsiTypesUtil.getMethodReturnType(expression);
|
||||
Nullability nullability = DfaPsiUtil.getTypeNullability(returnType);
|
||||
if (nullability == Nullability.UNKNOWN) {
|
||||
warnings.put(expression, "jspecify_nullness_not_enough_information");
|
||||
}
|
||||
if (nullability == Nullability.NOT_NULL) {
|
||||
warnings.put(expression, "jspecify_nullness_mismatch");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else if (problem.getKind() == NullabilityProblemKind.passingToNonAnnotatedParameter) continue;
|
||||
warnings.put(expression, "jspecify_nullness_mismatch");
|
||||
String warning = getJSpecifyWarning(problem);
|
||||
if (warning != null) {
|
||||
warnings.put(problem.getDereferencedExpression(), warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable String getJSpecifyWarning(NullabilityProblemKind.NullabilityProblem<?> problem) {
|
||||
PsiExpression expression = problem.getDereferencedExpression();
|
||||
if (expression == null) return null;
|
||||
if (problem.getKind() == NullabilityProblemKind.passingToNonAnnotatedParameter) return null;
|
||||
if (problem.getKind() == NullabilityProblemKind.nullableReturn) {
|
||||
PsiType returnType = PsiTypesUtil.getMethodReturnType(expression);
|
||||
Nullability nullability = DfaPsiUtil.getTypeNullability(returnType);
|
||||
if (nullability == Nullability.NULLABLE) return null;
|
||||
if (nullability == Nullability.UNKNOWN) return "jspecify_nullness_not_enough_information";
|
||||
}
|
||||
return problem.hasUnknownNullability() ? "jspecify_nullness_not_enough_information" : "jspecify_nullness_mismatch";
|
||||
}
|
||||
}
|
||||
|
||||
String getActualText(Map<PsiElement, String> actual, String stripped) {
|
||||
@@ -236,7 +238,7 @@ public class JSpecifyAnnotationTest extends LightJavaCodeInsightFixtureTestCase
|
||||
.grouping(TreeMap::new, Collectors.toList());
|
||||
for (String str : stripped.split("\n", -1)) {
|
||||
int endPos = pos + str.length() + 1;
|
||||
String warnings = StreamEx.of(map.subMap(pos, endPos).values()).flatMap(List::stream).joining(" & ");
|
||||
String warnings = StreamEx.of(map.subMap(pos, endPos).values()).flatMap(List::stream).distinct().joining(" & ");
|
||||
if (!warnings.isEmpty()) {
|
||||
sb.append("// ").append(warnings);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user