Skip to content

Commit f877a0e

Browse files
committed
Refactor AsyncAwaitInstrumentationAgent (move responsibilities to RuntimeBytecodeInjector were necessary)
1 parent a861d89 commit f877a0e

File tree

5 files changed

+115
-76
lines changed

5 files changed

+115
-76
lines changed

net.tascalate.async.examples/src/main/java/net/tascalate/async/examples/generator/GeneratorExample.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import java.util.concurrent.Future;
4242

4343
import net.tascalate.async.AsyncGenerator;
44+
import net.tascalate.async.AsyncValue;
4445
import net.tascalate.async.Sequence;
4546
import net.tascalate.async.YieldReply;
4647
import net.tascalate.async.async;
@@ -97,7 +98,7 @@ CompletableFuture<String> mergeStrings(String delimeter) {
9798
}
9899

99100
@async
100-
CompletionStage<String> iterateStringsEx(int z) {
101+
AsyncValue<String> iterateStringsEx(int z) {
101102
z += 2;
102103
System.out.println(z);
103104
int x = 3;

net.tascalate.async.tools.javaagent/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
<excludes>
128128
<exclude>META-INF/maven/**/*.*</exclude>
129129
<exclude>**/module-info.class</exclude>
130+
<exclude>META-INF/MANIFEST.MF</exclude>
130131
</excludes>
131132
</filter>
132133
<filter>

net.tascalate.async.tools.javaagent/src/main/java/net/tascalate/async/tools/instrumentation/AbstractInstrumentationAgent.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444

4545
public abstract class AbstractInstrumentationAgent {
4646

47-
private final Logger log = LoggerFactory.getLogger(getClass());
47+
protected final Logger log = LoggerFactory.getLogger(getClass());
4848

4949
protected AbstractInstrumentationAgent() {
5050
}

net.tascalate.async.tools.javaagent/src/main/java/net/tascalate/async/tools/instrumentation/AsyncAwaitInstrumentationAgent.java

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
*/
2525
package net.tascalate.async.tools.instrumentation;
2626

27-
import java.io.PrintStream;
2827
import java.lang.instrument.ClassFileTransformer;
2928
import java.lang.instrument.Instrumentation;
3029
import java.util.HashSet;
@@ -57,30 +56,28 @@ public static void premain(String args, Instrumentation instrumentation) throws
5756
System.setProperty(JavaFlowClassTransformer.class.getName(), "true");
5857
System.setProperty(AsyncAwaitInstrumentationAgent.class.getName(), "true");
5958

60-
if (getJdkVersion() < 9) {
59+
int jdkVersion = getJdkVersion();
60+
if (jdkVersion < 9) {
61+
if (log.isDebugEnabled()) {
62+
log.debug("JDK verion " + jdkVersion + " is less than 9, no lambda instrumentation patch required.");
63+
}
6164
return;
6265
}
6366

6467
// Need to patch lambda metafactory to support agent instrumentation for lambda classes
6568
// in JDK 9+
6669
try {
67-
instrumentation.redefineClasses(RuntimeBytecodeInjector.modifyLambdaMetafactory());
68-
System.setOut(new PrintStream(System.out, true) {
70+
if (!RuntimeBytecodeInjector.isInjectionApplied()) {
71+
log.debug("Applying lambda post-processing injection...");
72+
instrumentation.redefineClasses(RuntimeBytecodeInjector.modifyLambdaMetafactory());
73+
log.debug("Lambda post-processing injection is applied.");
74+
} else {
75+
log.warn("Lambda post-processing injection was already applied, probably by some other agent.");
76+
}
77+
RuntimeBytecodeInjector.installTransformer(new RuntimeBytecodeInjector.LambdaClassTransformer() {
6978
@Override
70-
public void println(Object o) {
71-
if (o instanceof Object[]) {
72-
Object[] params = (Object[])o;
73-
if (params.length == 4 &&
74-
RuntimeBytecodeInjector.isValidCaller(params[0]) &&
75-
params[1] instanceof Class &&
76-
params[2] instanceof byte[] &&
77-
(params[3] == null || params[3] instanceof byte[])) {
78-
79-
agent.enhanceLambdaClass((Class<?>)params[1], (byte[])params[2], params);
80-
return;
81-
}
82-
}
83-
super.println(o);
79+
public byte[] transform(Class<?> lambdaOwningClass, byte[] lambdaClassBytes) throws Throwable {
80+
return agent.transformLambdaClass(lambdaOwningClass, lambdaClassBytes);
8481
}
8582
});
8683
} catch (Throwable ex) {
@@ -113,24 +110,26 @@ protected ClassFileTransformer createRetransformableTransformer(String args, Ins
113110
return new AsyncAwaitClassFileTransformer(continuationsTransformer, instrumentation);
114111
}
115112

116-
void enhanceLambdaClass(Class<?> lambdaOwningClass, byte[] input, Object[] output) {
113+
byte[] transformLambdaClass(Class<?> lambdaOwningClass, byte[] input) throws Throwable {
117114
try {
115+
String className = null;
118116
if (log.isDebugEnabled()) {
119-
log.debug("Start transforming lambda class " + ClassEmitters.classNameOf(input) + ", defined in " + lambdaOwningClass);
117+
className = ClassEmitters.classNameOf(input);
118+
log.debug("Start transforming lambda class " + className + ", defined in " + lambdaOwningClass.getName());
120119
}
121-
byte[] altered = continuationsTransformer.transform(lambdaOwningClass.getClassLoader(),
122-
null, null, // Neither name, nor class
123-
lambdaOwningClass.getProtectionDomain(),
124-
input);
125-
if (input != altered && altered != null) {
126-
if (log.isDebugEnabled()) {
127-
log.debug("Done transforming lambda class " + ClassEmitters.classNameOf(input) + ", defined in " + lambdaOwningClass);
128-
}
129-
}
130-
output[3] = null == altered ? input : altered;
120+
byte[] output = continuationsTransformer.transform(lambdaOwningClass.getClassLoader(),
121+
null, null, // Neither name, nor class
122+
lambdaOwningClass.getProtectionDomain(),
123+
input);
124+
if (log.isDebugEnabled()) {
125+
log.debug("Done transforming lambda class " + className +
126+
", defined in " + lambdaOwningClass.getName() +
127+
", modified = " + (input != output && output != null));
128+
}
129+
return output;
131130
} catch (Throwable ex) {
132-
ex.printStackTrace();
133-
output[3] = input;
131+
log.error("Error transforming lambda class defined in " + lambdaOwningClass.getName(), ex);
132+
throw ex;
134133
}
135134

136135
}

net.tascalate.async.tools.javaagent/src/main/java/net/tascalate/async/tools/instrumentation/RuntimeBytecodeInjector.java

Lines changed: 80 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.io.IOException;
3232
import java.io.InputStream;
3333
import java.io.PrintStream;
34+
import java.lang.annotation.Documented;
3435
import java.lang.instrument.ClassDefinition;
3536

3637
import net.tascalate.asmx.ClassReader;
@@ -43,29 +44,22 @@
4344
class RuntimeBytecodeInjector {
4445

4546
private static final String CLASS = "java.lang.invoke.InnerClassLambdaMetafactory";
46-
47-
private static ClassDefinition loadClassDefinition(Class<?> clazz) throws IOException {
48-
ByteArrayOutputStream out = new ByteArrayOutputStream();
49-
byte[] buff = new byte[1024];
50-
try (InputStream in = Object.class.getResourceAsStream('/' + clazz.getName().replace('.', '/') + ".class")) {
51-
int c = 0;
52-
while ((c= in.read(buff)) > 0) {
53-
out.write(buff, 0, c);
54-
}
55-
}
56-
out.close();
57-
byte[] bytes = out.toByteArray();
58-
return new ClassDefinition(clazz, bytes);
59-
}
6047

61-
private static ClassDefinition loadClassDefinition(String className) throws ClassNotFoundException, IOException {
62-
return loadClassDefinition(Class.forName(className));
48+
static interface LambdaClassTransformer {
49+
byte[] transform(Class<?> lambdaOwningClass, byte[] lambdaClassBytes) throws Throwable;
6350
}
6451

65-
static boolean isValidCaller(Object o) {
66-
return o != null && CLASS.equals(o.getClass().getName());
52+
static boolean isInjectionApplied() {
53+
Class<?> cls;
54+
try {
55+
cls = ClassLoader.getSystemClassLoader().loadClass(CLASS);
56+
} catch (ClassNotFoundException ex) {
57+
throw new RuntimeException(ex);
58+
}
59+
Documented anno = cls.getAnnotation(Documented.class);
60+
return anno != null;
6761
}
68-
62+
6963
static ClassDefinition modifyLambdaMetafactory() throws ClassNotFoundException, IOException {
7064
ClassDefinition original = loadClassDefinition(CLASS);
7165
ClassReader classReader = new ClassReader(new ByteArrayInputStream(original.getDefinitionClassFile()));
@@ -93,14 +87,7 @@ public void visitMethodInsn(int opcode, String owner, String name, String descri
9387
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
9488
visitVarInsn(bytesType.getOpcode(ISTORE), bytesVar);
9589

96-
/*
97-
Type stringType = Type.getType(String.class);
98-
visitFieldInsn(GETSTATIC, systemType.getInternalName(), "out", printStreamType.getDescriptor());
99-
visitLdcInsn("HERE WE ARE: ");
100-
visitMethodInsn(INVOKEVIRTUAL, printStreamType.getInternalName(), "print", Type.getMethodDescriptor(Type.VOID_TYPE, stringType), false);
101-
*/
102-
103-
visitInsn(ICONST_4);
90+
visitInsn(ICONST_3);
10491
visitTypeInsn(ANEWARRAY, objectType.getInternalName());
10592
visitVarInsn(objectsType.getOpcode(ISTORE), params);
10693

@@ -117,30 +104,19 @@ public void visitMethodInsn(int opcode, String owner, String name, String descri
117104
visitFieldInsn(GETFIELD, "java/lang/invoke/AbstractValidatingLambdaMetafactory", "targetClass", classType.getDescriptor());
118105
visitInsn(AASTORE);
119106

120-
// params[2] = inBytes
107+
// params[2] = inBytes, after call replaced by outBytes
121108
visitVarInsn(objectsType.getOpcode(ILOAD), params);
122109
visitInsn(ICONST_2);
123110
visitVarInsn(bytesType.getOpcode(ILOAD), bytesVar);
124111
visitInsn(AASTORE);
125112

126-
// params[3] = inBytes, after call replaced by outBytes
127-
visitVarInsn(objectsType.getOpcode(ILOAD), params);
128-
visitInsn(ICONST_3);
129-
visitVarInsn(bytesType.getOpcode(ILOAD), bytesVar);
130-
visitInsn(AASTORE);
131-
132113
visitFieldInsn(GETSTATIC, systemType.getInternalName(), "out", printStreamType.getDescriptor());
133114
visitVarInsn(objectsType.getOpcode(ILOAD), params);
134-
visitMethodInsn(INVOKEVIRTUAL, printStreamType.getInternalName(), "println", Type.getMethodDescriptor(Type.VOID_TYPE, objectType), false);
135-
136-
/*
137-
visitFieldInsn(GETSTATIC, systemType.getInternalName(), "out", printStreamType.getDescriptor());
138-
visitMethodInsn(INVOKEVIRTUAL, printStreamType.getInternalName(), "println", Type.getMethodDescriptor(Type.VOID_TYPE), false);
139-
*/
115+
visitMethodInsn(INVOKEVIRTUAL, printStreamType.getInternalName(), "print", Type.getMethodDescriptor(Type.VOID_TYPE, objectType), false);
140116

141-
// get outBytes, params[3]
117+
// get outBytes, params[2]
142118
visitVarInsn(objectsType.getOpcode(ILOAD), params);
143-
visitInsn(ICONST_3);
119+
visitInsn(ICONST_2);
144120
visitInsn(AALOAD);
145121
} else {
146122
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
@@ -149,6 +125,18 @@ public void visitMethodInsn(int opcode, String owner, String name, String descri
149125
};
150126

151127
ClassVisitor cv = new ClassVisitor(ASM9, classWriter) {
128+
@Override
129+
public void visit(int version,
130+
int access,
131+
String name,
132+
String signature,
133+
String superName,String[] interfaces) {
134+
super.visit(version, access, name, signature, superName, interfaces);
135+
Type annoType = Type.getType(Documented.class);
136+
visitAnnotation(annoType.getDescriptor(), true).visitEnd();
137+
}
138+
139+
@Override
152140
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
153141
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
154142
if ("spinInnerClass".equals(name) || "generateInnerClass".equals(name)) {
@@ -162,4 +150,54 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si
162150
classReader.accept(cv, ClassReader.EXPAND_FRAMES);
163151
return new ClassDefinition(original.getDefinitionClass(), classWriter.toByteArray());
164152
}
153+
154+
static void installTransformer(final LambdaClassTransformer transformer) {
155+
System.setOut(new PrintStream(System.out, true) {
156+
@Override
157+
public void print(Object o) {
158+
if (o instanceof Object[]) {
159+
Object[] params = (Object[])o;
160+
if (params.length == 3 &&
161+
RuntimeBytecodeInjector.isValidCaller(params[0]) &&
162+
params[1] instanceof Class &&
163+
params[2] instanceof byte[]) {
164+
165+
byte[] inBytes = (byte[])params[2];
166+
byte[] outBytes = inBytes;
167+
try {
168+
outBytes = transformer.transform((Class<?>)params[1], inBytes);
169+
} catch (Throwable ex) {
170+
171+
}
172+
params[2] = outBytes == null ? inBytes : outBytes;
173+
return;
174+
}
175+
}
176+
super.println(o);
177+
}
178+
});
179+
}
180+
181+
private static boolean isValidCaller(Object o) {
182+
return o != null && CLASS.equals(o.getClass().getName());
183+
}
184+
185+
private static ClassDefinition loadClassDefinition(Class<?> clazz) throws IOException {
186+
ByteArrayOutputStream out = new ByteArrayOutputStream();
187+
byte[] buff = new byte[1024];
188+
try (InputStream in = Object.class.getResourceAsStream('/' + clazz.getName().replace('.', '/') + ".class")) {
189+
int c = 0;
190+
while ((c= in.read(buff)) > 0) {
191+
out.write(buff, 0, c);
192+
}
193+
}
194+
out.close();
195+
byte[] bytes = out.toByteArray();
196+
return new ClassDefinition(clazz, bytes);
197+
}
198+
199+
private static ClassDefinition loadClassDefinition(String className) throws ClassNotFoundException, IOException {
200+
return loadClassDefinition(Class.forName(className));
201+
}
202+
165203
}

0 commit comments

Comments
 (0)