-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
8246778: Compiler implementation for Sealed Classes (Second Preview) #1483
Conversation
…both for sealed classes in named and unnamed modules.
👋 Welcome back jlahoda! A progress list of the required criteria for merging this PR into |
@lahodaj |
@lahodaj |
Webrevs
|
* Then these names are converted to {@code Class} instances using | ||
* {@linkplain #getClassLoader() the defining class loader} of the current | ||
* {@code Class} object. If a name cannot be converted to the {@code Class} | ||
* instance, it is silently excluded from the result. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this paragraph will need a little bit of wordsmithing. The 3rd paragraph of getNestMembers might be useful to examine as it more clearly describes how the method attempts to "obtain" the Class object for each of the class names in the NestMembers attribute and maybe some of that wording could be used instead of using the term "convert".
Minor nit but the prevailing style for the @throws SecurityException is to align the description with the exception, probably best to keep it consistent if you can.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I've tried to improve the javadoc here:
4d48417
* loader} of the current {@code Class} object). | ||
* The {@code Class} objects which can be obtained using this procedure | ||
* 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the update, this reads much better.
* {@code false}. | ||
* | ||
* @apiNote | ||
* Sealed class or interface has no relationship with | ||
* {@linkplain Package#isSealed package sealing}. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Package sealing is legacy. Remi suggests to take out this api note which sounds good to me.
The API note in Package::isSealed has made this clear which has no relationship with sealed class or interface.
* cannot be obtained, it is silently ignored, and not included in the result | ||
* array. | ||
* | ||
* @return an array of class objects of the permitted subclasses of this class or interface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: s/class objects/{@code Class} objects/
} | ||
|
||
private native String[] getPermittedSubclasses0(); | ||
private native Class<?>[] getPermittedSubclasses0(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this JVM method return the permitted subclasses or subinterfaces with the following conditions enforced by JLS:
- If a sealed class C belongs to a named module, then every class named in the permits clause of the declaration of C must belong to the same module as C
- If a sealed class C belongs to an unnamed module, then every class named in the permits clause of the declaration of C must belong to the same package as C
I didn't check the VM implementation.
If the return array contains only classes as specified above, checkPackageAccessForClasses
can be simplified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The JVM method that returns the permitted subclasses (and interfaces) does not weed out permitted subclasses based on the above module requirements. It returns all the classes listed in the PermittedSubclasses attribute that it is able to load.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it could also return a class listed in PermittedSubclasses
attribute but not a subclass of this sealed class, right?
The specification of Class::getPermittedSubclasses
says:
Returns an array containing {@code Class} objects representing the direct subclasses or direct implementation classes permitted to extend or direct subinterfaces or subclasses permitted to extend or implement this class or interface if it is sealed.
I expect that Class::getPermittedSubclasses
should do more work and return only the subclasses or subinterfaces permitted at runtime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was investigating a little today. One thing to note is that there is a difference between the JLS and JVMS[1] restrictions - the JVMS restrictions only require the classes to be in the same module, but they can be in any package (even for an unnamed module).
Moreover, a lot of the constraints are checked automatically: e.g. consider class api.Api in module "a", which permits impl.Impl in module "b", subtyping Api. Loading of impl.Impl on behalf of getPermittedSubclasses() will fail (because the two classes are in different modules). So impl.Impl will not be included.
So far, it seems the only constraint that I think is not satisfied by this implicit loading is that the permitted subclass is really a direct subtype of the current class.
My proposal is to enhance the Java method with the direct subtype check (with a possible future cleanup task to move the check to native, as is done for getNestMembers()).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lahodaj It is okay with me if getPermittedSubclasses
returns the permitted subtypes matching the runtime view (that matches the current specification to me) and revisit this API as a follow up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, would be a surprise if getPermittedSubclasses returned Class objects for classes that are not subclasses. I think it should be okay to separate that out to a separate issue so that it can be further re-examined after JEP 397 goes in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW: I plan to change the method to not return such Class objects. It just will do the verification in Java, while for getNestMembers it is done in native. The follow up is for investigate the possibility of making the internal behavior consistent with how getNestMembers work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the record: similar/related questions/comments have been made on a-s-e [1][2]. The questions/comments were made in order to clarify the JVMS specification, so as to better inform what responsibilities are part Core Reflection versus what are part of the VM.
[1] https://mail.openjdk.java.net/pipermail/amber-spec-experts/2020-October/002626.html
[2] https://mail.openjdk.java.net/pipermail/amber-spec-experts/2020-November/002660.html
|
||
for (Class<?> c : classes) { | ||
// skip the package access check on a proxy class in default proxy package | ||
if (!Proxy.isProxyClass(c) || ReflectUtil.isNonPublicProxyClass(c)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a sealed class is in a named module, the permitted subclasses/subinterfaces are in the same module as the sealed class. If a sealed class is in an unnamed module, it will be in the same runtime package as the sealed class. A proxy class is dynamically generated and not intended for statically named in permits
clause of a sealed class`. It can be in a different module or different package. So a permitted subclass or interface should never be a proxy class.
So the package access check for permitted subclasses/subinterfaces can be simplified. I would suggest this check be inlined in getPermittedSubclasses
as follows:
SecurityManager sm = System.getSecurityManager();
if (subclasses.length > 0 && sm != null) {
ClassLoader ccl = ClassLoader.getClassLoader(Reflection.getCallerClass());
ClassLoader cl = 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.isEmpty())
packages.add(pkg);
}
for (String pkg : packages) {
sm.checkPackageAccess(pkg);
}
}
}
@@ -50,7 +50,7 @@ | |||
final class Final4 {} | |||
|
|||
public static void testSealedInfo(Class<?> c, String[] expected) { | |||
Object[] permitted = c.permittedSubclasses(); | |||
Object[] permitted = c.getPermittedSubclasses(); | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why permitted
is not Class<?>[]
? or simply var permitted = ...
@@ -65,7 +65,7 @@ public static void testSealedInfo(Class<?> c, String[] expected) { | |||
// 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(((Class)permitted[i]).getName()); | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This cast is not needed if permitted
is var
or Class<?>[]
type.
@@ -0,0 +1,260 @@ | |||
/* | |||
* Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. | |||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the copyright start year should be 2020.
From David's comment:
As I read the current spec of
IMO it is reasonable for |
Mailing list message from David Holmes on compiler-dev: On 1/12/2020 10:39 am, Mandy Chung wrote:
These subtleties are what I was also getting at in the CSR discussion. David |
…s that are not a subtype of the current class, and other adjustments per the review feedback.
Additional changes may be needed to Class.permittedSubclasses() and/or Class.isSealed() as part of fixing bug JDK-8256867. The JVM is being changed to treat classes with empty PermittedSubclasses attributes as sealed classes that cannot be extended (or implemented). Current thinking is that Class.permittedSubclasses() will return an empty array for both non-sealed classes and for sealed classes with empty PermittedSubclasses attributes. And, Class.isSealed() will return False in the former case and True in the latter. This will require changing the implementation of Class.isSealed() to call the JVM directly instead of calling Class.permittedSubclasses(). Does this seem like a reasonable way to handle this corner case? |
Uh, I just realized it may be necessary to implement
Where |
@lahodaj I raised this issue here: https://mail.openjdk.java.net/pipermail/amber-spec-experts/2020-October/002626.html Relevant comment inline:
|
I suggest If this |
Agree, that seems reasonable. Often, methods in |
After a discussion with Harold, I've reverted the patch where Class.getPermittedSubclasses returns null. Harold will do that separatelly under JDK-8256867, unless there are objections. The changes that were reverted are still available here: Please let me know if there are any issues. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have reviewed the hotspot changes and they look good.
Thanks,
Lois
@lahodaj This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 147 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. ➡️ To integrate this PR with the above commit message to the |
No objection. Keeping |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I approve this version of Class::getPermittedSubclasses
implementation for this PR. We need to follow up the specification of Class::getPermittedSubclasses
w.r.t. what it should return e.g. the classes whatever in PermittedSubclasses
attribute vs the classes that are permitted subtypes at runtime and return null if this class is not sealed.
I reviewed hotspot and java.base changes (not langtools) with a couple minor comments.
|
||
public class TestSecurityManagerChecks { | ||
|
||
private static final ClassLoader OBJECT_CL = Object.class.getClassLoader(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is null
- the bootstrap class loader. An alternative to the static variable we can simply use null.
URL testLocation = TestSecurityManagerChecks.class | ||
.getProtectionDomain() | ||
.getCodeSource() | ||
.getLocation(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is essentially the classpath. An alternative way to get the location through System.getProperty("test.class.path")
.
OBJECT_CL); | ||
|
||
// First get hold of the target classes before we enable security | ||
Class<?> sealed = testLayer.findLoader("test").loadClass("test.Base"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would recommend to use Class::forName
instead of ClassLoader::loadClass
even though this is just a test (for security reason for example avoid type confusion). If you want to load a class from a specific module, you can use Class.forName(String cn, Module m)
//try without a SecurityManager: | ||
Class<?>[] subclasses = sealed.getPermittedSubclasses(); | ||
|
||
if (subclasses.length != 3) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest to check against the expected list of permitted subclasses here and also the validation in the subsequent calls to getPermittedSubclasses
with security manager enabled. That would help the readers easier to understand this test.
denyPackageAccess[0] = "test"; | ||
|
||
//should pass - does not return a class from package "test": | ||
sealed.getPermittedSubclasses(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding a check to the expected returned value would make this clear (no permitted subclasses of package test
).
* NOTE: this method does not support Proxy classes | ||
*/ | ||
private static void checkPackageAccessForPermittedSubclasses(SecurityManager sm, | ||
final ClassLoader ccl, boolean checkProxyInterfaces, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
checkProxyInterfaces
parameter is not needed. It can be removed.
|
||
for (Class<?> c : subClasses) { | ||
if (Proxy.isProxyClass(c)) | ||
throw new InternalError("a permitted subclass should not be a proxy class: " + c); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor nit but I think the indentation may be messed up here.
* 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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean {@code Void} here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this means void.class
. void.class
is a little special (as are the Class objects for primitive types, like int.class
), but Void.class or Integer.class are not so much special.
I've updated the patch based on the other comments - thanks!
* 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 using this procedure, | ||
* and which are direct subinterfaces or subclasses of this class or interface, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor suggestion is to drop "using this procedure" from this sentence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Compiler changes look good to me
/integrate |
@lahodaj Since your change was applied there have been 167 commits pushed to the
Your commit was automatically rebased without conflicts. Pushed as commit 637b0c6. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
This pull request replaces #1227.
From the original PR:
This PR strives to reflect the review comments from 1227:
/contributor add vromero
/contributor add hseigel
Progress
Issue
Reviewers
Contributors
<vromero@openjdk.org>
<hseigel@openjdk.org>
Download
$ git fetch https://git.openjdk.java.net/jdk pull/1483/head:pull/1483
$ git checkout pull/1483