Skip to content

Commit

Permalink
[sealed types] ECJ accepts a cast from a disjoint interface to a seal…
Browse files Browse the repository at this point in the history
…ed interface(#2634)

* Fixes #2595
  • Loading branch information
srikanth-sankaran authored Jun 26, 2024
1 parent 7e0c89e commit 978b393
Show file tree
Hide file tree
Showing 6 changed files with 275 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,8 @@ public final boolean checkCastTypesCompatibility(Scope scope, TypeBinding castTy
if (match != null) {
return checkUnsafeCast(scope, castType, interfaceType, match, true);
}
if (((ReferenceBinding) castType).isDisjointFrom(interfaceType))
return false;
if (use15specifics) {
checkUnsafeCast(scope, castType, expressionType, null /*no match*/, true);
// ensure there is no collision between both interfaces: i.e. I1 extends List<String>, I2 extends List<Object>
Expand Down Expand Up @@ -504,7 +506,7 @@ public final boolean checkCastTypesCompatibility(Scope scope, TypeBinding castTy
if (match != null) {
return checkUnsafeCast(scope, castType, expressionType, match, true);
}
if (((ReferenceBinding) castType).isFinal()) {
if (((ReferenceBinding) castType).isDisjointFrom((ReferenceBinding) expressionType)) {
// no subclass for castType, thus compile-time check is invalid
return false;
}
Expand Down Expand Up @@ -554,8 +556,8 @@ public final boolean checkCastTypesCompatibility(Scope scope, TypeBinding castTy
if (match != null) {
return checkUnsafeCast(scope, castType, expressionType, match, false);
}
// unless final a subclass may implement the interface ==> no check at compile time
if (refExprType.isFinal()) {
// unless final, a subclass may implement the interface ==> no check at compile time
if (refExprType.isDisjointFrom((ReferenceBinding) castType)) {
return false;
}
tagAsNeedCheckCast();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.IntPredicate;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
Expand Down Expand Up @@ -350,8 +348,8 @@ public boolean visit(TNode node) {
availableTypes.add(child.type);
}
}
if (node.type instanceof ReferenceBinding && ((ReferenceBinding)node.type).isSealed()) {
List<ReferenceBinding> allAllowedTypes = getAllPermittedTypes((ReferenceBinding) node.type);
if (node.type instanceof ReferenceBinding ref && ref.isSealed()) {
List<ReferenceBinding> allAllowedTypes = ref.getAllEnumerableReferenceTypes();
this.covers &= isExhaustiveWithCaseTypes(allAllowedTypes, availableTypes);
return this.covers;
}
Expand Down Expand Up @@ -1375,7 +1373,7 @@ private boolean checkAndFlagDefaultSealed(BlockScope skope, CompilerOptions comp
return checkAndFlagDefaultRecord(skope, compilerOptions, ref);
}
if (!ref.isSealed()) return false;
if (!isExhaustiveWithCaseTypes(getAllPermittedTypes(ref), this.caseLabelElementTypes)) {
if (!isExhaustiveWithCaseTypes(ref.getAllEnumerableReferenceTypes(), this.caseLabelElementTypes)) {
if (this instanceof SwitchExpression) // non-exhaustive switch expressions will be flagged later.
return false;
skope.problemReporter().enhancedSwitchMissingDefaultCase(this.expression);
Expand All @@ -1384,25 +1382,6 @@ private boolean checkAndFlagDefaultSealed(BlockScope skope, CompilerOptions comp
this.switchBits |= SwitchStatement.Exhaustive;
return false;
}
List<ReferenceBinding> getAllPermittedTypes(ReferenceBinding ref) {
if (!ref.isSealed())
return new ArrayList<>(0);

Set<ReferenceBinding> permSet = new HashSet<>(Arrays.asList(ref.permittedTypes()));
if (ref.isClass() && (!ref.isAbstract()))
permSet.add(ref);
Set<ReferenceBinding> oldSet = new HashSet<>(permSet);
do {
for (ReferenceBinding type : permSet) {
oldSet.addAll(Arrays.asList(type.permittedTypes()));
}
Set<ReferenceBinding> tmp = oldSet;
oldSet = permSet;
permSet = tmp;
} while (oldSet.size() != permSet.size());
return Arrays.asList(permSet.toArray(new ReferenceBinding[0]));
}

private boolean checkAndFlagDefaultRecord(BlockScope skope, CompilerOptions compilerOptions, ReferenceBinding ref) {
RecordComponentBinding[] comps = ref.components();
List<ReferenceBinding> allallowedTypes = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@
package org.eclipse.jdt.internal.compiler.lookup;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.LambdaExpression;
Expand Down Expand Up @@ -1558,6 +1562,7 @@ public final boolean isNonSealed() {
/**
* Answer true if the receiver has sealed modifier
*/
@Override
public boolean isSealed() {
return (this.modifiers & ExtraCompilerModifiers.AccSealed) != 0;
}
Expand Down Expand Up @@ -2544,6 +2549,89 @@ public boolean hasEnclosingInstanceContext() {
return !enclosingMethod.isStatic();
return false;
}

@Override
public List<ReferenceBinding> getAllEnumerableReferenceTypes() {
if (!isSealed())
return Collections.emptyList();

Set<ReferenceBinding> permSet = new HashSet<>(Arrays.asList(permittedTypes()));
if (isClass() && (!isAbstract()))
permSet.add(this);
Set<ReferenceBinding> oldSet = new HashSet<>(permSet);
do {
for (ReferenceBinding type : permSet) {
oldSet.addAll(Arrays.asList(type.permittedTypes()));
}
Set<ReferenceBinding> tmp = oldSet;
oldSet = permSet;
permSet = tmp;
} while (oldSet.size() != permSet.size());
return Arrays.asList(permSet.toArray(new ReferenceBinding[0]));
}

// 5.1.6.1 Allowed Narrowing Reference Conversion
public boolean isDisjointFrom(ReferenceBinding that) {
if (this.isInterface()) {
if (that.isInterface()) {
/* • An interface named I is disjoint from another interface named J if (i) it is not that case that I <: J, and (ii) it is not the case that J <: I, and
* (iii) one of the following cases applies:
– I is sealed, and all of the permitted direct subclasses and subinterfaces of I are disjoint from J.
– J is sealed, and I is disjoint from all the permitted direct subclasses and subinterfaces of J.
*/
if (this.findSuperTypeOriginatingFrom(that) != null || that.findSuperTypeOriginatingFrom(this) != null)
return false;
if (this.isSealed()) {
for (ReferenceBinding directSubType : this.permittedTypes()) {
if (!directSubType.isDisjointFrom(that))
return false;
}
return true;
}
if (that.isSealed()) {
for (ReferenceBinding directSubType : that.permittedTypes()) {
if (!this.isDisjointFrom(directSubType))
return false;
}
return true;
}
return false;
} else {
// • An interface named I is disjoint from a class named C if C is disjoint from I.
return that.isDisjointFrom(this);
}
} else {
if (that.isInterface()) {
/* • A class named C is disjoint from an interface named I if (i) it is not the case that C <: I, and (ii) one of the following cases applies:
– C is final.
– C is sealed, and all of the permitted direct subclasses of C are disjoint from I.
– C is freely extensible (§8.1.1.2), and I is sealed, and C is disjoint from all of the permitted direct subclasses and subinterfaces of I
*/
if (this.findSuperTypeOriginatingFrom(that) != null)
return false;
if (this.isFinal())
return true;
if (this.isSealed()) {
for (ReferenceBinding directSubclass : this.permittedTypes()) {
if (!directSubclass.isDisjointFrom(that))
return false;
}
return true;
}
if (that.isSealed()) {
for (ReferenceBinding directSubType : that.permittedTypes()) {
if (!this.isDisjointFrom(directSubType))
return false;
}
return true;
}
return false;
} else {
// • A class named C is disjoint from another class named D if (i) it is not the case that C <: D, and (ii) it is not the case that D <: C.
return this.findSuperTypeOriginatingFrom(that) == null && that.findSuperTypeOriginatingFrom(this) == null;
}
}
}
static class InvalidBindingException extends Exception {
private static final long serialVersionUID = 1L;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
Expand Down Expand Up @@ -1806,4 +1807,12 @@ public boolean isNonDenotable() {
return false;
}

public boolean isSealed() {
return false;
}

public List<ReferenceBinding> getAllEnumerableReferenceTypes() {
return Collections.emptyList();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5077,17 +5077,37 @@ public void test139() {
}
//check final modifier
public void test140() {
this.runConformTest(
new String[] {
"X.java",
"public enum X {\n" +
" PLUS {/*ANONYMOUS*/}, MINUS;\n" +
" void bar(X x) {\n" +
" Runnable r = (Runnable)x;\n" +
" }\n" +
"}", // =================
},
"");
if (this.complianceLevel < ClassFileConstants.JDK17) {
this.runConformTest(
new String[] {
"X.java",
"public enum X {\n" +
" PLUS {/*ANONYMOUS*/}, MINUS;\n" +
" void bar(X x) {\n" +
" Runnable r = (Runnable)x;\n" +
" }\n" +
"}", // =================
},
"");
} else {
// An enum class E is implicitly sealed if its declaration contains at least one enum constant that has a class body
this.runNegativeTest(
new String[] {
"X.java",
"public enum X {\n" +
" PLUS {/*ANONYMOUS*/}, MINUS;\n" +
" void bar(X x) {\n" +
" Runnable r = (Runnable)x;\n" +
" }\n" +
"}", // =================
},
"----------\n" +
"1. ERROR in X.java (at line 4)\n" +
" Runnable r = (Runnable)x;\n" +
" ^^^^^^^^^^^\n" +
"Cannot cast from X to Runnable\n" +
"----------\n");
}
}
//check final modifier
public void test141() {
Expand Down
Loading

0 comments on commit 978b393

Please sign in to comment.