mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
infer method @Nullable/@NotNull by source code
This commit is contained in:
+34
-9
@@ -16,10 +16,7 @@
|
||||
package com.intellij.codeInsight;
|
||||
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalysis;
|
||||
import com.intellij.codeInspection.dataFlow.ContractInference;
|
||||
import com.intellij.codeInspection.dataFlow.HardcodedContracts;
|
||||
import com.intellij.codeInspection.dataFlow.MethodContract;
|
||||
import com.intellij.codeInspection.dataFlow.PurityInference;
|
||||
import com.intellij.codeInspection.dataFlow.*;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
@@ -58,8 +55,17 @@ public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager {
|
||||
}
|
||||
}
|
||||
|
||||
if (ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotationFQN) && canHaveContract(listOwner)) {
|
||||
return getInferredContractAnnotation((PsiMethod)listOwner);
|
||||
if (canInferFromSource(listOwner)) {
|
||||
//noinspection ConstantConditions
|
||||
PsiMethod method = (PsiMethod)listOwner;
|
||||
if (ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotationFQN)) {
|
||||
return getInferredContractAnnotation(method);
|
||||
}
|
||||
|
||||
if ((AnnotationUtil.NOT_NULL.equals(annotationFQN) || AnnotationUtil.NULLABLE.equals(annotationFQN))) {
|
||||
PsiAnnotation anno = getInferredNullityAnnotation(method);
|
||||
return anno == null ? null : annotationFQN.equals(anno.getQualifiedName()) ? anno : null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -100,6 +106,23 @@ public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager {
|
||||
return createContractAnnotation(ContractInference.inferContracts(method), PurityInference.inferPurity(method));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiAnnotation getInferredNullityAnnotation(PsiMethod method) {
|
||||
NullableNotNullManager manager = NullableNotNullManager.getInstance(myProject);
|
||||
if (AnnotationUtil.findAnnotation(method, manager.getNotNulls(), true) != null || AnnotationUtil.findAnnotation(method, manager.getNullables(), true) != null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Nullness nullness = NullityInference.inferNullity(method);
|
||||
if (nullness == Nullness.NOT_NULL) {
|
||||
return ProjectBytecodeAnalysis.getInstance(myProject).getNotNullAnnotation();
|
||||
}
|
||||
if (nullness == Nullness.NULLABLE) {
|
||||
return ProjectBytecodeAnalysis.getInstance(myProject).getNullableAnnotation();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiAnnotation createContractAnnotation(List<MethodContract> contracts, boolean pure) {
|
||||
final String attrs;
|
||||
@@ -115,7 +138,7 @@ public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager {
|
||||
return ProjectBytecodeAnalysis.getInstance(myProject).createContractAnnotation(attrs);
|
||||
}
|
||||
|
||||
private static boolean canHaveContract(PsiModifierListOwner listOwner) {
|
||||
private static boolean canInferFromSource(PsiModifierListOwner listOwner) {
|
||||
return listOwner instanceof PsiMethod && !PsiUtil.canBeOverriden((PsiMethod)listOwner);
|
||||
}
|
||||
|
||||
@@ -127,15 +150,17 @@ public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager {
|
||||
PsiAnnotation[] fromBytecode = ProjectBytecodeAnalysis.getInstance(myProject).findInferredAnnotations(listOwner);
|
||||
for (PsiAnnotation annotation : fromBytecode) {
|
||||
if (!ignoreInference(listOwner, annotation.getQualifiedName())) {
|
||||
if (!ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotation.getQualifiedName()) || canHaveContract(listOwner)) {
|
||||
if (!ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotation.getQualifiedName()) || canInferFromSource(listOwner)) {
|
||||
result.add(annotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (canHaveContract(listOwner)) {
|
||||
if (canInferFromSource(listOwner)) {
|
||||
PsiAnnotation hardcoded = getHardcodedContractAnnotation((PsiMethod)listOwner);
|
||||
ContainerUtil.addIfNotNull(result, hardcoded != null ? hardcoded : getInferredContractAnnotation((PsiMethod)listOwner));
|
||||
|
||||
ContainerUtil.addIfNotNull(result, getInferredNullityAnnotation((PsiMethod)listOwner));
|
||||
}
|
||||
|
||||
return result.isEmpty() ? PsiAnnotation.EMPTY_ARRAY : result.toArray(new PsiAnnotation[result.size()]);
|
||||
|
||||
+2
-2
@@ -198,7 +198,7 @@ public class ProjectBytecodeAnalysis {
|
||||
return PsiAnnotation.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
private PsiAnnotation getNotNullAnnotation() {
|
||||
public PsiAnnotation getNotNullAnnotation() {
|
||||
return CachedValuesManager.getManager(myProject).getCachedValue(myProject, new CachedValueProvider<PsiAnnotation>() {
|
||||
@Nullable
|
||||
@Override
|
||||
@@ -208,7 +208,7 @@ public class ProjectBytecodeAnalysis {
|
||||
});
|
||||
}
|
||||
|
||||
private PsiAnnotation getNullableAnnotation() {
|
||||
public PsiAnnotation getNullableAnnotation() {
|
||||
return CachedValuesManager.getManager(myProject).getCachedValue(myProject, new CachedValueProvider<PsiAnnotation>() {
|
||||
@Nullable
|
||||
@Override
|
||||
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2000-2014 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.
|
||||
*/
|
||||
package com.intellij.codeInspection.dataFlow;
|
||||
|
||||
import com.intellij.codeInsight.NullableNotNullManager;
|
||||
import com.intellij.openapi.util.Computable;
|
||||
import com.intellij.openapi.util.RecursionManager;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.CachedValueProvider;
|
||||
import com.intellij.psi.util.CachedValuesManager;
|
||||
import com.intellij.psi.util.PsiModificationTracker;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
public class NullityInference {
|
||||
|
||||
public static Nullness inferNullity(final PsiMethod method) {
|
||||
if (ContractInference.isLibraryCode(method)) {
|
||||
return Nullness.UNKNOWN;
|
||||
}
|
||||
|
||||
PsiType type = method.getReturnType();
|
||||
if (type == null || type instanceof PsiPrimitiveType) {
|
||||
return Nullness.UNKNOWN;
|
||||
}
|
||||
|
||||
return CachedValuesManager.getCachedValue(method, new CachedValueProvider<Nullness>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public Result<Nullness> compute() {
|
||||
Nullness result = RecursionManager.doPreventingRecursion(method, true, new Computable<Nullness>() {
|
||||
@Override
|
||||
public Nullness compute() {
|
||||
return doInferNullity(method);
|
||||
}
|
||||
});
|
||||
if (result == null) result = Nullness.UNKNOWN;
|
||||
return Result.create(result, method, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Nullness doInferNullity(PsiMethod method) {
|
||||
PsiCodeBlock body = method.getBody();
|
||||
if (body != null) {
|
||||
final AtomicBoolean hasErrors = new AtomicBoolean();
|
||||
final AtomicBoolean hasNotNulls = new AtomicBoolean();
|
||||
final AtomicBoolean hasNulls = new AtomicBoolean();
|
||||
final AtomicBoolean hasUnknowns = new AtomicBoolean();
|
||||
final List<PsiMethodCallExpression> calls = ContainerUtil.newArrayList();
|
||||
body.accept(new JavaRecursiveElementWalkingVisitor() {
|
||||
@Override
|
||||
public void visitReturnStatement(PsiReturnStatement statement) {
|
||||
PsiExpression value = statement.getReturnValue();
|
||||
if (value == null) {
|
||||
hasErrors.set(true);
|
||||
} else {
|
||||
if (value instanceof PsiLiteralExpression) {
|
||||
if (value.textMatches(PsiKeyword.NULL)) {
|
||||
hasNulls.set(true);
|
||||
} else {
|
||||
hasNotNulls.set(true);
|
||||
}
|
||||
} else if (value instanceof PsiMethodCallExpression) {
|
||||
calls.add((PsiMethodCallExpression)value);
|
||||
} else {
|
||||
hasUnknowns.set(true);
|
||||
}
|
||||
}
|
||||
super.visitReturnStatement(statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitErrorElement(PsiErrorElement element) {
|
||||
hasErrors.set(true);
|
||||
super.visitErrorElement(element);
|
||||
}
|
||||
});
|
||||
|
||||
if (hasNulls.get()) {
|
||||
return Nullness.NULLABLE;
|
||||
}
|
||||
|
||||
if (calls.size() > 1) {
|
||||
return Nullness.UNKNOWN;
|
||||
}
|
||||
|
||||
if (calls.size() == 1) {
|
||||
PsiMethod target = calls.get(0).resolveMethod();
|
||||
if (target != null && NullableNotNullManager.isNotNull(target)) {
|
||||
return Nullness.NOT_NULL;
|
||||
}
|
||||
return Nullness.UNKNOWN;
|
||||
}
|
||||
|
||||
if (hasUnknowns.get()) {
|
||||
return Nullness.UNKNOWN;
|
||||
}
|
||||
|
||||
if (hasNotNulls.get()) {
|
||||
return Nullness.NOT_NULL;
|
||||
}
|
||||
|
||||
}
|
||||
return Nullness.UNKNOWN;
|
||||
}
|
||||
}
|
||||
+12
-6
@@ -27,6 +27,7 @@ package com.intellij.codeInspection.dataFlow;
|
||||
import com.intellij.codeInsight.NullableNotNullManager;
|
||||
import com.intellij.codeInspection.dataFlow.instructions.InstanceofInstruction;
|
||||
import com.intellij.codeInspection.dataFlow.instructions.Instruction;
|
||||
import com.intellij.codeInspection.nullable.NullableStuffInspectionBase;
|
||||
import com.intellij.psi.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -53,17 +54,13 @@ public class StandardDataFlowRunner extends DataFlowRunner {
|
||||
myIsInMethod = parent instanceof PsiMethod;
|
||||
if (myIsInMethod) {
|
||||
PsiMethod method = (PsiMethod)parent;
|
||||
PsiType returnType = method.getReturnType();
|
||||
myInNullableMethod = NullableNotNullManager.isNullable(method) ||
|
||||
returnType != null && returnType.equalsToText(CommonClassNames.JAVA_LANG_VOID);
|
||||
myInNullableMethod = isTreatedAsNullable(method);
|
||||
myInNotNullMethod = NullableNotNullManager.isNotNull(method);
|
||||
} else if (parent instanceof PsiLambdaExpression) {
|
||||
PsiMethod method = LambdaUtil.getFunctionalInterfaceMethod(((PsiLambdaExpression)parent).getFunctionalInterfaceType());
|
||||
if (method != null) {
|
||||
myIsInMethod = true;
|
||||
PsiType returnType = method.getReturnType();
|
||||
myInNullableMethod = NullableNotNullManager.isNullable(method) ||
|
||||
returnType != null && returnType.equalsToText(CommonClassNames.JAVA_LANG_VOID);
|
||||
myInNullableMethod = isTreatedAsNullable(method);
|
||||
myInNotNullMethod = NullableNotNullManager.isNotNull(method);
|
||||
}
|
||||
}
|
||||
@@ -71,6 +68,15 @@ public class StandardDataFlowRunner extends DataFlowRunner {
|
||||
myCCEInstructions.clear();
|
||||
}
|
||||
|
||||
private static boolean isTreatedAsNullable(PsiMethod method) {
|
||||
if (NullableStuffInspectionBase.isNullableNotInferred(method, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PsiType returnType = method.getReturnType();
|
||||
return returnType != null && returnType.equalsToText(CommonClassNames.JAVA_LANG_VOID);
|
||||
}
|
||||
|
||||
public void onInstructionProducesCCE(Instruction instruction) {
|
||||
myCCEInstructions.add(instruction);
|
||||
}
|
||||
|
||||
+1
-1
@@ -528,7 +528,7 @@ public class NullableStuffInspectionBase extends BaseJavaBatchLocalInspectionToo
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isNullableNotInferred(@NotNull PsiModifierListOwner owner, boolean checkBases) {
|
||||
public static boolean isNullableNotInferred(@NotNull PsiModifierListOwner owner, boolean checkBases) {
|
||||
Project project = owner.getProject();
|
||||
NullableNotNullManager manager = NullableNotNullManager.getInstance(project);
|
||||
if (!manager.isNullable(owner, checkBases)) return false;
|
||||
|
||||
+4
-4
@@ -359,13 +359,13 @@ class ContractInferenceFromSourceTest extends LightCodeInsightFixtureTestCase {
|
||||
|
||||
public void "test use delegated method notnull"() {
|
||||
def c = inferContracts("""
|
||||
final Object foo(Object bar) {
|
||||
return doo();
|
||||
final Object foo(Object bar, boolean b) {
|
||||
return b ? doo() : null;
|
||||
}
|
||||
|
||||
@org.jetbrains.annotations.NotNull Object doo() {}
|
||||
""")
|
||||
assert c == ['_ -> !null']
|
||||
assert c == ['_, true -> !null', '_, false -> null']
|
||||
}
|
||||
|
||||
public void "test use delegated method notnull with contracts"() {
|
||||
@@ -379,7 +379,7 @@ class ContractInferenceFromSourceTest extends LightCodeInsightFixtureTestCase {
|
||||
return smth();
|
||||
}
|
||||
""")
|
||||
assert c == ['_, null -> fail', '_, _ -> !null']
|
||||
assert c == ['_, null -> fail']
|
||||
}
|
||||
|
||||
public void "test dig into type cast"() {
|
||||
|
||||
+2
-1
@@ -30,7 +30,8 @@ public class DataFlowInspectionTestSuite {
|
||||
suite.addTestSuite(DataFlowInspectionAncientTest.class);
|
||||
suite.addTestSuite(ContractCheckTest.class);
|
||||
suite.addTestSuite(ContractInferenceFromSourceTest.class);
|
||||
suite.addTestSuite(NullityInferenceFromDfaTest.class);
|
||||
suite.addTestSuite(NullityInferenceFromSourceTestCase.DfaInferenceTest.class);
|
||||
suite.addTestSuite(NullityInferenceFromSourceTestCase.LightInferenceTest.class);
|
||||
suite.addTestSuite(PurityInferenceFromSourceTest.class);
|
||||
suite.addTestSuite(SliceTreeTest.class);
|
||||
suite.addTestSuite(SliceBackwardTest.class);
|
||||
|
||||
+25
-4
@@ -14,17 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.codeInspection
|
||||
import com.intellij.codeInsight.NullableNotNullManager
|
||||
import com.intellij.codeInspection.dataFlow.DfaUtil
|
||||
import com.intellij.codeInspection.dataFlow.Nullness
|
||||
import com.intellij.psi.PsiMethod
|
||||
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
|
||||
|
||||
import static com.intellij.codeInspection.dataFlow.Nullness.*
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
class NullityInferenceFromDfaTest extends LightCodeInsightFixtureTestCase {
|
||||
abstract class NullityInferenceFromSourceTestCase extends LightCodeInsightFixtureTestCase {
|
||||
|
||||
void "test return string literal"() {
|
||||
assert inferNullity(parse('String foo() { return "a"; }')) == NOT_NULL
|
||||
@@ -38,12 +38,33 @@ class NullityInferenceFromDfaTest extends LightCodeInsightFixtureTestCase {
|
||||
assert inferNullity(parse('int foo() { return "z"; }')) == UNKNOWN
|
||||
}
|
||||
|
||||
private static Nullness inferNullity(PsiMethod method) {
|
||||
return DfaUtil.inferMethodNullity(method)
|
||||
void "test delegation"() {
|
||||
assert inferNullity(parse('String foo() { return bar(); }; String bar() { return "z"; }; ')) == NOT_NULL
|
||||
}
|
||||
|
||||
void "test if branch returns null"() {
|
||||
assert inferNullity(parse('String bar() { if (equals(2)) return null; return "a"; }; ')) == NULLABLE
|
||||
}
|
||||
|
||||
void "test delegation to nullable means nothing"() {
|
||||
assert inferNullity(parse('String foo() { return bar(); }; String bar() { if (equals(2)) return null; return "a"; }; ')) == UNKNOWN
|
||||
}
|
||||
|
||||
protected abstract Nullness inferNullity(PsiMethod method)
|
||||
|
||||
private PsiMethod parse(String method) {
|
||||
return myFixture.addClass("final class Foo { $method }").methods[0]
|
||||
}
|
||||
|
||||
static class LightInferenceTest extends NullityInferenceFromSourceTestCase {
|
||||
Nullness inferNullity(PsiMethod method) {
|
||||
return NullableNotNullManager.isNotNull(method) ? NOT_NULL : NullableNotNullManager.isNullable(method) ? NULLABLE : UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
static class DfaInferenceTest extends NullityInferenceFromSourceTestCase {
|
||||
Nullness inferNullity(PsiMethod method) {
|
||||
return DfaUtil.inferMethodNullity(method)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user