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
Conversation
5347c54
to
81c164f
Compare
this.method.invoke(this.handle, event); | ||
} | ||
public void handle(final Event event) throws Exception { | ||
// if (this.filter != null) { |
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 don't think this can reasonably come back, maybe just delete this whole factory?
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.
Having reviewed it in SF 1.12, it doesn't even seem to have been used even then. Deleting!
src/main/java/org/spongepowered/common/event/manager/ListenerClassVisitor.java
Outdated
Show resolved
Hide resolved
src/main/java/org/spongepowered/common/event/manager/ListenerClassVisitor.java
Outdated
Show resolved
Hide resolved
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: |
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.
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
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.
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.
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.
Rewrote with SignatureVisitor on the parameter
14c9551
to
e23feea
Compare
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>
e23feea
to
e52e553
Compare
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())); |
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 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()))) { |
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.
should use the declaring class's class loader for this as well
final Class<?> targetType = Class.forName(params[paramIdx].type().getClassName()); | ||
final Class<?> eventClass = Class.forName(params[0].type().getClassName()); |
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.
declaring class's loader, just in case that's different
} | ||
|
||
public Class<?> clazz() throws ClassNotFoundException { | ||
return Class.forName(this.type.getClassName()); |
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.
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())); |
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.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); |
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.clazz()
AnnotationFormatException { | ||
this.values.put(name, value); | ||
this.annotation = TypeFactory.annotation( | ||
(Class<? extends Annotation>) Class.forName(type.getClassName()), this.values); |
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.
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>
e52e553
to
5506503
Compare
Signed-off-by: Gabriel Harris-Rouquette <gabizou@me.com>
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