Skip to content

Commit

Permalink
8246778: Compiler implementation for Sealed Classes (Second Preview)
Browse files Browse the repository at this point in the history
Co-authored-by: Vicente Romero <vromero@openjdk.org>
Co-authored-by: Harold Seigel <hseigel@openjdk.org>
Reviewed-by: lfoltan, mchung, alanb, mcimadamore, chegar
  • Loading branch information
3 people committed Dec 7, 2020
1 parent 09707dd commit 637b0c6
Show file tree
Hide file tree
Showing 16 changed files with 1,127 additions and 83 deletions.
28 changes: 22 additions & 6 deletions src/hotspot/share/prims/jvm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
117 changes: 94 additions & 23 deletions src/java.base/share/classes/java/lang/Class.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,14 @@
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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -3020,6 +3020,37 @@ private void checkPackageAccess(SecurityManager sm, final ClassLoader ccl,
}
}

/*
* 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
Expand Down Expand Up @@ -4357,47 +4388,87 @@ public Optional<ClassDesc> describeConstable() {
* 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;
}
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);
@CallerSensitive
public Class<?>[] getPermittedSubclasses() {
Class<?>[] subClasses;
if (isArray() || isPrimitive() || (subClasses = getPermittedSubclasses0()).length == 0) {
return EMPTY_CLASS_ARRAY;
}
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.
Expand All @@ -4412,8 +4483,8 @@ public boolean isSealed() {
if (isArray() || isPrimitive()) {
return false;
}
return permittedSubclasses().length != 0;
return getPermittedSubclasses().length != 0;
}

private native String[] getPermittedSubclasses0();
private native Class<?>[] getPermittedSubclasses0();
}
2 changes: 1 addition & 1 deletion src/java.base/share/native/libjava/Class.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
43 changes: 40 additions & 3 deletions src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java
Original file line number Diff line number Diff line change
Expand Up @@ -1629,32 +1629,69 @@ public boolean isCastable(Type t, Type s) {
}

/**
* 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1303,7 +1303,10 @@ && checkDisjoint(pos, flags,
SEALED | NON_SEALED)
&& checkDisjoint(pos, flags,
SEALED,
FINAL | NON_SEALED)) {
FINAL | NON_SEALED)
&& checkDisjoint(pos, flags,
SEALED,
ANNOTATION)) {
// skip
}
return flags & (mask | ~ExtendedStandardFlags) | implicit;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4264,11 +4264,19 @@ protected boolean isSealedClassStart(boolean local) {
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;
};
Expand Down
Loading

1 comment on commit 637b0c6

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.