-
Notifications
You must be signed in to change notification settings - Fork 25.5k
Adjust jdk-api-extractor output to be compatible with the public-callers-finder #135824
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
Conversation
…ers-finder to easily find the transitive public surface of new additions that require entitlement instrumentation. Relates to ES-11757
Pinging @elastic/es-core-infra (Team:Core/Infra) |
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.
Looks fine just a couple questions/asks for the future
return EnumSet.of(ExternalAccess.PUBLIC_CLASS, ExternalAccess.PUBLIC_METHOD); | ||
} | ||
if ("PROTECTED".equals(accessAsString)) { | ||
return EnumSet.of(ExternalAccess.PUBLIC_CLASS, ExternalAccess.PROTECTED_METHOD); |
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 public class and not protected class? What does this class represent (a short javadoc would be nice)?
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.
PROTECTED
refers to methods, that's only used by the jdk-api-extractor. The class had to be public in this case.
I can add a comment for that 👍
@ldematte maybe you could also chime in here, this part of the old public-callers-finder
was a bit confusing.
fromPermissions
is invoked as follows:
Lines 123 to 128 in 7cbc236
EnumSet<ExternalAccess> externalAccess = ExternalAccess.fromPermissions( | |
moduleExports.contains(getPackageName(className)), | |
accessibleViaInterfaces || (classAccess & ACC_PUBLIC) != 0, | |
(methodAccess & ACC_PUBLIC) != 0, | |
(methodAccess & ACC_PROTECTED) != 0 | |
); |
so being exported is a requirement for PUBLIC_CLASS
in addition to either extending a public interface or being public:
Lines 27 to 48 in 7cbc236
public static EnumSet<ExternalAccess> fromPermissions( | |
boolean packageExported, | |
boolean publicClass, | |
boolean publicMethod, | |
boolean protectedMethod | |
) { | |
if (publicMethod && protectedMethod) { | |
throw new IllegalArgumentException(); | |
} | |
EnumSet<ExternalAccess> externalAccesses = EnumSet.noneOf(ExternalAccess.class); | |
if (publicMethod) { | |
externalAccesses.add(ExternalAccess.PUBLIC_METHOD); | |
} else if (protectedMethod) { | |
externalAccesses.add(ExternalAccess.PROTECTED_METHOD); | |
} | |
if (packageExported && publicClass) { | |
externalAccesses.add(ExternalAccess.PUBLIC_CLASS); | |
} | |
return externalAccesses; | |
} |
isExternallyAccessible
seems to be to restrictive as it requires PUBLIC_CLASS
. That practically excludes all classes that are not exported but which are externally accessible via exported public interfaces (or similarly super classes), doesn't it?
Lines 50 to 53 in 7cbc236
public static boolean isExternallyAccessible(EnumSet<ExternalAccess> access) { | |
return access.contains(ExternalAccess.PUBLIC_CLASS) | |
&& (access.contains(ExternalAccess.PUBLIC_METHOD) || access.contains(ExternalAccess.PROTECTED_METHOD)); | |
} |
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 you are right: looking at the code you posted, it seems to me that PUBLIC_CLASS
is accessibleViaInterfaces || (classAccess & ACC_PUBLIC) != 0
, but also packageExported
.
The fix here is maybe not too complicated: the boolean accessibleViaInterfaces
should be changed to be true only if moduleExports.contains(getPackageName(interfaceName)
(I think that is not the case currently?), and passed separately.
Then we can change PUBLIC_CLASS
to be true if packageExported && (classAccess & ACC_PUBLIC) != 0
OR accessibleViaInterfaces
.
Or something similar.
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 @ldematte , I'll follow up with a separate PR for this 👍
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 wanted to give this a go, unfortunately it is more involved. Having a closer look, i'm seeing a couple if issues:
moduleExports.contains(getPackageName(interfaceName)
is already checked, but not necessarily correct. the interface might belong to another module.superName
needs to be treated the same way as an interface- and last,
accessibleViaInterfaces
currently applies to all public methods. In case of interfaces likeAutoClosable
that could be very misleading, reporting far too much.
I'll create a separate issue for this.
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.
moduleExports.contains(getPackageName(interfaceName) is already checked, but not necessarily correct. the interface might belong to another module.
Yep, that's why I think we need to do that when we compute and pass accessibleViaInterfaces
(so at the call side, not here).
You might be right with superName; I acted under the assumption that were the JDK wants to expose something with an internal implementation it does so via interfaces, but it might be that they use e.g. abstract classes.
And yes, this need to be per-method, which I think is going to be the most complex part.
I want this to be correct, but not sure how urgent it is; probably we'd like to have it correct before JDK 26?
I wonder if this could be a good "first issue", e.g. something for onboarding? It's entitlements related, but this being a separate project it could be quite self-contained
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, I agree... I'm also leaning towards postponing this ➕
regarding accessibleViaInterfaces
: findAccessibility
currently uses the exports of the current class rather than the exports of the module the interface originates from.
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
...
if (interfaces.length > 0) {
this.accessibleViaInterfaces = findAccessibility(interfaces, moduleExports);
}
}
private static boolean findAccessibility(String[] interfaces, Set<String> moduleExports) {
var accessibleViaInterfaces = false;
for (var interfaceName : interfaces) {
if (moduleExports.contains(getPackageName(interfaceName))) {
var interfaceType = Type.getObjectType(interfaceName);
try {
var clazz = Class.forName(interfaceType.getClassName());
if (clazz.accessFlags().contains(AccessFlag.PUBLIC)) {
accessibleViaInterfaces = true;
}
} catch (ClassNotFoundException ignored) {}
}
}
return accessibleViaInterfaces;
}
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.
Yeah, I was suspecting this was the case :/
# review new additions next for critical ones that should require entitlements | ||
# once done, remove all lines that are not considered critical and run the public-callers-finder to report | ||
# the transitive public surface for these additions | ||
./gradlew :libs:entitlement:tools:public-callers-finder:run -Druntime.java=25 --args="api-jdk25-additions.tsv true" |
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 followup: can we make this use the latest jdk (or maybe the bundled?) so we don't have to specify it? And why --args
instead of specific args like --output-file
?
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 followup: can we make this use the latest jdk (or maybe the bundled?)
that's mostly for reference in the examples, I'll update the docs to mention it will default to the bundled JDK if not provided
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.
And why --args instead of specific args like --output-file?
That's how gradle run passes args to the main
class. We could use Gradle properties (-PoutputFile=api-jdk25-additions.tsv
) and pass onto the args in the build script. But --output-file=api-jdk25-additions.tsv
can't be done easily I think.
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.
Ah I see, this is a task created by the gradle application plugin. You can have --output-file
if you define the task yourself. @mark-vieira I assume there is no way to add --
inputs to a task as part of configuration rather than the definition of the task?
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'll follow up with a separate PR to simplify how to invoke these tools. @mark-vieira any advice?
Adjust jdk-api-extractor output to be compatible with the public-callers-finder to easily find the transitive public surface of new additions that require entitlement instrumentation.
Relates to ES-11757