Open SerVB opened 3 years ago
I've investigated a bit. There is a difference in System classpath appending:
With static agent, this code is executed in Thread[main,5,main]
and its cl
is ClassLoaders$AppClassLoader
.
With dynamic agent, this code is executed in Thread[Attach Listener,9,system]
and its cl
is null
.
So in the second case, indeed there are no system classes visible by Javassist.
As the result, I've come up with placing the following code to beginning of agentmain
:
try {
Method m = Thread.class.getDeclaredMethod("getThreads");
m.setAccessible(true);
Thread[] threads = (Thread[]) m.invoke(null);
for (Thread t : threads) {
if ("main".equals(t.getName())) {
ClassPool.getDefault().appendClassPath(new LoaderClassPath(t.getContextClassLoader()));
System.out.println("cp from main thread is appended");
break;
}
}
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
e.printStackTrace();
}
Now dynamic variant works, so it can be used as a workaround 😉
What do you think, should this be fixed in the library or is it designed behavior?
I think I solved your problem by refactoring for separation of concerns, see my answer on StackOverflow.
It might still be worth for the maintainer to investigate this issue on the Javassist side, but I think this is optional.
Method m = Thread.class.getDeclaredMethod("getThreads"); m.setAccessible(true);
The idea to access a the private method Thread.getThreads()
is not so nice. There are alternatives I want to mention just in case the Javassist maintainer wants to improve the situation for the attach listener situation discussed here:
We can utilise the knowledge that the Attach Listener
thread is in the system
thread group and the main
thread in its own main
thread group which has the system
group as a parent. There is no direct way to iterate over child groups, but several ThreadGroup
methods to copy child thread groups and/or child threads (optionally recursively) into predefined arrays. So it would be possible to
Attach Listener / system
,main
first and enumerate only there),main / main
thread,A similar approach is described here, you can easily adjust it to your needs.
You can use Thread.getAllStackTraces().keySet()
and then filter the set via stream API in order to find the main thread. I took this idea from here.
Thanks! I think at least there should be a clear message describing possible reasons why this is happened and how one can try to solve it.
You can use
Thread.getAllStackTraces().keySet()
and then filter the set via stream API in order to find the main thread.
You don't even need the stream API, you can just copy&paste the ranged for
loop that was posted, it works just as well on a Set<Thread>
, i.e.:
Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread t : threads) {
if ("main".equals(t.getName())) {
ClassPool.getDefault().appendClassPath(new LoaderClassPath(t.getContextClassLoader()));
System.out.println("cp from main thread is appended");
break;
}
}
(and the try
is not needed).
That said, this is only a workaround and this really ought to be fixed within javassist.
You can use
Thread.getAllStackTraces().keySet()
and then filter the set via stream API in order to find the main thread.You don't even need the stream API
I did not say you need the stream API, I said you can use it. It is just a different, more functional programming style. If you prefer a more procedural approach with for
loops and if
conditions inside instead of filtering a stream, be my guest. I am just wondering how your post adds value to this discussion.
Hey!
It seems like dynamic retransformations of classes work for me only on Java 8 but not on Java 11. In the latter case, I get exceptions from javassist about different not found standard Java classes, for example, the ones directly referenced by me or even from the signature of the method-to-transform.
What should I do to fix that on Java 11? I want to transform classes dynamically here too.
For illustration purposes, I've created a repro file. Here I retransform two classes: one is my own, another is system. I've created both
agentmain
andpremain
to compare. Dynamic variant is executed when a main argument is passed to the app (I pass it as just "o"). After the retransformation, I call two methods (of my own class and of the system one). When the transformation is successful, I receive additional logging ("hi-" and "scaled!").I build
jar
via Gradle using Java 11:On Java 8, both static and dynamic variants work:
On Java 11, dynamic variant doesn't work (it will fork for
hi
method if there is no reference toSystem.out
, for example, justdb.insertBefore("return 22;");
):Please take a look. It was initially asked here: https://stackoverflow.com/questions/64340794/class-retransformation-doesnt-work-for-dynamic-agent-on-java-11. Looks like a bug to me and to a community member.