Java: statements before super() part III - highlight illegal this access before super() call (IDEA-340403)

GitOrigin-RevId: 96f15a88940eabe10c7d907c203f7b58dfbc00c7
This commit is contained in:
Bas Leijdekkers
2023-12-31 17:39:46 +01:00
committed by intellij-monorepo-bot
parent 59ac6e04ee
commit b156a18110
3 changed files with 223 additions and 101 deletions

View File

@@ -2673,13 +2673,21 @@ public final class HighlightUtil {
}
static HighlightInfo.Builder checkMemberReferencedBeforeConstructorCalled(@NotNull PsiElement expression,
@Nullable PsiElement resolved,
@NotNull PsiFile containingFile,
@NotNull Function<? super PsiElement, ? extends PsiClass> insideConstructorOfClass) {
if (insideConstructorOfClass.apply(expression) == null) {
@Nullable PsiElement resolved,
@NotNull Function<? super PsiElement, ? extends PsiMethod> surroundingConstructor) {
PsiMethod constructor = surroundingConstructor.apply(expression);
if (constructor == null) {
// not inside expression inside constructor
return null;
}
PsiMethodCallExpression constructorCall = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(constructor);
if (constructorCall == null) {
return null;
}
if (expression.getTextOffset() > constructorCall.getTextOffset() + constructorCall.getTextLength()) {
return null;
}
// is in or before this() or super() call
PsiClass referencedClass;
String resolvedName;
@@ -2784,80 +2792,73 @@ public final class HighlightUtil {
return null;
}
PsiElement element = parent;
while (element != null) {
// check if expression inside super()/this() call
PsiClass parentClass = constructor.getContainingClass();
if (parentClass == null) {
return null;
}
if (JavaPsiConstructorUtil.isConstructorCall(element)) {
PsiClass parentClass = insideConstructorOfClass.apply(element);
if (parentClass == null) {
return null;
}
// references to private methods from the outer class are not calls to super methods
// even if the outer class is the super class
if (resolved instanceof PsiMember member && member.hasModifierProperty(PsiModifier.PRIVATE) && referencedClass != parentClass) {
return null;
}
// field or method should be declared in this class or super
if (!InheritanceUtil.isInheritorOrSelf(parentClass, referencedClass, true)) return null;
// and point to our instance
if (expression instanceof PsiReferenceExpression ref) {
PsiExpression qualifier = ref.getQualifierExpression();
if (!isThisOrSuperReference(qualifier, parentClass)) {
return null;
}
else if (qualifier instanceof PsiThisExpression || qualifier instanceof PsiSuperExpression) {
if (((PsiQualifiedExpression)qualifier).getQualifier() != null) return null;
}
}
if (expression instanceof PsiThisExpression || expression instanceof PsiSuperExpression) {
if (referencedClass != parentClass) return null;
}
if (expression instanceof PsiJavaCodeReferenceElement) {
if (!parentClass.equals(PsiTreeUtil.getParentOfType(expression, PsiClass.class)) &&
PsiTreeUtil.getParentOfType(expression, PsiTypeElement.class) != null) {
return null;
}
if (PsiTreeUtil.getParentOfType(expression, PsiClassObjectAccessExpression.class) != null) {
return null;
}
if (parent instanceof PsiNewExpression newExpression &&
newExpression.isArrayCreation() &&
newExpression.getClassOrAnonymousClassReference() == expression) {
return null;
}
if (parent instanceof PsiThisExpression || parent instanceof PsiSuperExpression) return null;
}
HighlightInfo.Builder builder = createMemberReferencedError(resolvedName, expression.getTextRange());
if (expression instanceof PsiReferenceExpression ref && PsiUtil.isInnerClass(parentClass)) {
String referenceName = ref.getReferenceName();
PsiClass containingClass = parentClass.getContainingClass();
LOG.assertTrue(containingClass != null);
PsiField fieldInContainingClass = containingClass.findFieldByName(referenceName, true);
if (fieldInContainingClass != null && ref.getQualifierExpression() == null) {
builder.registerFix(new QualifyWithThisFix(containingClass, ref), null, null, null, null);
}
}
return builder;
}
element = element.getParent();
if (element instanceof PsiClass && InheritanceUtil.isInheritorOrSelf((PsiClass)element, referencedClass, true)) {
if ((expression instanceof PsiThisExpression || expression instanceof PsiSuperExpression) &&
((PsiQualifiedExpression)expression).getQualifier() != null) {
continue;
}
// references to private methods from the outer class are not calls to super methods
// even if the outer class is the super class
if (resolved instanceof PsiMember member && member.hasModifierProperty(PsiModifier.PRIVATE) && referencedClass != parentClass) {
return null;
}
// field or method should be declared in this class or super
if (!InheritanceUtil.isInheritorOrSelf(parentClass, referencedClass, true)) return null;
// and point to our instance
if (expression instanceof PsiReferenceExpression ref) {
PsiExpression qualifier = ref.getQualifierExpression();
if (!isThisOrSuperReference(qualifier, parentClass)) {
return null;
}
else if (qualifier instanceof PsiThisExpression || qualifier instanceof PsiSuperExpression) {
if (((PsiQualifiedExpression)qualifier).getQualifier() != null) return null;
}
}
return null;
if (expression instanceof PsiThisExpression || expression instanceof PsiSuperExpression) {
if (referencedClass != parentClass) return null;
}
if (expression instanceof PsiJavaCodeReferenceElement) {
if (!parentClass.equals(PsiTreeUtil.getParentOfType(expression, PsiClass.class)) &&
PsiTreeUtil.getParentOfType(expression, PsiTypeElement.class) != null) {
return null;
}
if (PsiTreeUtil.getParentOfType(expression, PsiClassObjectAccessExpression.class) != null) {
return null;
}
if (parent instanceof PsiNewExpression newExpression &&
newExpression.isArrayCreation() &&
newExpression.getClassOrAnonymousClassReference() == expression) {
return null;
}
if (parent instanceof PsiThisExpression || parent instanceof PsiSuperExpression) return null;
}
if (!(expression instanceof PsiThisExpression) && !(expression instanceof PsiSuperExpression) ||
((PsiQualifiedExpression)expression).getQualifier() == null) {
PsiClass expressionClass = PsiTreeUtil.getParentOfType(expression, PsiClass.class, true);
while (expressionClass != null && parentClass != expressionClass) {
if (InheritanceUtil.isInheritorOrSelf(expressionClass, referencedClass, true)) {
return null;
}
expressionClass = PsiTreeUtil.getParentOfType(expressionClass, PsiClass.class, true);
}
}
HighlightInfo.Builder builder = createMemberReferencedError(resolvedName, expression.getTextRange());
if (expression instanceof PsiReferenceExpression ref && PsiUtil.isInnerClass(parentClass)) {
String referenceName = ref.getReferenceName();
PsiClass containingClass = parentClass.getContainingClass();
LOG.assertTrue(containingClass != null);
PsiField fieldInContainingClass = containingClass.findFieldByName(referenceName, true);
if (fieldInContainingClass != null && ref.getQualifierExpression() == null) {
builder.registerFix(new QualifyWithThisFix(containingClass, ref), null, null, null, null);
}
}
return builder;
}
@NotNull

View File

@@ -90,8 +90,8 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
private final Map<PsiClass, MostlySingularMultiMap<MethodSignature, PsiMethod>> myDuplicateMethods = new HashMap<>();
private final Set<PsiClass> myOverrideEquivalentMethodsVisitedClasses = new HashSet<>();
private final Map<PsiMethod, PsiType> myExpectedReturnTypes = new HashMap<>();
private final Function<? super PsiElement, ? extends PsiClass> myInsideConstructorOfClass = entry -> findInsideConstructorClass(entry);
private final Map<PsiElement, PsiClass> myInsideConstructorOfClassCache = new HashMap<>(); // null value means "cached but no corresponding ctr found"
private final Function<? super PsiElement, ? extends PsiMethod> mySurroundingConstructor = entry -> findSurroundingConstructor(entry);
private final Map<PsiElement, PsiMethod> myInsideConstructorOfClassCache = new HashMap<>(); // null value means "cached but no corresponding ctr found"
@NotNull
protected PsiResolveHelper getResolveHelper(@NotNull Project project) {
@@ -109,30 +109,17 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
return myHolder.getProject();
}
// element -> class inside which there is a constructor inside which this element is contained
private PsiClass findInsideConstructorClass(@NotNull PsiElement entry) {
PsiClass result = null;
PsiElement parent;
// element -> a constructor inside which this element is contained
private PsiMethod findSurroundingConstructor(@NotNull PsiElement entry) {
PsiMethod result = null;
PsiElement element;
for (element = entry; element != null && !(element instanceof PsiFile); element = parent) {
result = myInsideConstructorOfClassCache.get(entry);
if (result != null || myInsideConstructorOfClassCache.containsKey(entry)) {
return result;
for (element = entry; element != null && !(element instanceof PsiFile); element = element.getParent()) {
result = myInsideConstructorOfClassCache.get(element);
if (result != null || myInsideConstructorOfClassCache.containsKey(element)) {
break;
}
parent = element.getParent();
if (parent instanceof PsiExpressionStatement) {
PsiElement p2 = parent.getParent();
if (p2 instanceof PsiCodeBlock) {
PsiElement p3 = p2.getParent();
if (p3 instanceof PsiMethod && ((PsiMethod)p3).isConstructor()) {
PsiElement p4 = p3.getParent();
if (p4 instanceof PsiClass) {
result = (PsiClass)p4;
}
}
}
}
if (result != null) {
if (element instanceof PsiMethod method && method.isConstructor()) {
result = method;
break;
}
}
@@ -1342,7 +1329,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
add(HighlightUtil.checkRestrictedIdentifierReference(ref, (PsiClass)resolved, myLanguageLevel));
}
if (!hasErrorResults()) {
add(HighlightUtil.checkMemberReferencedBeforeConstructorCalled(ref, resolved, myFile, myInsideConstructorOfClass));
add(HighlightUtil.checkMemberReferencedBeforeConstructorCalled(ref, resolved, mySurroundingConstructor));
}
return result;
@@ -1786,7 +1773,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!(expr.getParent() instanceof PsiReceiverParameter)) {
add(HighlightUtil.checkThisOrSuperExpressionInIllegalContext(expr, expr.getQualifier(), myLanguageLevel));
if (!hasErrorResults()) {
add(HighlightUtil.checkMemberReferencedBeforeConstructorCalled(expr, null, myFile, myInsideConstructorOfClass));
add(HighlightUtil.checkMemberReferencedBeforeConstructorCalled(expr, null, mySurroundingConstructor));
}
if (!hasErrorResults()) visitExpression(expr);
}

View File

@@ -1,5 +1,14 @@
import java.util.List;
class A {
A() {}
int i;
A() {
<error descr="Cannot reference 'this' before supertype constructor has been called">this</error>.i++; // Error
<error descr="Cannot reference 'this' before supertype constructor has been called">this</error>.hashCode(); // Error
System.out.print(<error descr="Cannot reference 'this' before supertype constructor has been called">this</error>); // Error
super();
}
A(int i) {}
}
@@ -41,4 +50,129 @@ class B extends A {
<error descr="Call to 'this()' only allowed in constructor body">this()</error>;
}
}
class D {
int i;
}
class E extends D {
E() {
<error descr="Cannot reference 'D.i' before supertype constructor has been called">super.i</error>++; // Error
super();
}
}
class F {
int i;
F() {
<error descr="Cannot reference 'F.i' before supertype constructor has been called">i</error>++; // Error
<error descr="Cannot reference 'Object.hashCode()' before supertype constructor has been called">hashCode</error>(); // Error
super();
}
}
class G {
int b;
class C {
int c;
C() {
G.this.b++; // Allowed - enclosing instance
<error descr="Cannot reference 'C.this' before supertype constructor has been called">C.this</error>.c++; // Error - same instance
super();
}
}
}
class Outer {
void hello() {
System.out.println("Hello");
}
class Inner {
Inner() {
hello(); // Allowed - enclosing instance method
super();
}
}
}
class Outer2 {
class Inner {
}
Outer2() {
new <error descr="Cannot reference 'Inner' before supertype constructor has been called">Inner</error>(); // Error - 'this' is enclosing instance
super();
}
}
class X {
class S {
}
X() {
var tmp = new <error descr="Cannot reference 'S' before supertype constructor has been called">S</error>() { }; // Error
super();
}
}
class O {
class S {
}
class U {
U() {
var tmp = new S() { }; // Allowed
super();
}
}
}
class Y {
Y(Object o) {
if (o == null) throw new NullPointerException();
super();
}
}
class Z<T> extends Y {
Z() {
super(<error descr="Cannot reference 'this' before supertype constructor has been called">this</error>); // Error - refers to 'this'
}
Z(List<?> list) {
super((T)list.get(0)); // Allowed - refers to 'T' but not 'this'
}
}
record R(int x, int y) {
R(int x, int y, int z) {
if (z > 1000) throw new IllegalArgumentException();
this(x, y);
}
}
enum EE {
A, B;
EE() {
System.out.println(1);
this(1);
}
EE(int i) {}
}