Skip to content

Commit 6a4c4c4

Browse files
committed
Replace ASM code-gen with ByteBuddy in afterburner
1 parent f3c81b2 commit 6a4c4c4

20 files changed

+2056
-685
lines changed

afterburner/pom.xml

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ field access and method calls
1515
<url>https://github.com/FasterXML/jackson-modules-base</url>
1616

1717
<properties>
18+
<version.butebuddy>1.7.5</version.butebuddy>
1819
<!-- Generate PackageVersion.java into this directory. -->
1920
<packageVersion.dir>com/fasterxml/jackson/module/afterburner</packageVersion.dir>
2021
<packageVersion.package>${project.groupId}.afterburner</packageVersion.package>
@@ -27,7 +28,7 @@ field access and method calls
2728
Similarly, org.objectweb.asm is shaded... why require?
2829
(will try to hide via resolution directive)
2930
-->
30-
<osgi.import>org.objectweb.asm;resolution:=optional,
31+
<osgi.import>net.bytebuddy;resolution:=optional,
3132
*
3233
</osgi.import>
3334
<!--
@@ -48,9 +49,9 @@ field access and method calls
4849
<artifactId>jackson-databind</artifactId>
4950
</dependency>
5051
<dependency>
51-
<groupId>org.ow2.asm</groupId>
52-
<artifactId>asm</artifactId>
53-
<version>${version.asm}</version>
52+
<groupId>net.bytebuddy</groupId>
53+
<artifactId>byte-buddy</artifactId>
54+
<version>${version.butebuddy}</version>
5455
</dependency>
5556
<dependency> <!-- tests use Jackson annoations -->
5657
<groupId>com.fasterxml.jackson.core</groupId>
@@ -74,7 +75,7 @@ field access and method calls
7475
</plugin>
7576

7677
<plugin>
77-
<!-- We will shade ASM, to simplify deployment, avoid version conflicts -->
78+
<!-- We will shade ByteBuddy, to simplify deployment, avoid version conflicts -->
7879
<groupId>org.apache.maven.plugins</groupId>
7980
<artifactId>maven-shade-plugin</artifactId>
8081
<executions>
@@ -86,13 +87,13 @@ field access and method calls
8687
<configuration>
8788
<artifactSet>
8889
<includes>
89-
<include>org.ow2.asm:asm</include>
90+
<include>net.bytebuddy:byte-buddy</include>
9091
</includes>
9192
</artifactSet>
9293
<relocations>
9394
<relocation>
94-
<pattern>org.objectweb.asm</pattern>
95-
<shadedPattern>com.fasterxml.jackson.module.afterburner.asm</shadedPattern>
95+
<pattern>net.bytebuddy</pattern>
96+
<shadedPattern>com.fasterxml.jackson.module.afterburner.bytebuddy</shadedPattern>
9697
</relocation>
9798
</relocations>
9899
</configuration>
Lines changed: 80 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,36 @@
11
package com.fasterxml.jackson.module.afterburner.deser;
22

3-
import static org.objectweb.asm.Opcodes.*;
4-
5-
import java.lang.reflect.AnnotatedElement;
6-
import java.lang.reflect.Constructor;
7-
import java.lang.reflect.Method;
8-
import java.lang.reflect.Modifier;
9-
10-
import com.fasterxml.jackson.databind.DeserializationContext;
113
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
124
import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator;
135
import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams;
14-
15-
import org.objectweb.asm.ClassWriter;
16-
import org.objectweb.asm.Label;
17-
import org.objectweb.asm.MethodVisitor;
18-
import org.objectweb.asm.Type;
19-
206
import com.fasterxml.jackson.module.afterburner.util.ClassName;
217
import com.fasterxml.jackson.module.afterburner.util.DynamicPropertyAccessorBase;
228
import com.fasterxml.jackson.module.afterburner.util.MyClassLoader;
9+
import com.fasterxml.jackson.module.afterburner.util.bytebuddy.ConstructorCallStackManipulation;
10+
import com.fasterxml.jackson.module.afterburner.util.bytebuddy.SimpleExceptionHandler;
11+
import net.bytebuddy.ByteBuddy;
12+
import net.bytebuddy.ClassFileVersion;
13+
import net.bytebuddy.description.modifier.TypeManifestation;
14+
import net.bytebuddy.description.modifier.Visibility;
15+
import net.bytebuddy.description.type.TypeDescription;
16+
import net.bytebuddy.dynamic.DynamicType;
17+
import net.bytebuddy.dynamic.scaffold.TypeValidation;
18+
import net.bytebuddy.implementation.Implementation;
19+
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
20+
import net.bytebuddy.implementation.bytecode.StackManipulation;
21+
import net.bytebuddy.implementation.bytecode.member.MethodReturn;
22+
23+
import java.lang.reflect.AnnotatedElement;
24+
import java.lang.reflect.Constructor;
25+
import java.lang.reflect.Method;
26+
import java.lang.reflect.Modifier;
27+
28+
import static net.bytebuddy.description.method.MethodDescription.ForLoadedMethod;
29+
import static net.bytebuddy.description.method.MethodDescription.InDefinedShape;
30+
import static net.bytebuddy.description.type.TypeDescription.ForLoadedType;
31+
import static net.bytebuddy.implementation.bytecode.member.MethodInvocation.invoke;
32+
import static net.bytebuddy.implementation.bytecode.member.MethodVariableAccess.REFERENCE;
33+
import static net.bytebuddy.matcher.ElementMatchers.named;
2334

2435
/**
2536
* Helper class that tries to generate {@link ValueInstantiator} class
@@ -99,115 +110,65 @@ protected OptimizedValueInstantiator createSubclass(Constructor<?> ctor, Method
99110
}
100111
}
101112

102-
protected byte[] generateOptimized(ClassName baseName, Constructor<?> ctor, Method factory)
103-
{
104-
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
105-
String superClass = internalClassName(OptimizedValueInstantiator.class.getName());
113+
protected byte[] generateOptimized(ClassName baseName, Constructor<?> ctor, Method factory) {
106114
final String tmpClassName = baseName.getSlashedTemplate();
107-
108-
cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER + ACC_FINAL, tmpClassName, null, superClass, null);
109-
cw.visitSource(baseName.getSourceFilename(), null);
110-
111-
// First: must define 2 constructors:
112-
// (a) default constructor, for creating bogus instance (just calls default instance)
113-
// (b) copy-constructor which takes StdValueInstantiator instance, passes to superclass
114-
final String optimizedValueInstDesc = Type.getDescriptor(OptimizedValueInstantiator.class);
115-
final String stdValueInstDesc = Type.getDescriptor(StdValueInstantiator.class);
116-
117-
// default (no-arg) constructor:
118-
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
119-
mv.visitCode();
120-
mv.visitVarInsn(ALOAD, 0);
121-
mv.visitMethodInsn(INVOKESPECIAL, superClass, "<init>", "()V", false);
122-
mv.visitInsn(RETURN);
123-
mv.visitMaxs(0, 0);
124-
mv.visitEnd();
125-
// then single-arg constructor
126-
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "("+stdValueInstDesc+")V", null, null);
127-
mv.visitCode();
128-
mv.visitVarInsn(ALOAD, 0);
129-
mv.visitVarInsn(ALOAD, 1);
130-
mv.visitMethodInsn(INVOKESPECIAL, superClass, "<init>", "("+stdValueInstDesc+")V", false);
131-
mv.visitInsn(RETURN);
132-
mv.visitMaxs(0, 0);
133-
mv.visitEnd();
134-
135-
// and then non-static factory method to use second constructor (implements base-class method)
136-
// protected abstract OptimizedValueInstantiator with(StdValueInstantiator src);
137-
mv = cw.visitMethod(ACC_PUBLIC, "with", "("
138-
+stdValueInstDesc+")"+optimizedValueInstDesc, null, null);
139-
mv.visitCode();
140-
mv.visitTypeInsn(NEW, tmpClassName);
141-
mv.visitInsn(DUP);
142-
mv.visitVarInsn(ALOAD, 1);
143-
mv.visitMethodInsn(INVOKESPECIAL, tmpClassName, "<init>", "("+stdValueInstDesc+")V", false);
144-
mv.visitInsn(ARETURN);
145-
mv.visitMaxs(0, 0);
146-
mv.visitEnd();
147-
148-
// And then override: public Object createUsingDefault()
149-
mv = cw.visitMethod(ACC_PUBLIC, "createUsingDefault",
150-
"(" +Type.getDescriptor(DeserializationContext.class)+")Ljava/lang/Object;", null, null);
151-
mv.visitCode();
152-
153-
// 19-Apr-2017, tatu: Need to take care to of try catch block...
154-
Label startTryBlock = new Label();
155-
Label endTryBlock = new Label();
156-
Label startCatchBlock = new Label();
157-
158-
// Initiale try-catch block
159-
mv.visitTryCatchBlock(startTryBlock, endTryBlock, startCatchBlock, "java/lang/Exception");
160-
mv.visitLabel(startTryBlock);
161-
162-
// Then new/static-factory call
163-
if (ctor != null) {
164-
addCreator(mv, ctor);
165-
} else {
166-
addCreator(mv, factory);
167-
}
168-
mv.visitInsn(ARETURN);
169-
170-
mv.visitLabel(endTryBlock);
171-
// and then do catch block
172-
mv.visitLabel(startCatchBlock);
173-
mv.visitVarInsn(ASTORE, 2); // push Exception e
174-
mv.visitVarInsn(ALOAD, 0); // this
175-
mv.visitVarInsn(ALOAD, 1); // Arg #1 ("ctxt")
176-
mv.visitVarInsn(ALOAD, 2); // caught exception
177-
// 27-Jul-2017, tatu: as per [modules-base#27], need name not desc here. For reasons.
178-
final String optimizedValueInstName = Type.getInternalName(OptimizedValueInstantiator.class);
179-
mv.visitMethodInsn(INVOKEVIRTUAL,
180-
optimizedValueInstName, "_handleInstantiationProblem",
181-
String.format("(%s%s)Ljava/lang/Object;",
182-
Type.getDescriptor(DeserializationContext.class),
183-
"Ljava/lang/Exception;"),
184-
false);
185-
mv.visitInsn(ARETURN);
186-
187-
// and call it all done
188-
mv.visitMaxs(0, 0);
189-
mv.visitEnd();
190-
191-
cw.visitEnd();
192-
return cw.toByteArray();
115+
final DynamicType.Builder<?> builder =
116+
new ByteBuddy(ClassFileVersion.JAVA_V5)
117+
.with(TypeValidation.DISABLED)
118+
.subclass(OptimizedValueInstantiator.class) //default strategy ensures that all constructors are created
119+
.name(tmpClassName)
120+
.modifiers(Visibility.PUBLIC, TypeManifestation.FINAL)
121+
.method(named("with"))
122+
.intercept(
123+
//call the constructor of this method that takes a single StdValueInstantiator arg
124+
//the required arg is in the position 1 of the method's local variables
125+
new Implementation.Simple(
126+
new ByteCodeAppender.Simple(
127+
new ConstructorCallStackManipulation.OfInstrumentedType.OneArg(
128+
REFERENCE.loadFrom(1)
129+
),
130+
MethodReturn.REFERENCE
131+
)
132+
)
133+
134+
)
135+
.method(named("createUsingDefault"))
136+
.intercept(
137+
new SimpleExceptionHandler(
138+
creatorInvokerStackManipulation(ctor, factory),
139+
creatorExceptionHandlerStackManipulation(),
140+
Exception.class,
141+
1 //we added a new local variable in the catch block
142+
)
143+
);
144+
145+
146+
return builder.make().getBytes();
193147
}
194148

195-
protected void addCreator(MethodVisitor mv, Constructor<?> ctor)
196-
{
197-
Class<?> owner = ctor.getDeclaringClass();
198-
String valueClassInternal = Type.getInternalName(owner);
199-
mv.visitTypeInsn(NEW, valueClassInternal);
200-
mv.visitInsn(DUP);
201-
mv.visitMethodInsn(INVOKESPECIAL, valueClassInternal, "<init>", "()V",
202-
owner.isInterface());
149+
private StackManipulation creatorInvokerStackManipulation(Constructor<?> ctor, Method factory) {
150+
final StackManipulation invokeManipulation =
151+
null == ctor ?
152+
invoke(new ForLoadedMethod(factory)) :
153+
new ConstructorCallStackManipulation.KnownConstructorOfExistingType(ctor);
154+
return new StackManipulation.Compound(
155+
invokeManipulation,
156+
MethodReturn.REFERENCE
157+
);
203158
}
204159

205-
protected void addCreator(MethodVisitor mv, Method factory)
206-
{
207-
Class<?> owner = factory.getDeclaringClass();
208-
Class<?> valueClass = factory.getReturnType();
209-
mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(owner),
210-
factory.getName(), "()"+Type.getDescriptor(valueClass),
211-
owner.isInterface());
160+
private StackManipulation creatorExceptionHandlerStackManipulation() {
161+
final TypeDescription typeDescription = new ForLoadedType(OptimizedValueInstantiator.class);
162+
final InDefinedShape methodDescription =
163+
typeDescription.getDeclaredMethods().filter(named("_handleInstantiationProblem")).getOnly();
164+
165+
return new StackManipulation.Compound(
166+
REFERENCE.storeAt(2), //push exception to new local
167+
REFERENCE.loadFrom(0), //'this'
168+
REFERENCE.loadFrom(1), //Arg #1 ("ctxt")
169+
REFERENCE.loadFrom(2), //exception
170+
invoke(methodDescription),
171+
MethodReturn.REFERENCE
172+
);
212173
}
213174
}

0 commit comments

Comments
 (0)