Transform only one class quickly using a Java Agent - java

I want to measure the startup time of a server without a considerable overhead.
What I actually want to measure is the time from server process execution to the time that the server starts listening to a well-known port.
For example, I want to measure the startup time of a simple Netty Server. i.e. the time from startup to the time it is ready to accept requests.
I developed a Java Agent using Byte-Buddy.
public class Agent {
public static void premain(String arg, Instrumentation instrumentation) {
new AgentBuilder.Default()
.type(ElementMatchers.named("io.netty.bootstrap.AbstractBootstrap"))
.transform((builder, typeDescription, classLoader, javaModule) ->
builder.visit(Advice.to(TimeAdvice.class)
.on(ElementMatchers.named("bind").and(ElementMatchers.takesArguments(SocketAddress.class)))))
.installOn(instrumentation);
}
}
Following is the source code for TimeAdvice
public class TimeAdvice {
#Advice.OnMethodExit
static void exit(#Advice.Origin String method) {
System.out.println(String.format("Server started. Current Time (ms): %d", System.currentTimeMillis()));
System.out.println(String.format("Server started. Current Uptime (ms): %d",
ManagementFactory.getRuntimeMXBean().getUptime()));
}
}
With this agent, the startup time is around 1400ms. However, when I measure the startup time by modifying the server code, the startup time of the server is around 650ms.
Therefore, it seems that there is a considerable overhead with byte-buddy Java agent when considering the startup time.
I also tried another Java Agent with Javassist.
public class Agent {
private static final String NETTY_CLASS = "io/netty/bootstrap/AbstractBootstrap";
public static void premain(String arg, Instrumentation instrumentation) {
instrumentation.addTransformer((classLoader, s, aClass, protectionDomain, bytes) -> {
if (NETTY_CLASS.equals(s)) {
System.out.println(aClass);
long start = System.nanoTime();
// Javassist
try {
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("io.netty.bootstrap.AbstractBootstrap");
CtMethod m = cc.getDeclaredMethod("bind", new CtClass[]{cp.get("java.net.SocketAddress")});
m.insertAfter("{ System.out.println(\"Server started. Current Uptime (ms): \" + " +
"java.lang.management.ManagementFactory.getRuntimeMXBean().getUptime());}");
byte[] byteCode = cc.toBytecode();
cc.detach();
return byteCode;
} catch (Exception ex) {
ex.printStackTrace();
} finally {
System.out.println(String.format("Agent - Transformation Time (ms): %d", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
}
}
return null;
});
}
}
With this agent, the startup time is around 800ms.
How can I minimize the overhead and measure the startup time? Is there a way to directly transform a specific class without going through all classes? If I can transform a class directly, I think I should be able to minimize the overhead as much as possible.

Since you are in a premain, you likely measure the loading and initialization time for a lot of classes that have not been used before. It might be perfectly possible that a significant amount of these classes will be loaded and initialized anyway at a later time when the application uses them for the first time, without being measure as “startup time”, so having this time moved to the measured startup time might not be an actual issue.
Note that you are using lambda expressions in both variants, which causes the initialization of their backend provided by the JRE. In case of OpenJDK, it uses ASM under the hood, but since it has been repackaged to avoid conflicts with applications using ASM, it’s not the same classes Byte-Buddy is using internally, so you’re paying the price of initializing ASM twice here.
As said, if these classes will be used anyway, i.e. if the application will use lambda expressions or method references later-on, you should not worry about this, as “optimizing” it will only shift the initialization to a later time. But if the application is not using lambda expressions or method references or you want to remove this time span from the measured startup time at all costs, you may resort to an ordinary interface implementation, using an inner class or letting Agent implement the interface.
To reduce the startup time further, you may use ASM directly, skipping the initialization of the Byte-Buddy classes, e.g.
import java.lang.instrument.*;
import java.lang.management.ManagementFactory;
import java.security.ProtectionDomain;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.AdviceAdapter;
public class Agent extends ClassVisitor implements ClassFileTransformer {
private static final String NETTY_CLASS = "io/netty/bootstrap/AbstractBootstrap";
public static void premain(String arg, Instrumentation instrumentation) {
instrumentation.addTransformer(new Agent());
}
public Agent() {
super(Opcodes.ASM5);
}
public byte[] transform(ClassLoader loader, String className, Class<?> cl,
ProtectionDomain pd, byte[] classfileBuffer) {
if(!NETTY_CLASS.equals(className)) return null;
ClassReader cr = new ClassReader(classfileBuffer);
ClassWriter cw = new ClassWriter(cr, 0);
synchronized(this) {
super.cv = cw;
try { cr.accept(this, 0); }
finally { super.cv = null; }
}
return cw.toByteArray();
}
#Override
public MethodVisitor visitMethod(
int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if(name.equals("bind")
&& desc.equals("(Ljava/net/SocketAddress;)Lio/netty/channel/ChannelFuture;")) {
return new AdviceAdapter(Opcodes.ASM5, mv, access, name, desc) {
#Override
protected void onMethodExit(int opcode) {
super.visitMethodInsn(Opcodes.INVOKESTATIC,
Agent.class.getName().replace('.', '/'),
"injectedMethod", "()V", false);
super.onMethodExit(opcode);
}
};
}
return mv;
}
public static void injectedMethod() {
System.out.printf("Server started. Current Time (ms): %d",
System.currentTimeMillis());
System.out.printf("Server started. Current Uptime (ms): %d",
ManagementFactory.getRuntimeMXBean().getUptime());
}
}
(not tested)
Obviously, this code is more complicated than the code using Byte-Buddy, so you have to decide which trade-off to make.
ASM is already very lightweight. Going even deeper would imply doing the class file transformation using only a ByteBuffer and a HashMap; that’s possible, but surely not a road you want to go…

Related

Verify whether a method parameter is used in the method body

I have an interface which looks like the following
interface Evaluator {
boolean requiresP2();
EvalResult evaluate(Param1 p1, Param2 p2, Param3 p3);
// some more methods
}
This interface is implemented by several classes. The parameter p2 of the evaluate method is used by some and not used by others. The method requiresP2 basically returns a boolean telling whether the evaluate method uses p2 or not.
Now, this questions may appear a little weird out of context but believe me, it makes sense in our use case. Plus, it would require a lot of time to refactor all the code to eliminate the need for the requiresP2 method so I would appreciate if we discuss solutions other than a top-to-bottom refactoring of the codebase.
The problem is that the return value of method requiresP2 is based on how the evaluate method is implemented. Therefore everyone must ensure that they update the requiresP2 method when they change the evaluate method.
I am looking for ways so that this can be enforced by the compiler/unit-tests/linters rather than leaving it to the developer's memory.
EDIT: I am still exploring the applicability of mocking frameworks to this problem.
I thought that I could reflection in unit tests to inspect evaluate's body in the unit test to check if it refers to p2 or not and then making sure it matches with the value returned by requiresP2 method but it seems that it is not possible to inspect method body using reflection.
I am looking for suggestions on how to do this. Any input is appreciated.
There is another option you did not mention: a Static Code Analysis tool.
You can use the SonarQube + SonarLint combination in order to get your desired enforcement:
Use the SonarQube server in order to create a new static code analysis rule, which will be based on the interface you are using and your unique use case.
Then install SonarLint on your IDE/IDEs (Eclipse and IntelliJ are both supported), and connect it to the SonarQube server.
This way the static code analysis scan will detect improper usage of your interface and indicate this with a visual marking in the IDE, on the relevant code lines (which is actually linting your code).
You can use ASM to check whether the parameter is used.
To add it to your project using e.g. Apache Ivy, you would add this to ivy.xml:
<dependency org="org.ow2.asm" name="asm" rev="6.1.1" />
Or do the equivalent for Maven, Gradle, etc. Then you can check on the parameter by:
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
// . . .
public static boolean usesP2(Evaluator evaluator) {
AtomicBoolean usesP2 = new AtomicBoolean(false);
String internalName = evaluator.getClass().getName().replace('.', '/');
String classFileResource = "/" + internalName + ".class";
ClassVisitor visitor = new ClassVisitor(Opcodes.ASM6) {
#Override
public MethodVisitor visitMethod(int access, String name,
String desc, String signature, String[] exceptions) {
if ("evaluate".equals(name)) {
return new MethodVisitor(Opcodes.ASM6) {
#Override
public void visitVarInsn(final int insn, final int slot) {
if (slot == 2) usesP2.set(true);
}
};
}
return super.visitMethod(access, name, desc, signature, exceptions);
}
};
try (InputStream is = Evaluator.class.getResourceAsStream(classFileResource)) {
ClassReader reader = new ClassReader(is);
reader.accept(visitor, 0);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return usesP2.get();
}
public static void assertCorrectlyDocumentsP2(Evaluator evaluator) {
boolean usesP2 = usesP2(evaluator);
if (usesP2 && !evaluator.requiresP2()) {
throw new AssertionError(evaluator.getClass().getName() +
" uses P2 without documenting it");
}
if (!usesP2 && evaluator.requiresP2()) {
throw new AssertionError(evaluator.getClass().getName() +
" says it uses P2 but does not");
}
}
Unit tests:
#Test
public void testFalsePositive() {
assertCorrectlyDocumentsP2(new FalsePositive());
}
#Test
public static void testFalseNegative() {
assertCorrectlyDocumentsP2(new FalseNegative());
}
(This supposes there are two bad Evaluators, FalsePositive and FalseNegative, one of which documents that it uses P2 but doesn't, and the other which doesn't document that it uses P2 even though it does, respectively.)
Note: In usesP2 we check for a variable instruction (an instruction which accesses a local variable) in slot 2 of the stack frame. The slots are numbered from 0, and the first one is this. P2 is in slots 2 only because Evaluator::evaluate is an instance method. If it were a static method, we would have to check if slot 1 were used in order to detect if parameter P2 were used. Caveat lector.

Using java securityManager blocks me from reading files

In my java code I call another 3rd party java class.
I want to catch that latter System.exit() exit code
So I use security-manager as suggested in this post
The problem is that I cannot read files now, I get permissions error
as seen in that post.
How can I catch the exit code and still read files?
Published class MyClass {
class MySecurityManager extends SecurityManager {
#Override
public void checkExit(int status) {
throw new SecurityException();
}
}
public void foo() {
MySecurityManager secManager = new MySecurityManager();
System.setSecurityManager(secManager);
try {
ConfigValidator.main(new String[]{"-dirs", SdkServiceConfig.s.PROPERTIES_FILE_PATH});
new FileInputStream(new File("/Users/eladb/WorkspaceQa/sdk-service/src/main/resources/convert_conditions.sh"));
} catch (SecurityException e) {
//Do something if the external code used System.exit()
String a = "1";
}
catch (Exception e) {
logger.error("failed converting properties file to proto", e);
}
}
}
You have two separate problems: Your trusted code cannot read the file, while the untrusted third-party library can still call System#exit unhindered. The former can be easily circumvented by granting further privileges to the trusted code; the latter is a tad trickier to address.
A bit of background
Privilege assignment
Code (the ProtectionDomains encapsulated by a thread's AccessControlContext) generally gets assigned Permissions in two ways: Statically, by the ClassLoader, upon class definition, and/or dynamically, by the Policy in effect. Other, less frequently encountered alternatives, exist as well: DomainCombiners, for instance, can modify AccessControlContexts' domains (and therefore the effective permissions of their respective code that is subject to authorization) on the fly, and custom domain implementations may use their own logic for permission implication, possibly disregarding or altering the semantics of the policy. By default the permission set of a domain is the union of its static and dynamic permissions. As for how exactly classes are mapped to domains, it is, for the most part, up to the loader's implementation. By default, all classes, JAR'ed or otherwise, residing beneath the same class path entry, are grouped under the same domain. More restrictive class loaders may choose to e.g. allocate a domain per class, which could be used to prevent communication even between classes within the same package.
Privilege evaluation
Under the default SecurityManager, for a privileged operation (an invocation of any method having a SecurityManager#checkXXX within its body) to succeed, every domain (of every class of every method) of the effective AccessControlContext must have been assigned, as explained above, the permission being checked. Recall however that the context need not necessarily represent "the truth" (the actual call stack)—system code gets optimized away early on, while AccessController#doPrivileged calls, along with the DomainCombiner potentially coupled to the AccessControlContext can modify the context's domains, and the authorization algorithm in its entirety, consequently.
Problem and workarounds
The issue with System#exit is that the corresponding permission (RuntimePermission("exitVM.*")) is one amongst few that are statically assigned by the default application class loader (sun.misc.Launcher$AppClassLoader) to all domains associated with classes loaded from the class path.
A number of alternatives come to mind:
Installing a custom SecurityManager which denies the particular right based on, e.g., the class attempting to terminate the JVM process.
Loading the third-party library from a "remote" location (a directory outside of the class path), so that it gets treated as "untrusted" code by its class loader.
Authoring and installing a different application class loader, which does not assign the extraneous permission.
Plugging a custom domain combiner into the access control context, which replaces, at the time of an authorization decision, all third-party domains with equivalent ones that do not have the offending permission.
I should, for the sake of completeness, note that at the Policy level, unfortunately, nothing can be done to negate statically assigned permissions.
The first option is overall the most convenient one, but I will not explore it further because:
The default SecurityManager is quite flexible, thanks to the handful of components (AccessController et al.) it interacts with. The background section in the beginning aimed to serve as a reminder of that flexibility, which "quick-n'-dirty" method overrides tend to cripple.
Careless modifications of the default implementation might cause (system) code to misbehave in curious ways.
Frankly, because it's boring―it's the one-size-fits-all solution perpetually advocated, while the fact that the default manager was standardized in 1.2 for a reason has long been forgotten.
The second alternative is easy to implement but impractical, complicating either development or the build process. Assuming you are not planning to invoke the library solely reflectively, or aided by interfaces present on the class path, it would have to be present initially, during development, and relocated before execution.
The third is, at least in the context of a standalone Java SE application, fairly straightforward and should not pose too much of a burden on performance. It is the approach I will favour herein.
The last option is the most novel and least convenient. It is hard to securely implement, has the greatest potential for performance degradation, and burdens client code with ensuring presence of the combiner prior to every delegation to untrusted code.
Proposed solution
The custom ClassLoader
The following is to be used as the replacement of the default application loader, or alternatively as the context class loader, or the loader used to load at least the untrusted classes. There is nothing novel to this implementation—all it does is prevent delegation to the default application class loader when the class in question is assumed to not be a system one. URLClassLoader#findClass, in turn, does not assign RuntimePermission("exitVM.*") to the domains of the classes it defines.
package com.example.trusted;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.regex.Pattern;
public class ClasspathClassLoader extends URLClassLoader {
private static final Pattern SYSTEM_CLASS_PREFIX = Pattern.compile("((java(x)?|sun|oracle)\\.).*");
public ClasspathClassLoader(ClassLoader parent) {
super(new URL[0], parent);
String[] classpath = System.getProperty("java.class.path").split(File.pathSeparator);
for (String classpathEntry : classpath) {
try {
if (!classpathEntry.endsWith(".jar") && !classpathEntry.endsWith("/")) {
// URLClassLoader assumes paths without a trailing '/' to be JARs by default
classpathEntry += "/";
}
addURL(new URL("file:" + classpathEntry));
}
catch (MalformedURLException mue) {
System.err.println(MessageFormat.format("Erroneous class path entry [{0}] skipped.", classpathEntry));
}
}
}
#Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> ret;
synchronized (getClassLoadingLock(name)) {
ret = findLoadedClass(name);
if (ret != null) {
return ret;
}
if (SYSTEM_CLASS_PREFIX.matcher(name).matches()) {
return super.loadClass(name, resolve);
}
ret = findClass(name);
if (resolve) {
resolveClass(ret);
}
}
return ret;
}
}
If you also wish to fine-tune the domains assigned to loaded classes, you will additionally have to override findClass. The following variant of the loader is a very crude attempt at doing so. constructClassDomain therein merely creates one domain per class path entry (which is more or less the default), but can be modified to do something different.
package com.example.trusted;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.ByteBuffer;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public final class ClasspathClassLoader extends URLClassLoader {
private static final Pattern SYSTEM_CLASS_PREFIX = Pattern.compile("((java(x)?|sun|oracle)\\.).*");
private static final List<WeakReference<ProtectionDomain>> DOMAIN_CACHE = new ArrayList<>();
// constructor, loadClass same as above
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
URL classOrigin = getClassResource(name);
if (classOrigin == null) {
return super.findClass(name);
}
URL classCodeSourceOrigin = getClassCodeSourceResource(classOrigin);
if (classCodeSourceOrigin == null) {
return super.findClass(name);
}
return defineClass(name, readClassData(classOrigin), constructClassDomain(classCodeSourceOrigin));
}
private URL getClassResource(String name) {
return AccessController.doPrivileged((PrivilegedAction<URL>) () -> getResource(name.replace(".", "/") + ".class"));
}
private URL getClassCodeSourceResource(URL classResource) {
for (URL classpathEntry : getURLs()) {
if (classResource.getPath().startsWith(classpathEntry.getPath())) {
return classpathEntry;
}
}
return null;
}
private ByteBuffer readClassData(URL classResource) {
try {
BufferedInputStream in = new BufferedInputStream(classResource.openStream());
ByteArrayOutputStream out = new ByteArrayOutputStream();
int i;
while ((i = in.read()) != -1) {
out.write(i);
}
return ByteBuffer.wrap(out.toByteArray());
}
catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
private ProtectionDomain constructClassDomain(URL classCodeSourceResource) {
ProtectionDomain ret = getCachedDomain(classCodeSourceResource);
if (ret == null) {
CodeSource cs = new CodeSource(classCodeSourceResource, (Certificate[]) null);
DOMAIN_CACHE.add(new WeakReference<>(ret = new ProtectionDomain(cs, getPermissions(cs), this, null)));
}
return ret;
}
private ProtectionDomain getCachedDomain(URL classCodeSourceResource) {
for (WeakReference<ProtectionDomain> domainRef : DOMAIN_CACHE) {
ProtectionDomain domain = domainRef.get();
if (domain == null) {
DOMAIN_CACHE.remove(domainRef);
}
else if (domain.getCodeSource().implies(new CodeSource(classCodeSourceResource, (Certificate[]) null))) {
return domain;
}
}
return null;
}
}
The "unsafe" code
package com.example.untrusted;
public class Test {
public static void testExitVm() {
System.out.println("May I...?!");
System.exit(-1);
}
}
The entry point
package com.example.trusted;
import java.security.AccessControlException;
import java.security.Permission;
import com.example.untrusted.Test;
public class Main {
private static final Permission EXIT_VM_PERM = new RuntimePermission("exitVM.*");
public static void main(String... args) {
System.setSecurityManager(new SecurityManager());
try {
Test.testExitVm();
}
catch (AccessControlException ace) {
Permission deniedPerm = ace.getPermission();
if (EXIT_VM_PERM.implies(deniedPerm)) {
ace.printStackTrace();
handleUnauthorizedVmExitAttempt(Integer.parseInt(deniedPerm.getName().replace("exitVM.", "")));
}
}
}
private static void handleUnauthorizedVmExitAttempt(int exitCode) {
System.out.println("here let me do it for you");
System.exit(exitCode);
}
}
Testing
Packaging
Place the loader and the main class in one JAR (let's call it trusted.jar) and the demo untrusted class in another (untrusted.jar).
Assigning privileges
The default Policy (sun.security.provider.PolicyFile) is backed by the file at <JRE>/lib/security/java.policy, as well as any of the files referenced by the policy.url.n properties in <JRE>/lib/security/java.security. Modify the former (the latter should hopefully be empty by default) as follows:
// Standard extensions get all permissions by default
grant codeBase "file:${{java.ext.dirs}}/*" {
permission java.security.AllPermission;
};
// no default permissions
grant {};
// trusted code
grant codeBase "file:///path/to/trusted.jar" {
permission java.security.AllPermission;
};
// third-party code
grant codeBase "file:///path/to/untrusted.jar" {
permission java.lang.RuntimePermission "exitVM.-1", "";
};
Note that it is virtually impossible to get components extending the security infrastructure (custom class loaders, policy providers, etc.) to work properly without granting them AllPermission.
Running
Run:
java -classpath "/path/to/trusted.jar:/path/to/untrusted.jar" -Djava.system.class.loader=com.example.trusted.ClasspathClassLoader com.example.trusted.Main
The privileged operation should succeed.
Next comment out the RuntimePermission under untrusted.jar, within the policy file, and re-run. The privileged operation should fail.
As a closing note, when debugging AccessControlExceptions, running with -Djava.security.debug=access=domain,access=failure,policy can help track down offending domains and policy configuration issues.

ClassTransformer not executing

USE CASE
I'm attempting to write a Java SE8 ClassFileTransformer implementation. The goal of this is Debugging. I'm well aware of BTrace but it doesn't really fit the bill for what I'm trying to do. What that is, is internal method level inspection. BTrace limits its breakpoints to entry/exit. I wanted to get into the details of this.
So I figured I'd do this myself with ASM now hard can it be?
(ASM is a byte code manipulation library used by ByteBuddy and BTrace)
PROBLEM
So I started by defining a simple ClassFileTransformer.
public class PreMainInjection {
public static void premain(String agentArgs,
Instrumentation inst) {
inst.addTransformer(new EntryPoint(), true);
}
}
public class EntryPoint implements ClassFileTransformer {
public EntryPoint() { }
#Override
public byte[] transform(ClassLoader classloader, String name,
Class<?> clazz, ProtectionDomain prot, byte[] data) {
System.out.printf("Loaded: %s\n", name);
}
}
And this worked perfectly :D I see a list of all the classes as the application I'm inspecting start.
So now I bring in ASM.
public class PreMainInjection {
public static void premain(String agentArgs,
Instrumentation inst) {
inst.addTransformer(new EntryPoint(), true);
}
}
public class EntryPoint implements ClassFileTransformer {
public EntryPoint() { }
#Override
public byte[] transform(ClassLoader classloader, String name,
Class<?> clazz, ProtectionDomain prot, byte[] data) {
ClassReader reader = new ClassReader(bytes);
ClassNode node = new ClassNode();
ClassWriter writer = new ClassWriter(
ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);
reader.accept(node, ClassReader.EXPAND_FRAMES);
System.out.printf("Name: %s", node.name);
node.accept(writer);
return writer.toByteArray();
}
}
This works... so far as my app doesn't break. But I never see a single debug output.
So what is going on?
To expand further if my agent just does
System.out.printf("Loaded %s\n", name);
return null;
The app still works and I can see the outputs. So I'm deeply confused.
To answer your first question how hard ASM can be: very hard.
To get to your actual point: you are probably getting an exception before reaching the print output. With a class file transformer, exceptions are suppressed if they escape the transform method. Did you try to wrap the code in a try-catch block to see if an exception is raised?
There is also a chance that you did not bundle ASM with your agent. In this case, you would get an error rather than an exception during your transformer's execution. Finally, I would try your agent without frame computation as this requires access to all referenced class files which can also be a source of problems.
So I appear to be hitting a weird edge case
ClassNode's internal fields are only instantiated after a full visit of the ClassReader has been conducted. As that is the case it is statically provable System.out.printf("Name: %s", node.name); will always return a NullPointerException at compile time.
So I believe the JVM simply doesn't load my ClassFileTransformer.
Either way I have the system working.

Override Thread.sleep()

We have few classes which extends a base class. We noticed that we use quit a few sleeps method and we wanted to log when a sleep occurs. Is there a way to override the Thread.sleep method in which I can add some custom logic ( ie logging) and then just call the actual Thread.sleep()? This way I wouldn't have to change all the places where Thread.sleep is being used in my bases classes. I'm open to other options as well.
You cannot override Thread.sleep method, you cannot instrument or transform it either as it's a native method. One way is to automatically add logging to all places which call Thread.sleep using a Java Agent.
While the following approach works and is quite fun, in your case it's probably much better to refactor all calls to the Thread.sleep into a separate method and add the logging there.
You can find an introduction to Java Agents here. In a nutshell it's a special mechanism which allows (among other) transformation of loaded Java byte code. The following example of an Java Agent class automatically enhances all calls to the Thread.sleep with System.out logging and measure time spent in the method:
package fi.schafer.agent;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.expr.ExprEditor;
import javassist.expr.MethodCall;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
public class LoggingAgent {
public static void premain(String agentArgument, Instrumentation instrumentation) throws Exception {
instrumentation.addTransformer(new ClassFileTransformer() {
#Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
return doClass(className, classBeingRedefined, classfileBuffer);
}
});
}
/**
* Method enhances calls to Thread.sleep with logging.
*/
private static byte[] doClass(String name, Class clazz, byte[] b) {
ClassPool pool = ClassPool.getDefault();
CtClass cl = null;
try {
cl = pool.makeClass(new java.io.ByteArrayInputStream(b));
final CtMethod[] targetMethods = cl.getDeclaredMethods();
for (CtMethod targetMethod : targetMethods) {
targetMethod.instrument(new ExprEditor() {
public void edit(final MethodCall m) throws CannotCompileException {
if ("java.lang.Thread".equals(m.getClassName()) && "sleep".equals(m.getMethodName())) {
m.replace("{long startMs = System.currentTimeMillis(); " +
"$_ = $proceed($$); " +
"long endMs = System.currentTimeMillis();" +
"System.out.println(\"Logging Thread.sleep call execution, ms: \" + (endMs-startMs));}");
}
}
});
return cl.toBytecode();
}
} catch (Exception e) {
System.err.println("Could not instrument " + name
+ ", exception : " + e.getMessage());
} finally {
if (cl != null) {
cl.detach();
}
}
return b;
}
}
You will need to compile it into a loggerAgent.jar file and include the following META-INF/MANIFEST.MF in it:
Manifest-Version: 1.0
Premain-Class: fi.schafer.agent.LoggingAgent
Boot-Class-Path: javassist.jar
Download JavaAssist and put it into same folder as your jar with compiled Agent. Run your application with parameter -javaagent:loggerAgent.jar.
You can download a full example. Just extract it, open folder release and run the application with java -cp loggerAgent.jar -javaagent:loggerAgent.jar Test
More information and more examples can be found in this excellent article.

How to get the Groovy generated java source code

We have some legacy code with Groovy, and we want to remove Groovy from the application, so, we need to get the java source code generated after using the gmaven plug-in.
Basically, in other words I am dynamically generating new classes (using gmaven Groovy maven plug in) and I would like to be able to obtain the java source code of such generated classes.
I researched a little bit and can see that the only goals for this plug in are
<goal>generateStubs</goal>
<goal>compile</goal>
<goal>generateTestStubs</goal>
<goal>testCompile</goal>
I can't see any goal that allows you to obtain the fully implemented java source code, the stub code is not enough for us as we need the final implementation source code in order to get rid of Groovy.
I'm not very familiar with the gmaven plugin, but I assume it compiles the groovy code into byte code. In this case, you can use a byte code decompiler, there is a nice list here. In the past I've used JAD and it was quite nice. The best ones will also try to create meaningful variable names based on class names.
One warning though - Groovy objects are derived from GObject, not java.lang.Object, so you would probably need to keep the groovy jar until the groovy->java porting is done. Also, be prepared that it won't be a very easy to read java...
It may be out of your scope (1 year old) but I fought against the same problem and found a method to retrieve the algorithm (not the java source code) from the decompiled groovy classes.
You may want to take a look : http://michael.laffargue.fr/blog/2013/11/02/decompiling-groovy-made-classes/
The generated stubs will be useless for you. They are just what their names suggests: stubs.
The stubs are only useful when doing joint java/groovy compilation. That's because there are two compilers involved in a java/groovy mixed project.
Parse groovy
Create stubs
Compile java and stubs (using javac)
Continue groovy compilation (using groovyc)
The groovy code will be compiled using groovyc compiler and the result is byte code.
This is an example of a generated stub:
package maba.groovy;
import java.lang.*;
import java.io.*;
import java.net.*;
import java.util.*;
import groovy.lang.*;
import groovy.util.*;
#groovy.util.logging.Log4j() public class Order
extends java.lang.Object implements
groovy.lang.GroovyObject {
public groovy.lang.MetaClass getMetaClass() { return (groovy.lang.MetaClass)null;}
public void setMetaClass(groovy.lang.MetaClass mc) { }
public java.lang.Object invokeMethod(java.lang.String method, java.lang.Object arguments) { return null;}
public java.lang.Object getProperty(java.lang.String property) { return null;}
public void setProperty(java.lang.String property, java.lang.Object value) { }
public int getPrice() { return (int)0;}
public void setPrice(int value) { }
public int getQuantity() { return (int)0;}
public void setQuantity(int value) { }
#java.lang.Override() public java.lang.String toString() { return (java.lang.String)null;}
}
As you can see there is nothing useful. And you will still depend on some groovy libraries.
This question has been on the mailing-list some time ago [0]. To summarize: Groovy to Java is hard to achieve since there are language constructs and APIs (if you do want to totally remove the Groovy dependency) that are not available in Java.
Especially with the introduction of call-site caching and other performance optimizing techniques the generated Java code would look a lot like this (for the matter of simplicity I just threw some script into JD-GUI [1]):
public class script1351632333660 extends Script
{
public script1351632333660()
{
script1351632333660 this;
CallSite[] arrayOfCallSite = $getCallSiteArray();
}
public script1351632333660(Binding arg1)
{
Binding context;
CallSite[] arrayOfCallSite = $getCallSiteArray();
ScriptBytecodeAdapter.invokeMethodOnSuperN($get$$class$groovy$lang$Script(), this, "setBinding", new Object[] { context });
}
public Object run()
{
CallSite[] arrayOfCallSite = $getCallSiteArray(); Object items = ScriptBytecodeAdapter.createList(new Object[0]);
Object[] item = (Object[])ScriptBytecodeAdapter.castToType(ScriptBytecodeAdapter.createList(new Object[] { "Fluff", arrayOfCallSite[1].callConstructor($get$$class$java$util$Date()), (Integer)DefaultTypeTransformation.box(11235813) }), $get$array$$class$java$lang$Object());
arrayOfCallSite[2].call(items, item);
arrayOfCallSite[3].callCurrent(this, items);
ValueRecorder localValueRecorder = new ValueRecorder();
try
{
Object tmp102_101 = items; localValueRecorder.record(tmp102_101, 8);
Object tmp126_121 = arrayOfCallSite[4].call(tmp102_101, new script1351632333660._run_closure1(this)); localValueRecorder.record(tmp126_121, 14); if (DefaultTypeTransformation.booleanUnbox(tmp126_121)) localValueRecorder.clear(); else ScriptBytecodeAdapter.assertFailed(AssertionRenderer.render("assert items.findAll { it }", localValueRecorder), null); } finally {
localValueRecorder.clear(); throw finally; } return null; return null; }
static { __$swapInit();
Long localLong1 = (Long)DefaultTypeTransformation.box(0L);
__timeStamp__239_neverHappen1351632333665 = localLong1.longValue();
Long localLong2 = (Long)DefaultTypeTransformation.box(1351632333665L);
__timeStamp = localLong2.longValue(); }
class _run_closure1 extends Closure implements GeneratedClosure { public _run_closure1(Object _thisObject) { super(_thisObject); }
public Object doCall(Object it) { CallSite[] arrayOfCallSite = $getCallSiteArray(); return it; return null;
}
// ...
[0] http://groovy.329449.n5.nabble.com/Java-lt-gt-Groovy-converters-td337442.html
[1] http://java.decompiler.free.fr

Categories