Java: Look for classes which escape their scope in all parts of the method's signature (IDEA-166535)

This commit is contained in:
Pavel Dolgov
2017-02-08 15:53:28 +03:00
parent d45ed056ec
commit 764452dba1
4 changed files with 106 additions and 49 deletions
@@ -16,13 +16,16 @@
package com.siyeh.ig.visibility;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.BaseInspectionVisitor;
import org.intellij.lang.annotations.Pattern;
import org.jetbrains.annotations.NotNull;
public class ClassEscapesItsScopeInspection extends BaseInspection {
@Pattern(VALID_ID_PATTERN)
@Override
@NotNull
public String getID() {
@@ -47,70 +50,55 @@ public class ClassEscapesItsScopeInspection extends BaseInspection {
}
private static class ClassEscapesItsScopeVisitor extends BaseInspectionVisitor {
@Override
public void visitMethod(@NotNull PsiMethod method) {
//no call to super, so we don't drill into anonymous classes
if (method.isConstructor()) {
return;
public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
super.visitReferenceElement(reference);
PsiElement parent = reference.getParent();
if (parent instanceof PsiTypeElement || parent instanceof PsiReferenceList) {
PsiElement grandParent = PsiTreeUtil.skipParentsOfType(reference, PsiTypeElement.class, PsiReferenceList.class,
PsiParameter.class, PsiParameterList.class,
PsiReferenceParameterList.class, PsiJavaCodeReferenceElement.class,
PsiTypeParameter.class, PsiTypeParameterList.class);
if (grandParent instanceof PsiField || grandParent instanceof PsiMethod) {
PsiMember member = (PsiMember)grandParent;
if (!isPrivate(member)) {
PsiElement resolved = reference.resolve();
if (resolved instanceof PsiClass && !(resolved instanceof PsiTypeParameter)) {
PsiClass psiClass = (PsiClass)resolved;
if (isLessRestrictiveScope(member, psiClass)) {
registerError(reference);
}
}
}
}
}
if (method.hasModifierProperty(PsiModifier.PRIVATE)) {
return;
}
checkForEscaping(method, method.getReturnType(), method.getReturnTypeElement());
}
@Override
public void visitField(@NotNull PsiField field) {
//no call to super, so we don't drill into anonymous classes
if (field.hasModifierProperty(PsiModifier.PRIVATE)) {
return;
private static boolean isPrivate(@NotNull PsiMember member) {
if (member.hasModifierProperty(PsiModifier.PRIVATE)) {
return true;
}
final PsiClass containingClass = field.getContainingClass();
if (containingClass == null) {
return;
PsiClass containingClass = member.getContainingClass();
if (containingClass != null && isPrivate(containingClass)) {
return true;
}
if (containingClass.hasModifierProperty(PsiModifier.PRIVATE)) {
return;
}
checkForEscaping(field, field.getType(), field.getTypeElement());
return false;
}
private void checkForEscaping(PsiMember member, PsiType type, PsiTypeElement typeElement) {
if (type == null || typeElement == null) {
return;
}
final PsiType componentType = type.getDeepComponentType();
if (!(componentType instanceof PsiClassType)) {
return;
}
final PsiClass fieldClass = ((PsiClassType)componentType).resolve();
if (fieldClass == null || fieldClass instanceof PsiTypeParameter) {
return;
}
if (!isLessRestrictiveScope(member, fieldClass)) {
return;
}
final PsiJavaCodeReferenceElement baseTypeElement = typeElement.getInnermostComponentReferenceElement();
if (baseTypeElement == null) {
return;
}
registerError(baseTypeElement);
}
private static boolean isLessRestrictiveScope(PsiMember method, PsiClass aClass) {
final int methodScopeOrder = getScopeOrder(method);
private static boolean isLessRestrictiveScope(@NotNull PsiMember member, @NotNull PsiClass aClass) {
final int methodScopeOrder = getScopeOrder(member);
final int classScopeOrder = getScopeOrder(aClass);
final PsiClass containingClass = method.getContainingClass();
if (containingClass != null && containingClass.getQualifiedName() == null) {
final PsiClass containingClass = member.getContainingClass();
if (containingClass == null ||
containingClass.getQualifiedName() == null) {
return false;
}
final int containingClassScopeOrder = getScopeOrder(containingClass);
return methodScopeOrder > classScopeOrder && containingClassScopeOrder > classScopeOrder;
}
private static int getScopeOrder(PsiModifierListOwner element) {
private static int getScopeOrder(@NotNull PsiModifierListOwner element) {
if (element.hasModifierProperty(PsiModifier.PUBLIC)) {
return 4;
}
@@ -5,7 +5,15 @@ public class ClassEscapesItsScope<T> {
public <warning descr="Class 'A' is made visible outside its defined scope">A</warning> giveMeA() {
return new A();
}
void printA(<warning descr="Class 'A' is made visible outside its defined scope">A</warning> a) {
System.out.println(a);
}
private class A {}
void throwsE() throws <warning descr="Class 'E' is made visible outside its defined scope">E</warning> {
throw new E();
}
private static class E extends Exception {}
}
class BarInside {
@@ -0,0 +1,59 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.*;
public class GenericParameterEscapesItsScope {
public List<A> as;
public List<<warning descr="Class 'B' is made visible outside its defined scope">B</warning>> bs;
public List<<warning descr="Class 'B' is made visible outside its defined scope">B</warning>> getBs() { return bs; }
public void setBs(List<<warning descr="Class 'B' is made visible outside its defined scope">B</warning>> bs) { this.bs = bs; }
public List<A> getAs() { return as; }
public void setAs(List<A> as) { this.as = as; }
public class Inner extends B implements Getter<B>, Setter<B> {
public <warning descr="Class 'B' is made visible outside its defined scope">B</warning> b;
@Override
public <warning descr="Class 'B' is made visible outside its defined scope">B</warning> get() {
return b;
}
@Override
public void set(<warning descr="Class 'B' is made visible outside its defined scope">B</warning> b) {
this.b = b;
}
}
public Data<<warning descr="Class 'B' is made visible outside its defined scope">B</warning>> foo() {
class Local extends B implements Data<B> {
public B b;
@Override
public B get() {
return b;
}
@Override
public void set(B b) {
this.b = b;
}
}
return new Local();
}
public static class A {}
static class B extends A {}
public interface Getter<T> { T get(); }
interface Setter<T> { void set(T t); }
public interface Data<T> extends Getter<T>, Setter<T> { }
}
@@ -27,6 +27,8 @@ public class ClassEscapesItsScopeInspectionTest extends LightInspectionTestCase
public void testClassEscapesItsScope() { doTest(); }
public void testGenericParameterEscapesItsScope() { doTest(); }
@Nullable
@Override
protected InspectionProfileEntry getInspection() {