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
Correct class loader lost in ClassWriter.getCommonSuperClass(String,String) when using TransformingClassLoader #117
Comments
|
I've created a test case to demonstrate the workaround and the issue. |
|
This is probably not expected behavior, you are right but I am a bit scared to change that code, especially since you found a workaround. cglib was written in a time long before stack-map frames and use case around problems with them are therefore not really considered. cglib is used in a lot of applications and any change of implicit state can have rather far reaching consequences, unfortunately, and this particular change, I would need to analyze quite a bit before doing anything. cglib is no longer under active development, please consider alternative code generation libraries if you rely on stack-map frame construction. |
|
This problem has a broader impact - it's not only confined to TransformingClassLoader/AbstractClassLoader, but is also applicable to any class generated with DebuggingClassWriter (i.e. Enhancer with DefaultGeneratorStrategy). Cglib 3.2+ specifies COMPUTE_FRAMES during creation of ClassWriter object to enforce recalculation of stack map frames (which is probably the right thing to do), which causes invocation of ClassWriter.getCommonSuperClass method during incoming stack frames merging. Default ClassWriter implementation uses current class-loader (i.e. classloader which was used to load ClassWriter itself) to load classes of merging types - so, if actual classes are unavailable in this class-loader, then the code generation fails. This problem can be observed in any configuration with multi-level class loader configuration - one example is JEE environment in which cglib itself is located on EAR application level, but is used to instrument classes in children WAR-modules (which usually have their own classloaders under EAR to allow hot reloading of classes). In order to fix this problem, we can override the 'getCommonSuperClass' method of the ClassWriter class and load classes using the same classloader which is used to define the resulting generated class (i.e. Enhancer.getClassLoader - which is either explicitly defined or derived from super-class/interfaces). I cannot think now of any case in which we may want NOT to use this classloader to calculate common super-class during stackmap calculation. Implementation of possible fix (with unit tests for both Enhancer and TransformingClassLoader cases) is included into the following pull request: #118. The way of how class loader is injected into the DebuggingClassLoader in DefaultGeneratorStrategy is a little clumsy as I'd like to minimize changes to the interfaces. Unit tests uses special filtering class-loader to create complex class-loader configuration. I'd like also to mention, that this problem should also affect most implementations of 'AbstractTransformTask' - Ant-tasks which use CGLIB for bulk processing of classes or jar files. In this case we don't have any meaningful classloader at all, so if classes which are processed by the task are not already somehow present in the classpath of current JVM, then transformation will fail on the same attempt to resolve common super-class. However, I see no easy fix here (and I'm unsure whether anybody is really using this functionality at all). cglib doesn't provide the end-user implementation of such Ant tasks, so it seems that every user of 'AbstractTransformTask' should review transformations performed and provide a way to override ClassWriter creation or implementation - maybe it's useful to extract creation of DebuggingClassLoader in AbstractTransformTask into a separate protected method to allow its customization in sub-classes. |
We are transforming a class structure from an old format to a new one. We make use of the TransformingClassLoader to do this. We recently moved to Java 8 from Java 6, so we had to update our version of cglib from 2.2.1 to a more recent version, 3.2.5.
The code (which was previously working) has stopped working. It looks like the culprit is the class loader in the ClassWriter class (from ASM) is finding class loader which loaded the TransformingClassLoader and not the one which we passed into the constructor. The class loader which loaded TransformingClassLoader is unable to see this class and so we get a java.lang.ClassNotFoundException.
In our case the class loader we pass into the TransformingClassLoader has the class loader that loaded the TransformingClassLoader as it's parent, but this does not help us.
We pinned this down to the following line in ASM which is using the class loader which created the ClassWriter, but this is created by the parent class loader of the one we passed into the TransformingClassLoader.
https://gitlab.ow2.org/asm/asm/blob/master/asm/src/main/java/org/objectweb/asm/ClassWriter.java#L827
i.e. the class loaders are in the following structure.
[A] Java Class Loader <- [B] Our Class Loader <- [C] TransformingClassLoader
The problem comes when ClassWriter finds class loader [A] instead of the expected class loader [B] due to the getClass().getClassLoader() call.
We found this is setup via the line below using the DebuggingClassWriter which creates the ClassWriter in it's constructor with no reference to the class loader we provided to the TransformingClassLoader.
cglib/cglib/src/main/java/net/sf/cglib/transform/AbstractClassLoader.java
Lines 88 to 89 in dc71e9e
To work around this we have had to extend ClassWriter with a custom version that allows us to pass in the correct class loader (so it doesn't use the implied one via
getClass().getClassLoader()) and then to override the method AbstractClassLoader.loadClass(String) with a custom implementation that sets thei.e.
We then had to override TransformingClassLoader.loadClass(String) and use reflection to get to the ProtectionDomain.
Whilst we have a work around, we also feel it is a bug as the class loader [B] falls out of the picture during the transformation.
Cheers,
Stuart
The text was updated successfully, but these errors were encountered: