Skip to content

Commit a3e9bda

Browse files
Add After callsites support for void methods (#8116)
1 parent 8b2bb8e commit a3e9bda

File tree

8 files changed

+147
-5
lines changed

8 files changed

+147
-5
lines changed

buildSrc/call-site-instrumentation-plugin/src/main/java/datadog/trace/plugin/csi/impl/CallSiteSpecification.java

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -570,11 +570,20 @@ public AfterSpecification(
570570

571571
@Override
572572
protected void validateAdvice(@Nonnull final ValidationContext context) {
573-
if (advice.isVoidReturn()) {
574-
context.addError(ErrorCode.ADVICE_AFTER_SHOULD_NOT_RETURN_VOID);
575-
}
576-
if (findReturn() == null) {
577-
context.addError(ErrorCode.ADVICE_AFTER_SHOULD_HAVE_RETURN);
573+
if (shouldNotUseReturn(pointcut)) {
574+
if (!advice.isVoidReturn()) {
575+
context.addError(ErrorCode.ADVICE_AFTER_VOID_METHOD_SHOULD_RETURN_VOID);
576+
}
577+
if (findReturn() != null) {
578+
context.addError(ErrorCode.ADVICE_AFTER_VOID_METHOD_SHOULD_NOT_HAVE_RETURN);
579+
}
580+
} else {
581+
if (advice.isVoidReturn()) {
582+
context.addError(ErrorCode.ADVICE_AFTER_SHOULD_NOT_RETURN_VOID);
583+
}
584+
if (findReturn() == null) {
585+
context.addError(ErrorCode.ADVICE_AFTER_SHOULD_HAVE_RETURN);
586+
}
578587
}
579588
if (!pointcut.isConstructor()) {
580589
if (!isStaticPointcut() && !includeThis()) {
@@ -588,6 +597,10 @@ protected void validateAdvice(@Nonnull final ValidationContext context) {
588597
super.validateAdvice(context);
589598
}
590599

600+
private boolean shouldNotUseReturn(final MethodType type) {
601+
return !type.isConstructor() && type.isVoidReturn();
602+
}
603+
591604
@Override
592605
public String toString() {
593606
return "@CallSite.After(" + signature + ")";

buildSrc/call-site-instrumentation-plugin/src/main/java/datadog/trace/plugin/csi/util/ErrorCode.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,20 @@ public String apply(final Object[] objects) {
359359
}
360360
},
361361

362+
ADVICE_AFTER_VOID_METHOD_SHOULD_RETURN_VOID {
363+
@Override
364+
public String apply(final Object[] objects) {
365+
return "After advice for void method should return void";
366+
}
367+
},
368+
369+
ADVICE_AFTER_VOID_METHOD_SHOULD_NOT_HAVE_RETURN {
370+
@Override
371+
public String apply(final Object[] objects) {
372+
return "After advice for void method should not contain @Return annotated parameters";
373+
}
374+
},
375+
362376
ADVICE_AFTER_SHOULD_HAVE_RETURN {
363377
@Override
364378
public String apply(final Object[] objects) {

buildSrc/call-site-instrumentation-plugin/src/test/groovy/datadog/trace/plugin/csi/impl/AdviceGeneratorTest.groovy

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,36 @@ final class AdviceGeneratorTest extends BaseCsiPluginTest {
473473
}
474474
}
475475

476+
477+
@CallSite(spi = CallSites)
478+
class AfterAdviceWithVoidReturn {
479+
@CallSite.After("void java.lang.StringBuilder.setLength(int)")
480+
static void after(@CallSite.This StringBuilder self, @CallSite.Argument(0) int length) {
481+
}
482+
}
483+
484+
void 'test after advice with void return'() {
485+
setup:
486+
final spec = buildClassSpecification(AfterAdviceWithVoidReturn)
487+
final generator = buildAdviceGenerator(buildDir)
488+
489+
when:
490+
final result = generator.generate(spec)
491+
492+
then:
493+
assertNoErrors result
494+
assertCallSites(result.file) {
495+
advices(0) {
496+
pointcut('java/lang/StringBuilder', 'setLength', '(I)V')
497+
statements(
498+
'handler.dupInvoke(owner, descriptor, StackDupMode.COPY);',
499+
'handler.method(opcode, owner, name, descriptor, isInterface);',
500+
'handler.advice("datadog/trace/plugin/csi/impl/AdviceGeneratorTest$AfterAdviceWithVoidReturn", "after", "(Ljava/lang/StringBuilder;I)V");',
501+
)
502+
}
503+
}
504+
}
505+
476506
private static AdviceGenerator buildAdviceGenerator(final File targetFolder) {
477507
return new AdviceGeneratorImpl(targetFolder, pointcutParser())
478508
}

buildSrc/call-site-instrumentation-plugin/src/test/groovy/datadog/trace/plugin/csi/impl/AdviceSpecificationTest.groovy

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,4 +542,26 @@ class AdviceSpecificationTest extends BaseCsiPluginTest {
542542
then:
543543
0 * context.addError(_, _)
544544
}
545+
546+
547+
@CallSite(spi = CallSites)
548+
class AfterWithVoidWrongAdvice {
549+
@CallSite.After("void java.lang.String.getChars(int, int, char[], int)")
550+
static String after(@CallSite.AllArguments final Object[] args, @CallSite.Return final String result) {
551+
return result;
552+
}
553+
}
554+
555+
void 'test after advice with void should not use @Return'() {
556+
setup:
557+
final context = mockValidationContext()
558+
final spec = buildClassSpecification(AfterWithVoidWrongAdvice)
559+
560+
when:
561+
spec.advices.each { it.validate(context) }
562+
563+
then:
564+
1 * context.addError(ErrorCode.ADVICE_AFTER_VOID_METHOD_SHOULD_RETURN_VOID, _)
565+
1 * context.addError(ErrorCode.ADVICE_AFTER_VOID_METHOD_SHOULD_NOT_HAVE_RETURN, _)
566+
}
545567
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ class BaseCallSiteTest extends DDSpecification {
8383
return buildPointcut(String.getDeclaredMethod('concat', String))
8484
}
8585

86+
protected static Pointcut stringBuilderSetLengthPointcut() {
87+
return buildPointcut(StringBuilder.getDeclaredMethod('setLength', int))
88+
}
89+
8690
protected static Pointcut stringReaderPointcut() {
8791
return buildPointcut(StringReader.getDeclaredConstructor(String))
8892
}

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import net.bytebuddy.dynamic.DynamicType
1010
import net.bytebuddy.jar.asm.Opcodes
1111
import net.bytebuddy.jar.asm.Type
1212

13+
import java.util.function.BiConsumer
1314
import java.util.function.BiFunction
1415
import java.util.function.Consumer
1516

@@ -227,6 +228,38 @@ class CallSiteTransformerTest extends BaseCallSiteTest {
227228
helperCLass != null
228229
}
229230

231+
void 'test after advice with void return'() {
232+
setup:
233+
final source = Type.getType(StringBuilderSetLengthExample)
234+
final target = renameType(source, 'Test')
235+
final pointcut = stringBuilderSetLengthPointcut()
236+
final advice = Mock(InvokeAdvice)
237+
final callSite = Type.getType(StringBuilderSetLengthCallSite)
238+
final callSiteTransformer = new CallSiteTransformer(mockAdvices([mockCallSites(advice, pointcut)]))
239+
final sb = new StringBuilder("Hello World!")
240+
final int length = 5
241+
StringBuilderSetLengthCallSite.LAST_CALL = null
242+
243+
when:
244+
final transformedClass = transformType(source, target, callSiteTransformer)
245+
final instance = loadType(target, transformedClass) as BiConsumer<StringBuilder, Integer>
246+
instance.accept(sb, length)
247+
248+
then:
249+
1 * advice.apply(_ as MethodHandler, Opcodes.INVOKEVIRTUAL, pointcut.type, pointcut.method, pointcut.descriptor, false) >> { params ->
250+
final args = params as Object[]
251+
final handler = args[0] as MethodHandler
252+
final owner = args[2] as String
253+
final descriptor = args[4] as String
254+
handler.dupInvoke(owner, descriptor, COPY)
255+
handler.method(args[1] as int, owner, args[3] as String, descriptor, args[5] as Boolean)
256+
handler.advice(callSite.getInternalName(), "after", "(Ljava/lang/StringBuilder;I)V")
257+
}
258+
sb.toString() == 'Hello'
259+
StringBuilderSetLengthCallSite.LAST_CALL[0] == sb
260+
StringBuilderSetLengthCallSite.LAST_CALL[1] == length
261+
}
262+
230263
static class InstrumentationHelper {
231264

232265
private static Consumer<Object[]> callback = null // codenarc forces the lowercase name
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package datadog.trace.agent.tooling.csi;
2+
3+
public class StringBuilderSetLengthCallSite {
4+
5+
public static volatile Object[] LAST_CALL;
6+
7+
public static void after(final StringBuilder builder, int length) {
8+
LAST_CALL = new Object[] {builder, length};
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package datadog.trace.agent.tooling.csi;
2+
3+
import java.util.function.BiConsumer;
4+
import org.slf4j.Logger;
5+
import org.slf4j.LoggerFactory;
6+
7+
public class StringBuilderSetLengthExample implements BiConsumer<StringBuilder, Integer> {
8+
9+
private static final Logger LOGGER = LoggerFactory.getLogger(StringBuilderSetLengthExample.class);
10+
11+
public void accept(final StringBuilder builder, final Integer length) {
12+
LOGGER.debug("Before apply {} {}", builder, length);
13+
builder.setLength(length);
14+
LOGGER.debug("After apply {} {}", builder, length);
15+
}
16+
}

0 commit comments

Comments
 (0)