Skip to content

Commit c180b4f

Browse files
Fix issue with call sites on constructors without DUP bytecode (#9698)
1 parent 1ec528d commit c180b4f

File tree

3 files changed

+173
-2
lines changed

3 files changed

+173
-2
lines changed

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/csi/CallSiteTransformer.java

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import net.bytebuddy.implementation.Implementation;
2424
import net.bytebuddy.jar.asm.ClassVisitor;
2525
import net.bytebuddy.jar.asm.Handle;
26+
import net.bytebuddy.jar.asm.Label;
2627
import net.bytebuddy.jar.asm.MethodVisitor;
2728
import net.bytebuddy.jar.asm.Opcodes;
2829
import net.bytebuddy.jar.asm.Type;
@@ -128,23 +129,43 @@ public MethodVisitor visitMethod(
128129
private static class CallSiteMethodVisitor extends MethodVisitor
129130
implements CallSiteAdvice.MethodHandler {
130131
protected final Advices advices;
132+
protected int lastOpcode;
133+
protected boolean newFollowedByDup;
131134

132135
private CallSiteMethodVisitor(
133136
@Nonnull final Advices advices, @Nonnull final MethodVisitor delegated) {
134137
super(ASM_API, delegated);
135138
this.advices = advices;
136139
}
137140

141+
@Override
142+
public void visitTypeInsn(int opcode, String type) {
143+
lastOpcode = opcode;
144+
if (opcode == Opcodes.NEW) {
145+
newFollowedByDup = false;
146+
}
147+
super.visitTypeInsn(opcode, type);
148+
}
149+
150+
@Override
151+
public void visitInsn(final int opcode) {
152+
if (opcode == Opcodes.DUP) {
153+
newFollowedByDup = lastOpcode == Opcodes.NEW;
154+
}
155+
lastOpcode = opcode;
156+
super.visitInsn(opcode);
157+
}
158+
138159
@Override
139160
public void visitMethodInsn(
140161
final int opcode,
141162
final String owner,
142163
final String name,
143164
final String descriptor,
144165
final boolean isInterface) {
145-
166+
lastOpcode = opcode;
146167
CallSiteAdvice advice = advices.findAdvice(owner, name, descriptor);
147-
if (advice instanceof InvokeAdvice) {
168+
if (applyInvokeAdvice(advice, name, descriptor)) {
148169
invokeAdvice((InvokeAdvice) advice, opcode, owner, name, descriptor, isInterface);
149170
} else {
150171
mv.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
@@ -167,6 +188,7 @@ public void visitInvokeDynamicInsn(
167188
final String descriptor,
168189
final Handle bootstrapMethodHandle,
169190
final Object... bootstrapMethodArguments) {
191+
lastOpcode = Opcodes.INVOKEDYNAMIC;
170192
CallSiteAdvice advice = advices.findAdvice(bootstrapMethodHandle);
171193
if (advice instanceof InvokeDynamicAdvice) {
172194
invokeDynamicAdvice(
@@ -303,6 +325,75 @@ private Type[] methodParamTypesWithThis(String owner, String methodDescriptor) {
303325
methodParameterTypesWithThis.length - 1);
304326
return methodParameterTypesWithThis;
305327
}
328+
329+
protected boolean applyInvokeAdvice(
330+
final CallSiteAdvice advice, final String methodName, final String methodDescriptor) {
331+
if (!(advice instanceof InvokeAdvice)) {
332+
return false;
333+
}
334+
if (!"<init>".equals(methodName)) {
335+
return true;
336+
}
337+
// TODO: do not ignore ctors where there is no DUP after NEW
338+
return newFollowedByDup;
339+
}
340+
341+
// Keep track of the latest opcode to match a DUP with its previous NEW
342+
@Override
343+
public void visitIntInsn(final int opcode, final int operand) {
344+
lastOpcode = opcode;
345+
super.visitIntInsn(opcode, operand);
346+
}
347+
348+
@Override
349+
public void visitVarInsn(final int opcode, final int var) {
350+
lastOpcode = opcode;
351+
super.visitVarInsn(opcode, var);
352+
}
353+
354+
@Override
355+
public void visitFieldInsn(
356+
final int opcode, final String owner, final String name, final String descriptor) {
357+
lastOpcode = opcode;
358+
super.visitFieldInsn(opcode, owner, name, descriptor);
359+
}
360+
361+
@Override
362+
public void visitJumpInsn(final int opcode, final Label label) {
363+
lastOpcode = opcode;
364+
super.visitJumpInsn(opcode, label);
365+
}
366+
367+
@Override
368+
public void visitLdcInsn(final Object value) {
369+
lastOpcode = Opcodes.LDC;
370+
super.visitLdcInsn(value);
371+
}
372+
373+
@Override
374+
public void visitIincInsn(final int var, final int increment) {
375+
lastOpcode = Opcodes.IINC;
376+
super.visitIincInsn(var, increment);
377+
}
378+
379+
@Override
380+
public void visitTableSwitchInsn(
381+
final int min, final int max, final Label dflt, final Label... labels) {
382+
lastOpcode = Opcodes.TABLESWITCH;
383+
super.visitTableSwitchInsn(min, max, dflt, labels);
384+
}
385+
386+
@Override
387+
public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
388+
lastOpcode = Opcodes.LOOKUPSWITCH;
389+
super.visitLookupSwitchInsn(dflt, keys, labels);
390+
}
391+
392+
@Override
393+
public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) {
394+
lastOpcode = Opcodes.MULTIANEWARRAY;
395+
super.visitMultiANewArrayInsn(descriptor, numDimensions);
396+
}
306397
}
307398

308399
private static class CallSiteCtorMethodVisitor extends CallSiteMethodVisitor {
@@ -390,5 +481,14 @@ protected void invokeAdvice(
390481
super.invokeAdvice(advice, opcode, owner, name, descriptor, isInterface);
391482
}
392483
}
484+
485+
@Override
486+
protected boolean applyInvokeAdvice(
487+
final CallSiteAdvice advice, final String methodName, final String descriptor) {
488+
if (isSuperCall) {
489+
return advice instanceof InvokeAdvice && "<init>".equals(methodName);
490+
}
491+
return super.applyInvokeAdvice(advice, methodName, descriptor);
492+
}
393493
}
394494
}

dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/tooling/csi/BaseCallSiteTest.groovy

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,14 @@ class BaseCallSiteTest extends DDSpecification {
193193
final CallSiteTransformer transformer,
194194
final ClassLoader loader = Thread.currentThread().contextClassLoader) {
195195
final classContent = loader.getResourceAsStream("${source.getInternalName()}.class").bytes
196+
return transformType(source, classContent, target, transformer, loader)
197+
}
198+
199+
protected static byte[] transformType(final Type source,
200+
final byte[] classContent,
201+
final Type target,
202+
final CallSiteTransformer transformer,
203+
final ClassLoader loader = Thread.currentThread().contextClassLoader) {
196204
final classFileTransformer = new AgentBuilder.Default()
197205
.type(named(source.className))
198206
.transform(new AgentBuilder.Transformer() {

dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/tooling/csi/CallSiteInstrumentationTest.groovy

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
11
package datadog.trace.agent.tooling.csi
22

33
import datadog.trace.agent.tooling.bytebuddy.csi.CallSiteTransformer
4+
import net.bytebuddy.ByteBuddy
45
import net.bytebuddy.asm.AsmVisitorWrapper
6+
import net.bytebuddy.description.method.MethodDescription
7+
import net.bytebuddy.description.modifier.Ownership
8+
import net.bytebuddy.description.modifier.Visibility
59
import net.bytebuddy.description.type.TypeDescription
610
import net.bytebuddy.dynamic.DynamicType
11+
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy
12+
import net.bytebuddy.implementation.Implementation
13+
import net.bytebuddy.implementation.bytecode.ByteCodeAppender
14+
import net.bytebuddy.implementation.bytecode.StackManipulation
15+
import net.bytebuddy.implementation.bytecode.TypeCreation
16+
import net.bytebuddy.implementation.bytecode.member.MethodInvocation
17+
import net.bytebuddy.implementation.bytecode.member.MethodReturn
18+
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess
19+
import net.bytebuddy.jar.asm.MethodVisitor
720
import net.bytebuddy.jar.asm.Type
821

922
import java.util.concurrent.atomic.AtomicInteger
@@ -103,6 +116,56 @@ class CallSiteInstrumentationTest extends BaseCallSiteTest {
103116
new StringBuilder("test") | "Inconsistent stackmap frames"
104117
}
105118
119+
void 'test new invocation without dup'() {
120+
setup:
121+
def (clazz, bytes) = buildConsumerWithNewAndNoDup()
122+
final source = Type.getType(clazz)
123+
final target = renameType(source, 'Test')
124+
final pointcut = stringReaderPointcut()
125+
final advice = Mock(InvokeAdvice)
126+
final callSiteTransformer = new CallSiteTransformer(mockAdvices([mockCallSites(AFTER, advice, pointcut)]))
127+
128+
when:
129+
final transformedClass = transformType(source, bytes, target, callSiteTransformer)
130+
final transformed = loadClass(target, transformedClass)
131+
final instance = transformed.newInstance()
132+
instance.accept('test')
133+
134+
then:
135+
0 * advice._
136+
}
137+
138+
139+
private static Tuple2<Class<?>, byte[]> buildConsumerWithNewAndNoDup() {
140+
final newType = new ByteBuddy()
141+
.subclass(Cloneable)
142+
.name('foo.CtorIssue')
143+
.modifiers(Visibility.PUBLIC)
144+
.defineMethod("accept", void, Visibility.PUBLIC, Ownership.MEMBER)
145+
.withParameters(String)
146+
.intercept(
147+
new Implementation.Simple(new ByteCodeAppender() {
148+
@Override
149+
ByteCodeAppender.Size apply(MethodVisitor mv,
150+
Implementation.Context ctx,
151+
MethodDescription method) {
152+
StackManipulation compound = new StackManipulation.Compound(
153+
TypeCreation.of(new TypeDescription.ForLoadedType(StringReader)),
154+
// ignore DUP opcode
155+
MethodVariableAccess.REFERENCE.loadFrom(1),
156+
MethodInvocation.invoke(new MethodDescription.ForLoadedConstructor(StringReader.getConstructor(String))),
157+
MethodReturn.VOID
158+
)
159+
final size = compound.apply(mv, ctx)
160+
return new ByteCodeAppender.Size(size.getMaximalSize(), method.getStackSize())
161+
}
162+
})
163+
)
164+
.make()
165+
.load(Thread.currentThread().contextClassLoader, ClassLoadingStrategy.Default.INJECTION)
166+
return Tuple.tuple(newType.loaded, newType.bytes)
167+
}
168+
106169
static class StringCallSites implements CallSites, TestCallSites {
107170

108171
@Override

0 commit comments

Comments
 (0)