8246778: Compiler implementation for Sealed Classes (Second Preview)
Co-authored-by: Vicente Romero <vromero@openjdk.org> Co-authored-by: Harold Seigel <hseigel@openjdk.org> Reviewed-by: lfoltan, mchung, alanb, mcimadamore, chegar
This commit is contained in:
parent
09707dd4f2
commit
637b0c64b0
src
hotspot/share/prims
java.base/share
jdk.compiler/share/classes/com/sun/tools/javac
test
hotspot/jtreg/runtime/sealedClasses
jdk/java/lang/reflect/sealed_classes
langtools/tools/javac/sealed
@ -2128,16 +2128,32 @@ JVM_ENTRY(jobjectArray, JVM_GetPermittedSubclasses(JNIEnv* env, jclass current))
|
||||
JvmtiVMObjectAllocEventCollector oam;
|
||||
Array<u2>* subclasses = ik->permitted_subclasses();
|
||||
int length = subclasses == NULL ? 0 : subclasses->length();
|
||||
objArrayOop r = oopFactory::new_objArray(SystemDictionary::String_klass(),
|
||||
objArrayOop r = oopFactory::new_objArray(SystemDictionary::Class_klass(),
|
||||
length, CHECK_NULL);
|
||||
objArrayHandle result(THREAD, r);
|
||||
int count = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
int cp_index = subclasses->at(i);
|
||||
// This returns <package-name>/<class-name>.
|
||||
Symbol* klass_name = ik->constants()->klass_name_at(cp_index);
|
||||
assert(klass_name != NULL, "Unexpected null klass_name");
|
||||
Handle perm_subtype_h = java_lang_String::create_from_symbol(klass_name, CHECK_NULL);
|
||||
result->obj_at_put(i, perm_subtype_h());
|
||||
Klass* k = ik->constants()->klass_at(cp_index, THREAD);
|
||||
if (HAS_PENDING_EXCEPTION) {
|
||||
if (PENDING_EXCEPTION->is_a(SystemDictionary::VirtualMachineError_klass())) {
|
||||
return NULL; // propagate VMEs
|
||||
}
|
||||
CLEAR_PENDING_EXCEPTION;
|
||||
continue;
|
||||
}
|
||||
if (k->is_instance_klass()) {
|
||||
result->obj_at_put(count++, k->java_mirror());
|
||||
}
|
||||
}
|
||||
if (count < length) {
|
||||
objArrayOop r2 = oopFactory::new_objArray(SystemDictionary::Class_klass(),
|
||||
count, CHECK_NULL);
|
||||
objArrayHandle result2(THREAD, r2);
|
||||
for (int i = 0; i < count; i++) {
|
||||
result2->obj_at_put(i, result->obj_at(i));
|
||||
}
|
||||
return (jobjectArray)JNIHandles::make_local(THREAD, result2());
|
||||
}
|
||||
return (jobjectArray)JNIHandles::make_local(THREAD, result());
|
||||
}
|
||||
|
@ -58,12 +58,14 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jdk.internal.loader.BootLoader;
|
||||
@ -200,8 +202,6 @@ public final class Class<T> implements java.io.Serializable,
|
||||
private static final int ENUM = 0x00004000;
|
||||
private static final int SYNTHETIC = 0x00001000;
|
||||
|
||||
private static final ClassDesc[] EMPTY_CLASS_DESC_ARRAY = new ClassDesc[0];
|
||||
|
||||
private static native void registerNatives();
|
||||
static {
|
||||
registerNatives();
|
||||
@ -3020,6 +3020,37 @@ public final class Class<T> implements java.io.Serializable,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if a client loaded in ClassLoader ccl is allowed to access the provided
|
||||
* classes under the current package access policy. If access is denied,
|
||||
* throw a SecurityException.
|
||||
*
|
||||
* NOTE: this method should only be called if a SecurityManager is active
|
||||
* classes must be non-empty
|
||||
* all classes provided must be loaded by the same ClassLoader
|
||||
* NOTE: this method does not support Proxy classes
|
||||
*/
|
||||
private static void checkPackageAccessForPermittedSubclasses(SecurityManager sm,
|
||||
final ClassLoader ccl, Class<?>[] subClasses) {
|
||||
final ClassLoader cl = subClasses[0].getClassLoader0();
|
||||
|
||||
if (ReflectUtil.needsPackageAccessCheck(ccl, cl)) {
|
||||
Set<String> packages = new HashSet<>();
|
||||
|
||||
for (Class<?> c : subClasses) {
|
||||
if (Proxy.isProxyClass(c))
|
||||
throw new InternalError("a permitted subclass should not be a proxy class: " + c);
|
||||
String pkg = c.getPackageName();
|
||||
if (pkg != null && !pkg.isEmpty()) {
|
||||
packages.add(pkg);
|
||||
}
|
||||
}
|
||||
for (String pkg : packages) {
|
||||
sm.checkPackageAccess(pkg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a package name prefix if the name is not absolute Remove leading "/"
|
||||
* if name is absolute
|
||||
@ -4357,47 +4388,87 @@ public final class Class<T> implements java.io.Serializable,
|
||||
* may be removed in a future release, or upgraded to permanent
|
||||
* features of the Java language.}
|
||||
*
|
||||
* Returns an array containing {@code ClassDesc} objects representing all the
|
||||
* direct subclasses or direct implementation classes permitted to extend or
|
||||
* Returns an array containing {@code Class} objects representing the
|
||||
* direct subinterfaces or subclasses permitted to extend or
|
||||
* implement this class or interface if it is sealed. The order of such elements
|
||||
* is unspecified. If this {@code Class} object represents a primitive type,
|
||||
* {@code void}, an array type, or a class or interface that is not sealed,
|
||||
* an empty array is returned.
|
||||
*
|
||||
* @return an array of class descriptors of all the permitted subclasses of this class or interface
|
||||
* For each class or interface {@code C} which is recorded as a permitted
|
||||
* direct subinterface or subclass of this class or interface,
|
||||
* this method attempts to obtain the {@code Class}
|
||||
* object for {@code C} (using {@linkplain #getClassLoader() the defining class
|
||||
* loader} of the current {@code Class} object).
|
||||
* The {@code Class} objects which can be obtained and which are direct
|
||||
* subinterfaces or subclasses of this class or interface,
|
||||
* are indicated by elements of the returned array. If a {@code Class} object
|
||||
* cannot be obtained, it is silently ignored, and not included in the result
|
||||
* array.
|
||||
*
|
||||
* @return an array of {@code Class} objects of the permitted subclasses of this class or interface
|
||||
*
|
||||
* @throws SecurityException
|
||||
* If a security manager, <i>s</i>, is present and the caller's
|
||||
* class loader is not the same as or an ancestor of the class
|
||||
* loader for that returned class and invocation of {@link
|
||||
* SecurityManager#checkPackageAccess s.checkPackageAccess()}
|
||||
* denies access to the package of any class in the returned array.
|
||||
*
|
||||
* @jls 8.1 Class Declarations
|
||||
* @jls 9.1 Interface Declarations
|
||||
* @since 15
|
||||
*/
|
||||
@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.SEALED_CLASSES, essentialAPI=false)
|
||||
public ClassDesc[] permittedSubclasses() {
|
||||
String[] subclassNames;
|
||||
if (isArray() || isPrimitive() || (subclassNames = getPermittedSubclasses0()).length == 0) {
|
||||
return EMPTY_CLASS_DESC_ARRAY;
|
||||
@CallerSensitive
|
||||
public Class<?>[] getPermittedSubclasses() {
|
||||
Class<?>[] subClasses;
|
||||
if (isArray() || isPrimitive() || (subClasses = getPermittedSubclasses0()).length == 0) {
|
||||
return EMPTY_CLASS_ARRAY;
|
||||
}
|
||||
ClassDesc[] constants = new ClassDesc[subclassNames.length];
|
||||
int i = 0;
|
||||
for (String subclassName : subclassNames) {
|
||||
try {
|
||||
constants[i++] = ClassDesc.of(subclassName.replace('/', '.'));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
throw new InternalError("Invalid type in permitted subclasses information: " + subclassName, iae);
|
||||
if (subClasses.length > 0) {
|
||||
if (Arrays.stream(subClasses).anyMatch(c -> !isDirectSubType(c))) {
|
||||
subClasses = Arrays.stream(subClasses)
|
||||
.filter(this::isDirectSubType)
|
||||
.toArray(s -> new Class<?>[s]);
|
||||
}
|
||||
}
|
||||
return constants;
|
||||
if (subClasses.length > 0) {
|
||||
// If we return some classes we need a security check:
|
||||
SecurityManager sm = System.getSecurityManager();
|
||||
if (sm != null) {
|
||||
checkPackageAccessForPermittedSubclasses(sm,
|
||||
ClassLoader.getClassLoader(Reflection.getCallerClass()),
|
||||
subClasses);
|
||||
}
|
||||
}
|
||||
return subClasses;
|
||||
}
|
||||
|
||||
private boolean isDirectSubType(Class<?> c) {
|
||||
if (isInterface()) {
|
||||
for (Class<?> i : c.getInterfaces(/* cloneArray */ false)) {
|
||||
if (i == this) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return c.getSuperclass() == this;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* * {@preview Associated with sealed classes, a preview feature of the Java language.
|
||||
* {@preview Associated with sealed classes, a preview feature of the Java language.
|
||||
*
|
||||
* This method is associated with <i>sealed classes</i>, a preview
|
||||
* feature of the Java language. Preview features
|
||||
* may be removed in a future release, or upgraded to permanent
|
||||
* features of the Java language.}
|
||||
*
|
||||
* Returns {@code true} if and only if this {@code Class} object represents a sealed class or interface.
|
||||
* If this {@code Class} object represents a primitive type, {@code void}, or an array type, this method returns
|
||||
* Returns {@code true} if and only if this {@code Class} object represents
|
||||
* a sealed class or interface. If this {@code Class} object represents a
|
||||
* primitive type, {@code void}, or an array type, this method returns
|
||||
* {@code false}.
|
||||
*
|
||||
* @return {@code true} if and only if this {@code Class} object represents a sealed class or interface.
|
||||
@ -4412,8 +4483,8 @@ public final class Class<T> implements java.io.Serializable,
|
||||
if (isArray() || isPrimitive()) {
|
||||
return false;
|
||||
}
|
||||
return permittedSubclasses().length != 0;
|
||||
return getPermittedSubclasses().length != 0;
|
||||
}
|
||||
|
||||
private native String[] getPermittedSubclasses0();
|
||||
private native Class<?>[] getPermittedSubclasses0();
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ static JNINativeMethod methods[] = {
|
||||
{"getNestMembers0", "()[" CLS, (void *)&JVM_GetNestMembers},
|
||||
{"getRecordComponents0", "()[" RC, (void *)&JVM_GetRecordComponents},
|
||||
{"isRecord0", "()Z", (void *)&JVM_IsRecord},
|
||||
{"getPermittedSubclasses0", "()[" STR, (void *)&JVM_GetPermittedSubclasses},
|
||||
{"getPermittedSubclasses0", "()[" CLS, (void *)&JVM_GetPermittedSubclasses},
|
||||
};
|
||||
|
||||
#undef OBJ
|
||||
|
@ -1629,32 +1629,69 @@ public class Types {
|
||||
}
|
||||
|
||||
/**
|
||||
* Is t is castable to s?<br>
|
||||
* Is t castable to s?<br>
|
||||
* s is assumed to be an erased type.<br>
|
||||
* (not defined for Method and ForAll types).
|
||||
*/
|
||||
public boolean isCastable(Type t, Type s, Warner warn) {
|
||||
// if same type
|
||||
if (t == s)
|
||||
return true;
|
||||
// if one of the types is primitive
|
||||
if (t.isPrimitive() != s.isPrimitive()) {
|
||||
t = skipTypeVars(t, false);
|
||||
return (isConvertible(t, s, warn)
|
||||
|| (s.isPrimitive() &&
|
||||
isSubtype(boxedClass(s).type, t)));
|
||||
}
|
||||
boolean result;
|
||||
if (warn != warnStack.head) {
|
||||
try {
|
||||
warnStack = warnStack.prepend(warn);
|
||||
checkUnsafeVarargsConversion(t, s, warn);
|
||||
return isCastable.visit(t,s);
|
||||
result = isCastable.visit(t,s);
|
||||
} finally {
|
||||
warnStack = warnStack.tail;
|
||||
}
|
||||
} else {
|
||||
return isCastable.visit(t,s);
|
||||
result = isCastable.visit(t,s);
|
||||
}
|
||||
if (result && (t.tsym.isSealed() || s.tsym.isSealed())) {
|
||||
return (t.isCompound() || s.isCompound()) ?
|
||||
false :
|
||||
!areDisjoint((ClassSymbol)t.tsym, (ClassSymbol)s.tsym);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// where
|
||||
private boolean areDisjoint(ClassSymbol ts, ClassSymbol ss) {
|
||||
if (isSubtype(ts.type, ss.type)) {
|
||||
return false;
|
||||
}
|
||||
// if both are classes or both are interfaces, shortcut
|
||||
if (ts.isInterface() == ss.isInterface() && isSubtype(ss.type, ts.type)) {
|
||||
return false;
|
||||
}
|
||||
if (ts.isInterface() && !ss.isInterface()) {
|
||||
/* so ts is interface but ss is a class
|
||||
* an interface is disjoint from a class if the class is disjoint form the interface
|
||||
*/
|
||||
return areDisjoint(ss, ts);
|
||||
}
|
||||
// a final class that is not subtype of ss is disjoint
|
||||
if (!ts.isInterface() && ts.isFinal()) {
|
||||
return true;
|
||||
}
|
||||
// if at least one is sealed
|
||||
if (ts.isSealed() || ss.isSealed()) {
|
||||
// permitted subtypes have to be disjoint with the other symbol
|
||||
ClassSymbol sealedOne = ts.isSealed() ? ts : ss;
|
||||
ClassSymbol other = sealedOne == ts ? ss : ts;
|
||||
return sealedOne.permitted.stream().allMatch(sym -> areDisjoint((ClassSymbol)sym, other));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private TypeRelation isCastable = new TypeRelation() {
|
||||
|
||||
public Boolean visitType(Type t, Type s) {
|
||||
|
@ -1303,7 +1303,10 @@ public class Check {
|
||||
SEALED | NON_SEALED)
|
||||
&& checkDisjoint(pos, flags,
|
||||
SEALED,
|
||||
FINAL | NON_SEALED)) {
|
||||
FINAL | NON_SEALED)
|
||||
&& checkDisjoint(pos, flags,
|
||||
SEALED,
|
||||
ANNOTATION)) {
|
||||
// skip
|
||||
}
|
||||
return flags & (mask | ~ExtendedStandardFlags) | implicit;
|
||||
|
@ -4264,11 +4264,19 @@ public class JavacParser implements Parser {
|
||||
private boolean allowedAfterSealedOrNonSealed(Token next, boolean local, boolean currentIsNonSealed) {
|
||||
return local ?
|
||||
switch (next.kind) {
|
||||
case MONKEYS_AT, ABSTRACT, FINAL, STRICTFP, CLASS, INTERFACE, ENUM -> true;
|
||||
case MONKEYS_AT -> {
|
||||
Token afterNext = S.token(2);
|
||||
yield afterNext.kind != INTERFACE || currentIsNonSealed;
|
||||
}
|
||||
case ABSTRACT, FINAL, STRICTFP, CLASS, INTERFACE, ENUM -> true;
|
||||
default -> false;
|
||||
} :
|
||||
switch (next.kind) {
|
||||
case MONKEYS_AT, PUBLIC, PROTECTED, PRIVATE, ABSTRACT, STATIC, FINAL, STRICTFP, CLASS, INTERFACE, ENUM -> true;
|
||||
case MONKEYS_AT -> {
|
||||
Token afterNext = S.token(2);
|
||||
yield afterNext.kind != INTERFACE || currentIsNonSealed;
|
||||
}
|
||||
case PUBLIC, PROTECTED, PRIVATE, ABSTRACT, STATIC, FINAL, STRICTFP, CLASS, INTERFACE, ENUM -> true;
|
||||
case IDENTIFIER -> isNonSealedIdentifier(next, currentIsNonSealed ? 3 : 1) || next.name() == names.sealed;
|
||||
default -> false;
|
||||
};
|
||||
|
@ -21,11 +21,10 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
// This class has entries in its PermittedSubclasses attribute that do not exist.
|
||||
// Test that this does not prevent JVM_GetPermittedSubclasses() from returning
|
||||
// their names.
|
||||
// This class has an entry in its PermittedSubclasses attribute that does not exist.
|
||||
// Test that JVM_GetPermittedSubclasses() only returns the existing class.
|
||||
//
|
||||
// sealed class NoLoadSubclasses permits iDontExist, I/Dont/Exist/Either { }
|
||||
// sealed class NoLoadSubclasses permits ExistingClassFile, I/Dont/Exist/Either { }
|
||||
//
|
||||
class NoLoadSubclasses {
|
||||
0xCAFEBABE;
|
||||
@ -47,7 +46,7 @@ class NoLoadSubclasses {
|
||||
Utf8 "NoLoadSubclasses.java"; // #12 at 0x73
|
||||
Utf8 "PermittedSubclasses"; // #13 at 0x89
|
||||
class #15; // #14 at 0x9D
|
||||
Utf8 "iDontExist"; // #15 at 0xA0
|
||||
Utf8 "ExistingClassFile"; // #15 at 0xA0
|
||||
class #17; // #16 at 0xAA
|
||||
Utf8 "I/Dont/Exist/Either"; // #17 at 0xAD
|
||||
} // Constant Pool
|
||||
@ -99,7 +98,70 @@ class NoLoadSubclasses {
|
||||
} // Attributes
|
||||
} // end class NoLoadSubclasses
|
||||
|
||||
// class ExistingClassFile extends NoLoadSubclasses { }
|
||||
//
|
||||
class ExistingClassFile {
|
||||
0xCAFEBABE;
|
||||
0; // minor version
|
||||
55; // version
|
||||
[] { // Constant Pool
|
||||
; // first element is empty
|
||||
Method #3 #10; // #1
|
||||
class #11; // #2
|
||||
class #12; // #3
|
||||
Utf8 "<init>"; // #4
|
||||
Utf8 "()V"; // #5
|
||||
Utf8 "Code"; // #6
|
||||
Utf8 "LineNumberTable"; // #7
|
||||
Utf8 "SourceFile"; // #8
|
||||
Utf8 "ExistingClassFile.java"; // #9
|
||||
NameAndType #4 #5; // #10
|
||||
Utf8 "ExistingClassFile"; // #11
|
||||
Utf8 "NoLoadSubclasses"; // #12
|
||||
} // Constant Pool
|
||||
|
||||
0x0021; // access
|
||||
#2;// this_cpx
|
||||
#3;// super_cpx
|
||||
|
||||
[] { // Interfaces
|
||||
} // Interfaces
|
||||
|
||||
[] { // Fields
|
||||
} // Fields
|
||||
|
||||
[] { // Methods
|
||||
{ // method
|
||||
0x0001; // access
|
||||
#4; // name_index
|
||||
#5; // descriptor_index
|
||||
[] { // Attributes
|
||||
Attr(#6) { // Code
|
||||
1; // max_stack
|
||||
1; // max_locals
|
||||
Bytes[]{
|
||||
0x2AB70001B1;
|
||||
}
|
||||
[] { // Traps
|
||||
} // end Traps
|
||||
[] { // Attributes
|
||||
Attr(#7) { // LineNumberTable
|
||||
[] { // line_number_table
|
||||
0 1;
|
||||
}
|
||||
} // end LineNumberTable
|
||||
} // Attributes
|
||||
} // end Code
|
||||
} // Attributes
|
||||
}
|
||||
} // Methods
|
||||
|
||||
[] { // Attributes
|
||||
Attr(#8) { // SourceFile
|
||||
#9;
|
||||
} // end SourceFile
|
||||
} // Attributes
|
||||
} // end class ExistingClassFile
|
||||
|
||||
// This class contains an empty PermittedSubclasses attribute. Test that
|
||||
// this causes an exception to get thrown.
|
||||
|
@ -25,11 +25,12 @@
|
||||
* @test
|
||||
* @bug 8225056
|
||||
* @compile GetPermittedSubclasses.jcod
|
||||
* @compile --enable-preview -source ${jdk.version} noSubclass/BaseC.java noSubclass/BaseI.java noSubclass/Impl1.java
|
||||
* @compile --enable-preview -source ${jdk.version} noSubclass/Impl2.java
|
||||
* @compile --enable-preview -source ${jdk.version} GetPermittedSubclassesTest.java
|
||||
* @run main/othervm --enable-preview GetPermittedSubclassesTest
|
||||
*/
|
||||
|
||||
import java.lang.constant.ClassDesc;
|
||||
import java.util.ArrayList;
|
||||
|
||||
// Test Class GetPermittedSubtpes() and Class.isSealed() APIs.
|
||||
@ -50,11 +51,11 @@ public class GetPermittedSubclassesTest {
|
||||
final class Final4 {}
|
||||
|
||||
public static void testSealedInfo(Class<?> c, String[] expected) {
|
||||
Object[] permitted = c.permittedSubclasses();
|
||||
var permitted = c.getPermittedSubclasses();
|
||||
|
||||
if (permitted.length != expected.length) {
|
||||
throw new RuntimeException(
|
||||
"Unexpected number of permitted subclasses for: " + c.toString());
|
||||
"Unexpected number of permitted subclasses for: " + c.toString() + "(" + java.util.Arrays.asList(permitted));
|
||||
}
|
||||
|
||||
if (permitted.length > 0) {
|
||||
@ -65,7 +66,7 @@ public class GetPermittedSubclassesTest {
|
||||
// Create ArrayList of permitted subclasses class names.
|
||||
ArrayList<String> permittedNames = new ArrayList<String>();
|
||||
for (int i = 0; i < permitted.length; i++) {
|
||||
permittedNames.add(((ClassDesc)permitted[i]).descriptorString());
|
||||
permittedNames.add(permitted[i].getName());
|
||||
}
|
||||
|
||||
if (permittedNames.size() != expected.length) {
|
||||
@ -102,10 +103,11 @@ public class GetPermittedSubclassesTest {
|
||||
}
|
||||
|
||||
public static void main(String... args) throws Throwable {
|
||||
testSealedInfo(SealedI1.class, new String[] {"LGetPermittedSubclassesTest$NotSealed;",
|
||||
"LGetPermittedSubclassesTest$Sub1;",
|
||||
"LGetPermittedSubclassesTest$Extender;"});
|
||||
testSealedInfo(Sealed1.class, new String[] {"LGetPermittedSubclassesTest$Sub1;"});
|
||||
testSealedInfo(SealedI1.class, new String[] {"GetPermittedSubclassesTest$NotSealed",
|
||||
"GetPermittedSubclassesTest$Sub1",
|
||||
"GetPermittedSubclassesTest$Extender"});
|
||||
|
||||
testSealedInfo(Sealed1.class, new String[] {"GetPermittedSubclassesTest$Sub1"});
|
||||
testSealedInfo(Final4.class, new String[] { });
|
||||
testSealedInfo(NotSealed.class, new String[] { });
|
||||
|
||||
@ -115,8 +117,8 @@ public class GetPermittedSubclassesTest {
|
||||
// Test class with an empty PermittedSubclasses attribute.
|
||||
testBadSealedClass("NoSubclasses", "PermittedSubclasses attribute is empty");
|
||||
|
||||
// Test returning names of non-existing classes.
|
||||
testSealedInfo(NoLoadSubclasses.class, new String[]{"LiDontExist;", "LI/Dont/Exist/Either;"});
|
||||
// Test returning only names of existing classes.
|
||||
testSealedInfo(NoLoadSubclasses.class, new String[]{"ExistingClassFile" });
|
||||
|
||||
// Test that loading a class with a corrupted PermittedSubclasses attribute
|
||||
// causes a ClassFormatError.
|
||||
@ -134,5 +136,10 @@ public class GetPermittedSubclassesTest {
|
||||
// Test that loading a sealed class with an empty class name in its PermittedSubclasses
|
||||
// attribute causes a ClassFormatError.
|
||||
testBadSealedClass("EmptyPermittedSubclassEntry", "Illegal class name \"\" in class file");
|
||||
|
||||
//test type enumerated in the PermittedSubclasses attribute,
|
||||
//which are not direct subtypes of the current class are not returned:
|
||||
testSealedInfo(noSubclass.BaseC.class, new String[] {"noSubclass.ImplCIntermediate"});
|
||||
testSealedInfo(noSubclass.BaseI.class, new String[] {"noSubclass.ImplIIntermediateI", "noSubclass.ImplIIntermediateC"});
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package noSubclass;
|
||||
|
||||
public sealed class BaseC permits ImplC, ImplCIntermediate, ImplCIndirect {}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package noSubclass;
|
||||
|
||||
public sealed interface BaseI permits ImplII, ImplIIntermediateI, ImplIIndirectI, ImplIC, ImplIIntermediateC, ImplIIndirectC {}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package noSubclass;
|
||||
|
||||
final class ImplC extends BaseC {}
|
||||
non-sealed class ImplCIntermediate extends BaseC {}
|
||||
final class ImplCIndirect extends BaseC {}
|
||||
|
||||
non-sealed interface ImplII extends BaseI {}
|
||||
non-sealed interface ImplIIntermediateI extends BaseI {}
|
||||
non-sealed interface ImplIIndirectI extends ImplIIntermediateI, BaseI {}
|
||||
|
||||
final class ImplIC implements BaseI {}
|
||||
non-sealed class ImplIIntermediateC implements BaseI {}
|
||||
final class ImplIIndirectC extends ImplIIntermediateC implements BaseI {}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package noSubclass;
|
||||
|
||||
final class ImplC {}
|
||||
non-sealed class ImplCIntermediate extends BaseC {}
|
||||
final class ImplCIndirect extends ImplCIntermediate {}
|
||||
|
||||
interface ImplII {}
|
||||
non-sealed interface ImplIIntermediateI extends BaseI {}
|
||||
interface ImplIIndirectI extends ImplIIntermediateI {}
|
||||
|
||||
final class ImplIC {}
|
||||
non-sealed class ImplIIntermediateC implements BaseI {}
|
||||
final class ImplIIndirectC extends ImplIIntermediateC {}
|
@ -86,8 +86,8 @@ public class SealedClassesReflectionTest {
|
||||
public void testSealedClasses(Class<?> cls) {
|
||||
assertTrue(cls.isSealed());
|
||||
assertTrue(!Modifier.isFinal(cls.getModifiers()));
|
||||
assertTrue(cls.permittedSubclasses() != null);
|
||||
assertTrue(cls.permittedSubclasses().length > 0);
|
||||
assertTrue(cls.getPermittedSubclasses() != null);
|
||||
assertTrue(cls.getPermittedSubclasses().length > 0);
|
||||
}
|
||||
|
||||
@DataProvider(name = "notSealedClasses")
|
||||
@ -110,8 +110,8 @@ public class SealedClassesReflectionTest {
|
||||
@Test(dataProvider = "notSealedClasses")
|
||||
public void testNotSealedClasses(Class<?> cls) {
|
||||
assertTrue(!cls.isSealed());
|
||||
assertTrue(cls.permittedSubclasses() != null);
|
||||
assertTrue(cls.permittedSubclasses().length == 0);
|
||||
assertTrue(cls.getPermittedSubclasses() != null);
|
||||
assertTrue(cls.getPermittedSubclasses().length == 0);
|
||||
}
|
||||
|
||||
@DataProvider(name = "non_sealedClasses")
|
||||
@ -128,8 +128,8 @@ public class SealedClassesReflectionTest {
|
||||
assertTrue(!cls.isSealed());
|
||||
assertTrue(!Modifier.isFinal(cls.getModifiers()));
|
||||
assertTrue((cls.getSuperclass() != null && cls.getSuperclass().isSealed()) || Arrays.stream(cls.getInterfaces()).anyMatch(Class::isSealed));
|
||||
assertTrue(cls.permittedSubclasses() != null);
|
||||
assertTrue(cls.permittedSubclasses().length == 0);
|
||||
assertTrue(cls.getPermittedSubclasses() != null);
|
||||
assertTrue(cls.getPermittedSubclasses().length == 0);
|
||||
}
|
||||
|
||||
@DataProvider(name = "reflectionData")
|
||||
@ -215,10 +215,10 @@ public class SealedClassesReflectionTest {
|
||||
throws ReflectiveOperationException
|
||||
{
|
||||
assertTrue(sealedClass.isSealed());
|
||||
assertTrue(sealedClass.permittedSubclasses().length == numberOfSubclasses);
|
||||
assertTrue(sealedClass.getPermittedSubclasses().length == numberOfSubclasses);
|
||||
int i = 0;
|
||||
for (ClassDesc cd : sealedClass.permittedSubclasses()) {
|
||||
assertTrue(cd.displayName().equals(subclassDescriptors[i]), "expected: " + subclassDescriptors[i] + " found: " + cd.displayName());
|
||||
for (Class<?> cd : sealedClass.getPermittedSubclasses()) {
|
||||
assertTrue(cd.getName().equals(subclassDescriptors[i]), "expected: " + subclassDescriptors[i] + " found: " + cd.getName());
|
||||
i++;
|
||||
}
|
||||
i = 0;
|
||||
|
@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8246778
|
||||
* @summary Test that security checks occur for getPermittedSubclasses
|
||||
* @library /test/lib
|
||||
* @modules java.compiler
|
||||
* @build jdk.test.lib.compiler.CompilerUtils jdk.test.lib.compiler.ModuleInfoMaker TestSecurityManagerChecks
|
||||
* @run main/othervm --enable-preview TestSecurityManagerChecks named
|
||||
* @run main/othervm --enable-preview TestSecurityManagerChecks unnamed
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.module.Configuration;
|
||||
import java.lang.module.ModuleFinder;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jdk.test.lib.compiler.*;
|
||||
|
||||
public class TestSecurityManagerChecks {
|
||||
|
||||
private static final ClassLoader OBJECT_CL = null;
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
if ("named".equals(args[0])) {
|
||||
runNamedModuleTest();
|
||||
} else {
|
||||
runUnnamedModuleTest();
|
||||
}
|
||||
}
|
||||
|
||||
private static void runNamedModuleTest() throws Throwable {
|
||||
Path classes = compileNamedModuleTest();
|
||||
URL[] testClassPath = getTestClassPath();
|
||||
|
||||
//need to use a different ClassLoader to run the test, so that the checks are performed:
|
||||
ClassLoader testCL = new URLClassLoader(testClassPath, OBJECT_CL);
|
||||
testCL.loadClass("TestSecurityManagerChecks")
|
||||
.getDeclaredMethod("doRunNamedModuleTest", Path.class)
|
||||
.invoke(null, classes);
|
||||
}
|
||||
|
||||
public static void doRunNamedModuleTest(Path classes) throws Throwable {
|
||||
Configuration testConfig = ModuleLayer.boot()
|
||||
.configuration()
|
||||
.resolve(ModuleFinder.of(),
|
||||
ModuleFinder.of(classes),
|
||||
List.of("test"));
|
||||
ModuleLayer testLayer = ModuleLayer.boot()
|
||||
.defineModulesWithOneLoader(testConfig,
|
||||
OBJECT_CL);
|
||||
|
||||
// First get hold of the target classes before we enable security
|
||||
Class<?> sealed = Class.forName(testLayer.findModule("test").get(), "test.Base");
|
||||
|
||||
//try without a SecurityManager:
|
||||
checkPermittedSubclasses(sealed, "test.a.ImplA1",
|
||||
"test.a.ImplA2",
|
||||
"test.b.ImplB");
|
||||
|
||||
String[] denyPackageAccess = new String[1];
|
||||
int[] checkPackageAccessCallCount = new int[1];
|
||||
|
||||
//try with a SecurityManager:
|
||||
SecurityManager sm = new SecurityManager() {
|
||||
@Override
|
||||
public void checkPackageAccess(String pkg) {
|
||||
if (pkg.startsWith("test.")) {
|
||||
checkPackageAccessCallCount[0]++;
|
||||
}
|
||||
if (Objects.equals(denyPackageAccess[0], pkg)) {
|
||||
throw new SecurityException();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
System.setSecurityManager(sm);
|
||||
|
||||
denyPackageAccess[0] = "test";
|
||||
|
||||
//passes - does not return a class from package "test":
|
||||
checkPermittedSubclasses(sealed, "test.a.ImplA1",
|
||||
"test.a.ImplA2",
|
||||
"test.b.ImplB");
|
||||
|
||||
if (checkPackageAccessCallCount[0] != 2) {
|
||||
throw new AssertionError("Unexpected call count: " +
|
||||
checkPackageAccessCallCount[0]);
|
||||
}
|
||||
|
||||
denyPackageAccess[0] = "test.a";
|
||||
|
||||
try {
|
||||
sealed.getPermittedSubclasses();
|
||||
throw new Error("getPermittedSubclasses incorrectly succeeded for " +
|
||||
sealed.getName());
|
||||
} catch (SecurityException e) {
|
||||
System.out.println("OK - getPermittedSubclasses for " + sealed.getName() +
|
||||
" got expected exception: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Path compileNamedModuleTest() throws IOException {
|
||||
Path base = Paths.get(".", "named");
|
||||
Path src = base.resolve("src");
|
||||
Path classes = base.resolve("classes");
|
||||
|
||||
ModuleInfoMaker maker = new ModuleInfoMaker(src);
|
||||
maker.writeJavaFiles("test",
|
||||
"module test {}",
|
||||
"package test; public sealed interface Base permits test.a.ImplA1, test.a.ImplA2, test.b.ImplB, test.c.ImplC {}",
|
||||
"package test.a; public final class ImplA1 implements test.Base {}",
|
||||
"package test.a; public final class ImplA2 implements test.Base {}",
|
||||
"package test.b; public final class ImplB implements test.Base {}",
|
||||
"package test.c; public final class ImplC implements test.Base {}"
|
||||
);
|
||||
|
||||
if (!CompilerUtils.compile(src, classes.resolve("test"), "--enable-preview", "-source", System.getProperty("java.specification.version"))) {
|
||||
throw new AssertionError("Compilation didn't succeed!");
|
||||
}
|
||||
|
||||
Files.delete(classes.resolve("test").resolve("test").resolve("c").resolve("ImplC.class"));
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
private static void runUnnamedModuleTest() throws Throwable {
|
||||
Path classes = compileUnnamedModuleTest();
|
||||
URL[] testClassPath = getTestClassPath();
|
||||
|
||||
//need to use a different ClassLoader to run the test, so that the checks are performed:
|
||||
ClassLoader testCL = new URLClassLoader(testClassPath, OBJECT_CL);
|
||||
testCL.loadClass("TestSecurityManagerChecks")
|
||||
.getDeclaredMethod("doRunUnnamedModuleTest", Path.class)
|
||||
.invoke(null, classes);
|
||||
}
|
||||
|
||||
public static void doRunUnnamedModuleTest(Path classes) throws Throwable {
|
||||
ClassLoader unnamedModuleCL =
|
||||
new URLClassLoader(new URL[] {classes.toUri().toURL()}, OBJECT_CL);
|
||||
|
||||
// First get hold of the target classes before we enable security
|
||||
Class<?> sealed = unnamedModuleCL.loadClass("test.Base");
|
||||
|
||||
//try without a SecurityManager:
|
||||
checkPermittedSubclasses(sealed, "test.ImplA1",
|
||||
"test.ImplA2",
|
||||
"test.ImplB");
|
||||
|
||||
String[] denyPackageAccess = new String[1];
|
||||
int[] checkPackageAccessCallCount = new int[1];
|
||||
|
||||
//try with a SecurityManager:
|
||||
SecurityManager sm = new SecurityManager() {
|
||||
@Override
|
||||
public void checkPackageAccess(String pkg) {
|
||||
if (pkg.equals("test")) {
|
||||
checkPackageAccessCallCount[0]++;
|
||||
}
|
||||
if (Objects.equals(denyPackageAccess[0], pkg)) {
|
||||
throw new SecurityException();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
System.setSecurityManager(sm);
|
||||
|
||||
denyPackageAccess[0] = "test.unknown";
|
||||
|
||||
//passes - does not return a class from package "test.unknown":
|
||||
checkPermittedSubclasses(sealed, "test.ImplA1",
|
||||
"test.ImplA2",
|
||||
"test.ImplB");
|
||||
|
||||
if (checkPackageAccessCallCount[0] != 1) {
|
||||
throw new AssertionError("Unexpected call count: " +
|
||||
checkPackageAccessCallCount[0]);
|
||||
}
|
||||
|
||||
denyPackageAccess[0] = "test";
|
||||
|
||||
try {
|
||||
sealed.getPermittedSubclasses();
|
||||
throw new Error("getPermittedSubclasses incorrectly succeeded for " +
|
||||
sealed.getName());
|
||||
} catch (SecurityException e) {
|
||||
System.out.println("OK - getPermittedSubclasses for " + sealed.getName() +
|
||||
" got expected exception: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Path compileUnnamedModuleTest() throws IOException {
|
||||
Path base = Paths.get(".", "unnamed");
|
||||
Path src = base.resolve("src");
|
||||
Path classes = base.resolve("classes");
|
||||
|
||||
ModuleInfoMaker maker = new ModuleInfoMaker(src);
|
||||
maker.writeJavaFiles("test",
|
||||
"module test {}",
|
||||
"package test; public sealed interface Base permits ImplA1, ImplA2, ImplB, ImplC {}",
|
||||
"package test; public final class ImplA1 implements test.Base {}",
|
||||
"package test; public final class ImplA2 implements test.Base {}",
|
||||
"package test; public final class ImplB implements test.Base {}",
|
||||
"package test; public final class ImplC implements test.Base {}"
|
||||
);
|
||||
|
||||
Files.delete(src.resolve("test").resolve("module-info.java"));
|
||||
|
||||
if (!CompilerUtils.compile(src.resolve("test"), classes, "--enable-preview", "-source", System.getProperty("java.specification.version"))) {
|
||||
throw new AssertionError("Compilation didn't succeed!");
|
||||
}
|
||||
|
||||
Files.delete(classes.resolve("test").resolve("ImplC.class"));
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
private static void checkPermittedSubclasses(Class<?> c, String... expected) {
|
||||
Class<?>[] subclasses = c.getPermittedSubclasses();
|
||||
List<String> subclassesNames = Arrays.stream(subclasses)
|
||||
.map(Class::getName)
|
||||
.collect(Collectors.toList());
|
||||
if (!subclassesNames.equals(Arrays.asList(expected))) {
|
||||
throw new AssertionError("Incorrect permitted subclasses: " +
|
||||
subclassesNames);
|
||||
}
|
||||
}
|
||||
|
||||
private static URL[] getTestClassPath() {
|
||||
return Arrays.stream(System.getProperty("test.class.path")
|
||||
.split(System.getProperty("path.separator")))
|
||||
.map(TestSecurityManagerChecks::path2URL)
|
||||
.toArray(s -> new URL[s]);
|
||||
}
|
||||
|
||||
private static URL path2URL(String p) {
|
||||
try {
|
||||
return Path.of(p).toUri().toURL();
|
||||
} catch (MalformedURLException ex) {
|
||||
throw new AssertionError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -77,9 +77,13 @@ public class BinaryCompatibilityTests extends TestRunner {
|
||||
|
||||
@Test
|
||||
public void testCompatibilityAfterMakingSuperclassSealed(Path base) throws Exception {
|
||||
// sealing a super class which was not sealed, should fail with IncompatibleClassChangeError
|
||||
/* If a class that was not declared sealed is changed to be declared sealed, then an
|
||||
* IncompatibleClassChangeError is thrown if a binary of a pre-existing subclass of
|
||||
* this class is loaded that is not contained in its permits clause
|
||||
*/
|
||||
testCompatibilityAfterModifyingSupertype(
|
||||
base,
|
||||
true,
|
||||
"""
|
||||
package pkg;
|
||||
public class Super {
|
||||
@ -103,16 +107,16 @@ public class BinaryCompatibilityTests extends TestRunner {
|
||||
"""
|
||||
package pkg;
|
||||
class Sub extends Super {}
|
||||
""",
|
||||
true
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompatibilityAfterMakingSuperInterfaceSealed(Path base) throws Exception {
|
||||
// sealing a super interface which was not sealed, should fail with IncompatibleClassChangeError
|
||||
// test similar to testCompatibilityAfterMakingSuperclassSealed but with interfaces
|
||||
testCompatibilityAfterModifyingSupertype(
|
||||
base,
|
||||
true,
|
||||
"""
|
||||
package pkg;
|
||||
public interface Super {
|
||||
@ -136,8 +140,7 @@ public class BinaryCompatibilityTests extends TestRunner {
|
||||
"""
|
||||
package pkg;
|
||||
class Sub implements Super {}
|
||||
""",
|
||||
true
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
@ -149,18 +152,17 @@ public class BinaryCompatibilityTests extends TestRunner {
|
||||
*/
|
||||
private void testCompatibilityAfterModifyingSupertype(
|
||||
Path base,
|
||||
boolean shouldFail,
|
||||
String superClassCode1,
|
||||
String superClassCode2,
|
||||
String subClassCode,
|
||||
boolean shouldFail) throws Exception {
|
||||
String... subClassesCode) throws Exception {
|
||||
Path src = base.resolve("src");
|
||||
Path pkg = src.resolve("pkg");
|
||||
Path superClass = pkg.resolve("Super");
|
||||
Path sub = pkg.resolve("Sub");
|
||||
|
||||
// super class initially not sealed
|
||||
tb.writeJavaFiles(superClass, superClassCode1);
|
||||
tb.writeJavaFiles(sub, subClassCode);
|
||||
tb.writeJavaFiles(sub, subClassesCode);
|
||||
|
||||
Path out = base.resolve("out");
|
||||
Files.createDirectories(out);
|
||||
@ -220,9 +222,12 @@ public class BinaryCompatibilityTests extends TestRunner {
|
||||
|
||||
@Test
|
||||
public void testRemoveSealedModifierToClass(Path base) throws Exception {
|
||||
// should execute without error
|
||||
/* Changing a class that is declared sealed to no longer be declared
|
||||
* sealed does not break compatibility with pre-existing binaries.
|
||||
*/
|
||||
testCompatibilityAfterModifyingSupertype(
|
||||
base,
|
||||
false,
|
||||
"""
|
||||
package pkg;
|
||||
public sealed class Super permits pkg.Sub {
|
||||
@ -244,16 +249,16 @@ public class BinaryCompatibilityTests extends TestRunner {
|
||||
"""
|
||||
package pkg;
|
||||
final class Sub extends Super {}
|
||||
""",
|
||||
false
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveSealedModifierToInterface(Path base) throws Exception {
|
||||
// should execute without error
|
||||
// same as testRemoveSealedModifierToClass but with an interface
|
||||
testCompatibilityAfterModifyingSupertype(
|
||||
base,
|
||||
false,
|
||||
"""
|
||||
package pkg;
|
||||
public sealed interface Super permits pkg.Sub {
|
||||
@ -275,14 +280,15 @@ public class BinaryCompatibilityTests extends TestRunner {
|
||||
"""
|
||||
package pkg;
|
||||
final class Sub implements Super {}
|
||||
""",
|
||||
false
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddNonSealedModifierToClass(Path base) throws Exception {
|
||||
// should execute without error
|
||||
/* Changing a class that is not declared non-sealed to be declared
|
||||
* non-sealed does not break compatibility with pre-existing binaries
|
||||
*/
|
||||
testCompatibilityOKAfterSubclassChange(
|
||||
base,
|
||||
"""
|
||||
@ -307,7 +313,7 @@ public class BinaryCompatibilityTests extends TestRunner {
|
||||
|
||||
@Test
|
||||
public void testAddNonSealedModifierToInterface(Path base) throws Exception {
|
||||
// should execute without error
|
||||
// same as `testAddNonSealedModifierToClass` but with interfaces
|
||||
testCompatibilityOKAfterSubclassChange(
|
||||
base,
|
||||
"""
|
||||
@ -332,7 +338,9 @@ public class BinaryCompatibilityTests extends TestRunner {
|
||||
|
||||
@Test
|
||||
public void testRemoveNonSealedModifier(Path base) throws Exception {
|
||||
// should execute without error
|
||||
/* Changing a class that is declared non-sealed to no longer be declared
|
||||
* non-sealed does not break compatibility with pre-existing binaries
|
||||
*/
|
||||
testCompatibilityOKAfterSubclassChange(
|
||||
base,
|
||||
"""
|
||||
@ -357,7 +365,7 @@ public class BinaryCompatibilityTests extends TestRunner {
|
||||
|
||||
@Test
|
||||
public void testRemoveNonSealedModifierFromInterface(Path base) throws Exception {
|
||||
// should execute without error
|
||||
// same as `testRemoveNonSealedModifier` but with interfaces
|
||||
testCompatibilityOKAfterSubclassChange(
|
||||
base,
|
||||
"""
|
||||
@ -452,9 +460,13 @@ public class BinaryCompatibilityTests extends TestRunner {
|
||||
|
||||
@Test
|
||||
public void testAfterChangingPermitsClause(Path base) throws Exception {
|
||||
// the VM will throw IncompatibleClassChangeError
|
||||
/* If a class is removed from the set of permitted direct subclasses of
|
||||
* a sealed class then an IncompatibleClassChangeError is thrown if the
|
||||
* pre-existing binary of the removed class is loaded
|
||||
*/
|
||||
testCompatibilityAfterModifyingSupertype(
|
||||
base,
|
||||
true,
|
||||
"""
|
||||
package pkg;
|
||||
public sealed class Super permits pkg.Sub1, Sub2 {
|
||||
@ -480,8 +492,139 @@ public class BinaryCompatibilityTests extends TestRunner {
|
||||
"""
|
||||
package pkg;
|
||||
final class Sub1 extends Super {}
|
||||
""",
|
||||
true
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAfterChangingPermitsClause2(Path base) throws Exception {
|
||||
/* If a class is removed from the set of permitted direct subclasses of
|
||||
* a sealed class then an IncompatibleClassChangeError is thrown if the
|
||||
* pre-existing binary of the removed class is loaded
|
||||
*/
|
||||
testCompatibilityAfterModifyingSupertype(
|
||||
base,
|
||||
true,
|
||||
"""
|
||||
package pkg;
|
||||
public sealed class Super permits pkg.Sub1, pkg.Sub2 {
|
||||
public static void main(String... args) {
|
||||
pkg.Sub1 sub1 = new pkg.Sub1();
|
||||
pkg.Sub2 sub2 = new pkg.Sub2();
|
||||
System.out.println("done");
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
package pkg;
|
||||
public sealed class Super permits pkg.Sub1 {
|
||||
public static void main(String... args) {
|
||||
pkg.Sub1 sub1 = new pkg.Sub1();
|
||||
pkg.Sub2 sub2 = new pkg.Sub2();
|
||||
System.out.println("done");
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
package pkg;
|
||||
final class Sub1 extends Super {}
|
||||
""",
|
||||
"""
|
||||
package pkg;
|
||||
final class Sub2 extends Super {}
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAfterChangingPermitsClause3(Path base) throws Exception {
|
||||
/* Changing the set of permitted direct subclasses of a sealed class will
|
||||
* not break compatibility with pre-existing binaries, provided that the
|
||||
* total set of permitted direct subclasses of the sealed class loses no
|
||||
* members
|
||||
*/
|
||||
String superClassCode1 =
|
||||
"""
|
||||
package pkg;
|
||||
public sealed class Super permits pkg.Sub1 {
|
||||
public static void main(String... args) {
|
||||
pkg.Sub1 sub1 = new pkg.Sub1();
|
||||
System.out.println("done");
|
||||
}
|
||||
}
|
||||
""";
|
||||
String subClass1Code =
|
||||
"""
|
||||
package pkg;
|
||||
final class Sub1 extends Super {}
|
||||
""";
|
||||
|
||||
String superClassCode2 =
|
||||
"""
|
||||
package pkg;
|
||||
public sealed class Super permits pkg.Sub1, pkg.Sub2 {
|
||||
public static void main(String... args) {
|
||||
pkg.Sub1 sub1 = new pkg.Sub1();
|
||||
pkg.Sub2 sub2 = new pkg.Sub2();
|
||||
System.out.println("done");
|
||||
}
|
||||
}
|
||||
""";
|
||||
String subClass2Code =
|
||||
"""
|
||||
package pkg;
|
||||
final class Sub2 extends Super {}
|
||||
""";
|
||||
|
||||
Path src = base.resolve("src");
|
||||
Path pkg = src.resolve("pkg");
|
||||
Path superClass = pkg.resolve("Super");
|
||||
Path sub1 = pkg.resolve("Sub1");
|
||||
|
||||
tb.writeJavaFiles(superClass, superClassCode1);
|
||||
tb.writeJavaFiles(sub1, subClass1Code);
|
||||
|
||||
Path out = base.resolve("out");
|
||||
Files.createDirectories(out);
|
||||
|
||||
new JavacTask(tb)
|
||||
.options("--enable-preview",
|
||||
"-source", Integer.toString(Runtime.version().feature()))
|
||||
.outdir(out)
|
||||
.files(findJavaFiles(pkg))
|
||||
.run();
|
||||
|
||||
// let's execute to check that it's working
|
||||
String output = new JavaTask(tb)
|
||||
.vmOptions("--enable-preview")
|
||||
.classpath(out.toString())
|
||||
.classArgs("pkg.Super")
|
||||
.run()
|
||||
.writeAll()
|
||||
.getOutput(Task.OutputKind.STDOUT);
|
||||
|
||||
// let's first check that it runs wo issues
|
||||
if (!output.contains("done")) {
|
||||
throw new AssertionError("execution of Super didn't finish");
|
||||
}
|
||||
|
||||
// now lets change the super class
|
||||
tb.writeJavaFiles(superClass, superClassCode2);
|
||||
Path sub2 = pkg.resolve("Sub2");
|
||||
tb.writeJavaFiles(sub2, subClass2Code);
|
||||
|
||||
new JavacTask(tb)
|
||||
.options("--enable-preview",
|
||||
"-source", Integer.toString(Runtime.version().feature()))
|
||||
.classpath(out)
|
||||
.outdir(out)
|
||||
.files(findJavaFiles(superClass)[0], findJavaFiles(sub2)[0])
|
||||
.run();
|
||||
|
||||
new JavaTask(tb)
|
||||
.vmOptions("--enable-preview")
|
||||
.classpath(out.toString())
|
||||
.classArgs("pkg.Super")
|
||||
.run(Task.Expect.SUCCESS);
|
||||
}
|
||||
}
|
||||
|
@ -468,6 +468,26 @@ public class SealedCompilationTests extends CompilationTestCase {
|
||||
final class D extends C { }
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
sealed class C {
|
||||
void m() {
|
||||
class L {
|
||||
final class D extends C { }
|
||||
}
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
sealed class C {
|
||||
void m() {
|
||||
class L {
|
||||
void foo() {
|
||||
final class D extends C { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""))
|
||||
assertFail("compiler.err.local.classes.cant.extend.sealed", s);
|
||||
}
|
||||
@ -613,7 +633,7 @@ public class SealedCompilationTests extends CompilationTestCase {
|
||||
float.class, float[].class, double.class, double[].class, char.class, char[].class, boolean.class, boolean[].class, void.class,
|
||||
String[].class}) {
|
||||
Assert.check(!c.isSealed());
|
||||
Assert.check(c.permittedSubclasses().length == 0);
|
||||
Assert.check(c.getPermittedSubclasses().length == 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -956,4 +976,282 @@ public class SealedCompilationTests extends CompilationTestCase {
|
||||
assertOK(s);
|
||||
}
|
||||
}
|
||||
|
||||
public void testDoNotAllowSealedAnnotation() {
|
||||
for (String s : List.of(
|
||||
"""
|
||||
sealed @interface A {}
|
||||
non-sealed interface I extends A {}
|
||||
"""
|
||||
)) {
|
||||
assertFail("compiler.err.expected4", s);
|
||||
}
|
||||
}
|
||||
|
||||
public void testNarrowConversion() {
|
||||
for (String s : List.of(
|
||||
"""
|
||||
interface I {}
|
||||
sealed class C permits D {}
|
||||
final class D extends C {}
|
||||
|
||||
class Test {
|
||||
void test () {
|
||||
C c = null;
|
||||
I i = (I) c;
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
sealed interface I permits C {}
|
||||
final class C implements I {}
|
||||
interface J {}
|
||||
|
||||
class Test {
|
||||
void test () {
|
||||
I i = null;
|
||||
J j = (J) i;
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
interface I {}
|
||||
sealed interface J permits C {}
|
||||
final class C implements J {}
|
||||
|
||||
class Test {
|
||||
void test () {
|
||||
I i = null;
|
||||
J j = (J) i;
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
sealed interface I permits A {}
|
||||
sealed interface J permits B {}
|
||||
final class A implements I {}
|
||||
final class B implements J {}
|
||||
|
||||
class Test {
|
||||
void test () {
|
||||
I i = null;
|
||||
J j = (J) i;
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
class C {}
|
||||
sealed interface I permits A {}
|
||||
final class A implements I {}
|
||||
|
||||
class Test {
|
||||
void test () {
|
||||
C c = null;
|
||||
I i = (I) c;
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
final class C {}
|
||||
interface I {}
|
||||
|
||||
class Test {
|
||||
void test () {
|
||||
C c = null;
|
||||
I i = (I) c;
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
final class C {}
|
||||
sealed interface I permits D {}
|
||||
final class D implements I {}
|
||||
|
||||
class Test {
|
||||
void test () {
|
||||
C c = null;
|
||||
I i = (I) c;
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
sealed class C permits D {}
|
||||
final class D extends C {}
|
||||
non-sealed interface I {}
|
||||
|
||||
class Test {
|
||||
void test () {
|
||||
C c = null;
|
||||
I i = (I) c;
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
sealed class C permits D {}
|
||||
final class D {}
|
||||
sealed interface I permits E {}
|
||||
final class E {}
|
||||
|
||||
class Test {
|
||||
void test () {
|
||||
C c = null;
|
||||
I i = (I) c;
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
sealed class C permits D {}
|
||||
sealed class D permits NS {}
|
||||
non-sealed class NS extends D {}
|
||||
sealed interface I permits E {}
|
||||
final class E {}
|
||||
|
||||
class Test {
|
||||
void test () {
|
||||
C c = null;
|
||||
I i = (I) c;
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
interface I {}
|
||||
final class C {}
|
||||
|
||||
class Test {
|
||||
void test () {
|
||||
I i = null;
|
||||
C c = (C) i;
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
interface I {}
|
||||
sealed class C permits D {}
|
||||
final class D {}
|
||||
|
||||
class Test {
|
||||
void test () {
|
||||
I i = null;
|
||||
C c = (C) i;
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
sealed interface I permits D {}
|
||||
final class D {}
|
||||
class C {}
|
||||
|
||||
class Test {
|
||||
void test () {
|
||||
I i = null;
|
||||
C c = (C) i;
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
sealed interface I permits D {}
|
||||
final class D implements I {}
|
||||
final class C {}
|
||||
|
||||
class Test {
|
||||
void test () {
|
||||
I i = null;
|
||||
C c = (C) i;
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
sealed interface I permits D {}
|
||||
final class D implements I {}
|
||||
sealed class C permits E {}
|
||||
final class E extends C {}
|
||||
|
||||
class Test {
|
||||
void test () {
|
||||
I i = null;
|
||||
C c = (C) i;
|
||||
}
|
||||
}
|
||||
"""
|
||||
)) {
|
||||
assertFail("compiler.err.prob.found.req", s);
|
||||
}
|
||||
|
||||
for (String s : List.of(
|
||||
"""
|
||||
interface I {}
|
||||
sealed class C permits D, E {}
|
||||
non-sealed class D extends C {}
|
||||
final class E extends C {}
|
||||
|
||||
class Test {
|
||||
void test () {
|
||||
C c = null;
|
||||
I i = (I) c;
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
interface I {}
|
||||
interface J {}
|
||||
|
||||
class Test {
|
||||
void test () {
|
||||
I i = null;
|
||||
J j = (J) i;
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
class C {}
|
||||
interface I {}
|
||||
|
||||
class Test {
|
||||
void test () {
|
||||
C c = null;
|
||||
I i = (I) c;
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
interface I {}
|
||||
class C {}
|
||||
|
||||
class Test {
|
||||
void test () {
|
||||
I i = null;
|
||||
C c = (C) i;
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
sealed class C permits D {}
|
||||
sealed class D extends C permits NS {}
|
||||
non-sealed class NS extends D {}
|
||||
interface I {}
|
||||
|
||||
class Test {
|
||||
void test () {
|
||||
C c = null;
|
||||
I i = (I) c;
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
sealed interface A permits B { }
|
||||
non-sealed interface B extends A { }
|
||||
interface C { }
|
||||
|
||||
class D implements C, B { }
|
||||
|
||||
class Test {
|
||||
void m(A a, C c) {
|
||||
a = (A)c;
|
||||
}
|
||||
}
|
||||
"""
|
||||
)) {
|
||||
assertOK(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user