Skip to content
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

Wrap Event Listener Registration #3619

Merged
merged 4 commits into from Feb 8, 2022
Merged

Wrap Event Listener Registration #3619

merged 4 commits into from Feb 8, 2022

Conversation

gabizou
Copy link
Member

@gabizou gabizou commented Feb 1, 2022

The end of Reflection

The main goal of this PR is to eliminate usages of Java's Reflection API due to the intrinsic nature of Reflection being finicky with our lack of control over ClassLoaders. There are specific cases where classes may not be available and due to the nature of the JVM, it's acceptable for some classes to be missing since the exception will only be triggered by code path explicitly. This explicit access is normally triggered by compiled code, but can be implicitly triggered by Reflection.

The Solution

Instead of triggering class loads by Reflection, we will instead use ASM ClassReaders to perform the work we need to do. In some cases, this is as simple as a ClassVisitor that analyzes the methods declared on the class. In other cases, it's finding any Listeners available on a Class.

Fixes #3589 #3597 #3574 #3548 #3532

this.method.invoke(this.handle, event);
}
public void handle(final Event event) throws Exception {
// if (this.filter != null) {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this can reasonably come back, maybe just delete this whole factory?

Copy link
Member Author

Choose a reason for hiding this comment

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

Having reviewed it in SF 1.12, it doesn't even seem to have been used even then. Deleting!

for (int i = 0; i < type.length; i++) {
parameters[i] = new ListenerParameter(this.discoveredMethod, type[i]);
}
// TODO - figure out a cleaner way to do this, this works for:
Copy link
Member

Choose a reason for hiding this comment

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

maybe take a look at ASM's SignatureReader? it should be able to capture the applicable type, and Geantyref has methods to create a runtime reflection Type ourselves

Copy link
Member Author

Choose a reason for hiding this comment

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

That's fair, I did read briefly about using SignatureReader, but didn't connect the dots on making it work (code written at 2am isn't always the sanest).

I did take advantage of Geantyref's TypeFactory in a few places already for creating Type instances as necessary.

Copy link
Member Author

Choose a reason for hiding this comment

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

Rewrote with SignatureVisitor on the parameter

Avoids game crashes for seemingly client sided methods.
Client sided methods can be included in class bytecode, but
ultimately it's up to the developer to exclude it properly. In our case,
we don't have control over it normally, so we'll have to expect any
client side code to trigger some RuntimeExceptions.

Signed-off-by: Gabriel Harris-Rouquette <gabizou@me.com>
The end goal is to replace usage of Java Reflection API that can be prone
to classloading exceptions/errors that end up crashing or throwing errors.
The major problem with reflection is the lack of clear control over
ClassLoaders and their ability to throw any arbitrary error. In retrospect,
we can now safely scan entire classes for specific methods that we care
about without triggering these classes to be loaded. Likewise, as a test,
we can assure that a defined class with an impossible to use method will
still be correctly covered for our purposes.

Signed-off-by: Gabriel Harris-Rouquette <gabizou@me.com>
Object obj = FilterGenerator.filterFromAnnotation(anno.annotationType());
final List<ParameterFilterDelegate> paramFilters = new ArrayList<>();
for (final ListenerClassVisitor.ListenerAnnotation anno : param.annotations()) {
final Object obj = FilterGenerator.filterFromAnnotation(Class.forName(anno.type().getClassName()));
Copy link
Member

Choose a reason for hiding this comment

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

This should probably use method.declaringClass().getClassLoader() to look up the annotation class name

final Parameter param = params[i];
if (!ValueContainer.class.isAssignableFrom(param.getType())) {
final ListenerClassVisitor.ListenerParameter param = params[i];
if (!ValueContainer.class.isAssignableFrom(Class.forName(param.type().getClassName()))) {
Copy link
Member

Choose a reason for hiding this comment

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

should use the declaring class's class loader for this as well

Comment on lines 65 to 66
final Class<?> targetType = Class.forName(params[paramIdx].type().getClassName());
final Class<?> eventClass = Class.forName(params[0].type().getClassName());
Copy link
Member

Choose a reason for hiding this comment

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

declaring class's loader, just in case that's different

}

public Class<?> clazz() throws ClassNotFoundException {
return Class.forName(this.type.getClassName());
Copy link
Member

Choose a reason for hiding this comment

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

should use method's declaring class


public java.lang.reflect.Type genericType() throws ClassNotFoundException {
if (this.genericType == null) {
return TypeFactory.parameterizedClass(Class.forName(this.type.getClassName()));
Copy link
Member

Choose a reason for hiding this comment

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

this.clazz()

return TypeFactory.parameterizedClass(Class.forName(this.type.getClassName()));
}
final java.lang.reflect.Type generic = TypeFactory.parameterizedClass(Class.forName(this.genericType.getClassName()));
return TypeFactory.parameterizedClass(Class.forName(this.type.getClassName()), generic);
Copy link
Member

Choose a reason for hiding this comment

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

this.clazz()

AnnotationFormatException {
this.values.put(name, value);
this.annotation = TypeFactory.annotation(
(Class<? extends Annotation>) Class.forName(type.getClassName()), this.values);
Copy link
Member

Choose a reason for hiding this comment

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

need to pas the declaring class's classloader through here

This was brutal awakening of how much reflection is relied on to create all sorts
of classes. Fortunately, the brunt of the changes are to do with figuring out how
to use ASM visitors to do the discovery work. It is now unit tested to ensure
classes that can be triggered with reflection for failure, will not fail.
An advantage of this is now we are assured the only class loading failures would
be breaking changes to our own API classes.

Signed-off-by: Gabriel Harris-Rouquette <gabizou@me.com>
Signed-off-by: Gabriel Harris-Rouquette <gabizou@me.com>
@gabizou gabizou merged commit 5368fe2 into api-8 Feb 8, 2022
@gabizou gabizou deleted the api8/wrap-event-listeners branch February 8, 2022 03:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Server crash with Randompatches
2 participants