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.
Related
I'm trying to write a java instrumentation agent using byte buddy. My goal is to replace a java standard library method call with a proxy call of my own. I was suggested to use Byte Buddy's MemberSubstitution to achieve this. I used this and this questions from SO for my reference.
I'm using Intellij IDEA for coding. My Agent code is split into multiple files as follows:
MyFirstAgent.java
public class MyFirstAgent {
public static void premain(String agentArgs, Instrumentation inst) {
new AgentBuilder.Default()
.type(ElementMatchers.any())
.transform(new ByteBuddyTransformer())
.with(AgentBuilder.Listener.StreamWriting.toSystemOut())
.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
.installOn(inst);
}
ByteBuddyTransformer.java
public class ByteBuddyTransformer implements AgentBuilder.Transformer {
#Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription,
ClassLoader classLoader, JavaModule javaModule) {
try {
return builder.visit(MemberSubstitution.relaxed()
.method(named("add"))
.replaceWith(MyClass.class.getMethod("printLine"))
.on(any()));
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return builder;
}
}
MyClass.java
public class MyClass {
public boolean printLine(){
System.out.println("This is the proxy!");
return true;
}
}
And the application that I want to instrument is in another Intellij IDEA project with the following:
Main.java
public class Main {
public static void main(String[] args) {
ClassToMonitor classToMonitor = new ClassToMonitor();
classToMonitor.bar();
}
}
ClassToMonitor.java
package com.company;
import java.util.ArrayList;
import java.util.Arrays;
public class ClassToMonitor {
public void bar() {
// create an empty array list with an initial capacity
ArrayList<Integer> arrlist = new ArrayList<Integer>(5);
// use add() method to add elements in the list
arrlist.add(15);
// print all the elements available in list
for (Integer number : arrlist) {
System.out.println("Number = " + number);
}
}
}
When I build the fat jar of my agent and run it with my application, I get the following error:
[Byte Buddy] ERROR com.company.ClassToMonitor [jdk.internal.loader.ClassLoaders$AppClassLoader#2626b418, unnamed module #385e9564, loaded=false]
java.lang.IllegalStateException: Cannot invoke public boolean com.company.MyClass.printLine() on [class java.util.ArrayList, E]
I can provide the full error message if required. Also, I'm new to Java and Instrumentation in general so I might be missing something fundamental here, please kindly excuse me and point it out if that's the case.
For substitution to work, the target method needs to accept the same arguments as the replaced method, in your case an int. Also, since you are calling a member, the implicit first argument of your class needs to be the receiver type, i.e. ArrayList or any super type, even Object. Also, your replacement method needs to be static:
public class MyClass {
public static boolean printLine(Object ignored, int ignored2){
System.out.println("This is the proxy!");
return true;
}
}
MemberSubstitution is still not as flexible as it is supposed to be. You can however already inject custom byte code using the chained step if that is what you want.
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…
I'm pretty new to groovy, and scripting in java generally, and I really
hope there is a simple solution for my problem.
In our application, the users can execute groovy scripts which they write
themselves, and we need to control what those scripts can and can not do.
I read a lot of stuff about sandboxing groovy, but either I am looking at
wrong places or I am overlooking the obvious.
To make it simple, I have a small example which demonstrates the problem.
This is my class loader which should prevent java.lang.System from being
loaded and available to scripts:
public class MyClassLoader extends ClassLoader {
#Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name.startsWith("java.lang.System")) {
throw new ClassNotFoundException("Class not found: " + name);
}
return super.loadClass(name);
}
}
And this is a simple program that tries to call System.currentTimeMillis():
public static void main(String[] args) {
String code = "java.lang.System.currentTimeMillis();";
ClassLoader classLoader = new MyClassLoader();
Thread.currentThread().setContextClassLoader(classLoader);
GroovyShell shell = new GroovyShell();
Script script = shell.parse(code);
Object result = script.run();
log.debug(result);
}
MyClassLoader throws exceptions for java.lang.SystemBeanInfo
and java.lang.SystemCustomizer, but the code executes.
Same thing happens if I use javax.script classes:
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("Groovy");
Object o = engine.eval(code);
log.debug(o);
And if I try it with JavaScript engine, it works as expected (just replace
"Groovy" with "JavaScript" in the above example).
Can anyone help me with this? BTW, I'm using groovy-all-1.8.8.jar, with
jdk1.7.0_55.
Thanks
I can recommend Groovy Sandbox for this purpose. In contrast to SecureASTCustomizer it will check if an execution is allowed dynamically at runtime. It intercepts every method call, object allocations, property/attribute access, array access, and so on - and you thus have a very fine grained control on what you allow (white-listing).
Naturally the configuration on what is allowed is very important. For example you may want to allow using Strings and use methods like substring, but probably not the execute method on String, which could be exploited with something like 'rm -R ~/*'.execute().
Creating a configuration that is really safe is a challenge, and it is more difficult the more you allow.
Downside of the Groovy Sandbox is that the code must run with the interceptor registered and you will have a performance penalty during execution.
This image [1] shows an example from a project where we used Groovy Sandbox for Groovy code entered by the user. The code is run to valide the script - so if the statement there would actually be executed as part of it, the application would have exited before I could do the screenshot ;)
Perhaps you'd be interested in using a SecureASTCustomizer in conjunction with a CompilerConfiguration. If you are concerned with security, an explicit white list might be better than a black list.
def s = new SecureASTCustomizer()
s.importsWhiteList = [ 'a.legal.Klass', 'other.legal.Klass' ]
def c = new CompilerConfiguration()
c.addCompilationCustomizers(s)
def sh = new GroovyShell(c)
Take a look at that class, it contains a lot of options that are ready to use.
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyShell;
import groovy.lang.Script;
public class SandboxGroovyClassLoader extends ClassLoader {
public SandboxGroovyClassLoader(ClassLoader parent) {
super(parent);
}
#Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name.startsWith("java.lang.System"))
return null;
return super.loadClass(name);
}
#Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (name.startsWith("java.lang.System"))
return null;
return super.loadClass(name, resolve);
}
static void runWithGroovyClassLoader() throws Exception {
System.out.println("Begin runWithGroovyClassLoader");
String code = "def hello_world() { java.lang.System.currentTimeMillis(); };";
GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
Class<?> scriptClass = groovyClassLoader.parseClass(code);
Object scriptInstance = scriptClass.newInstance();
Object result = scriptClass.getDeclaredMethod("hello_world", new Class[] {}).invoke(scriptInstance, new Object[] {});
System.out.println(result);
groovyClassLoader.close();
System.out.println("End runWithGroovyClassLoader");
}
static void runWithSandboxGroovyClassLoader() throws Exception {
System.out.println("Begin runWithSandboxGroovyClassLoader");
ClassLoader parentClassLoader = SandboxGroovyClassLoader.class.getClassLoader();
SandboxGroovyClassLoader classLoader = new SandboxGroovyClassLoader(parentClassLoader);
String code = "def hello_world() { java.lang.System.currentTimeMillis(); };";
GroovyClassLoader groovyClassLoader = new GroovyClassLoader(classLoader);
Class<?> scriptClass = groovyClassLoader.parseClass(code);
Object scriptInstance = scriptClass.newInstance();
Object result = scriptClass.getDeclaredMethod("hello_world", new Class[] {}).invoke(scriptInstance, new Object[] {});
System.out.println(result);
groovyClassLoader.close();
System.out.println("End runWithSandboxGroovyClassLoader");
}
static void runWithSandboxGroovyShellClassLoader() throws Exception {
System.out.println("Begin runWithSandboxGroovyShellClassLoader");
String code = "java.lang.System.currentTimeMillis();";
ClassLoader parentClassLoader = SandboxGroovyClassLoader.class.getClassLoader();
SandboxGroovyClassLoader classLoader = new SandboxGroovyClassLoader(parentClassLoader);
Thread.currentThread().setContextClassLoader(classLoader);
GroovyShell shell = new GroovyShell();
Script script = shell.parse(code);
Object result = script.run();
System.out.println(result);
System.out.println("End runWithSandboxGroovyShellClassLoader");
}
public static void main(String[] args) throws Exception {
runWithGroovyClassLoader();
runWithSandboxGroovyClassLoader();
runWithSandboxGroovyShellClassLoader();
}
}
Is it what you want ?
I was trying to modify class dynamically, such as call sleep() before a line. I attached agent to a jvm during runtime using Attach method. Then I got target class from jvm, and modified it(Add a line to call sleep()). And I got redine class error. I am using JDK1.6. I am using ASM core API to modify class.
The Error:
Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields)
at sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:124)
Is there something wrong with ASM code? Actually my ASM code finished its job(to Add a line to call sleep()). Does current jvm not support retransform class? It seems failed to execute retransformClasses(). Does retransformClasses() not support the ASM operation(to add a line into a method to call sleep())? Any ideas? thx
EDIT:
The class which I want modify:
import java.util.concurrent.TimeUnit;
public class Person {
public String name = "abc";
public String address = "xxxxx" ;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void sayHello() throws InterruptedException {
System.out.println("aaaaaaaaaa");
System.out.println("Hello World!");
TimeUnit.SECONDS.sleep(120);
System.out.println("dd");
}
public void sayHello2() {
System.out.println("aaaaaaaaaa1");
System.out.println("Hello World!2");
}
public static void main (String args[]) {
try {
Person p = new Person();
p.sayHello(); // linenumber #9. A line to call Sleep() should be added before #here.
p.sayHello2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
My ASM code:
public void visitMethodInsn(int arg0, String arg1, String arg2, String arg3) {
Label la=new Label();
mv.visitLabel(la);
int linenumber=la.getOffset();
if(linenumber==9) {
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/util/concurrent/TimeUnit", "SECONDS", "Ljava/util/concurrent/TimeUnit;");
mv.visitLdcInsn(new Long("5"));
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/util/concurrent/TimeUnit", "sleep", "(J)V");
super.visitMethodInsn(arg0, arg1, arg2, arg3);
}
}
Not looking at your code yet, I think I can suggest something. When a class is first loaded, in addition to storing the class's byte codes, the JVM also has tables where it keeps track of the types of fields and the signatures of methods in each class.
The error you are seeing would suggest that the class was loaded, this signature information was stored and then you tried to add the method after that.
If you instead put your agent jar onto the command line, you can do things before the class is loaded for the first time. If you add your method before the signature info is stored away, you should be good.
If you have to connect the agent after the process is already launched, you may be able to transform the class but you may only be able to transform it without changing the set of fields, their types, or the methods, or their signatures. In other words, you may be able to change the byte codes but you have to not invalidate the previously-stored meta information.
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