@@ -426,6 +426,71 @@ void partialArgumentsWithBeforeAdvice() {
426426 });
427427 }
428428
429+ /**
430+ * Captures two of three arguments positionally. Before the fix, Stream.sorted() without a
431+ * comparator threw ClassCastException (ArgumentSpecification is not Comparable). The parameters
432+ * TreeMap is keyed by advice-parameter index, so the stream is already in the correct order —
433+ * sorted() must not be called. The reversed case verifies that parameterIndices follows the
434+ * advice signature order, not the pointcut index order.
435+ */
436+ @ CallSite (spi = CallSites .class )
437+ public static class MultiplePartialArgumentsBeforeAdvice {
438+ /** Captures args 0 and 1 in the same order as the pointcut. */
439+ @ CallSite .Before (
440+ "java.lang.String java.lang.String.format(java.util.Locale, java.lang.String, java.lang.Object[])" )
441+ public static void before (
442+ @ CallSite .Argument (0 ) java .util .Locale locale , @ CallSite .Argument (1 ) String format ) {}
443+
444+ /**
445+ * Captures the same two args but with their advice positions reversed. parameterIndices must be
446+ * {1, 0} (advice order), not {0, 1} (pointcut index order).
447+ */
448+ @ CallSite .Before (
449+ "java.lang.String java.lang.String.format(java.util.Locale, java.lang.String, java.lang.Object[])" )
450+ public static void beforeReversed (
451+ @ CallSite .Argument (1 ) String format , @ CallSite .Argument (0 ) java .util .Locale locale ) {}
452+ }
453+
454+ @ Test
455+ void multiplePartialArgumentsWithBeforeAdvice () {
456+ CallSiteSpecification spec =
457+ buildClassSpecification (MultiplePartialArgumentsBeforeAdvice .class );
458+ AdviceGenerator generator = buildAdviceGenerator (buildDir );
459+
460+ CallSiteResult result = generator .generate (spec );
461+
462+ assertNoErrors (result );
463+ CallSiteAssert asserter = assertCallSites (result .getFile ());
464+ // In-order capture: {0, 1} matches both advice and pointcut order
465+ asserter .advices (
466+ 0 ,
467+ advice -> {
468+ advice .pointcut (
469+ "java/lang/String" ,
470+ "format" ,
471+ "(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;" );
472+ advice .statements (
473+ "int[] parameterIndices = new int[] { 0, 1 };" ,
474+ "handler.dupParameters(descriptor, parameterIndices, null);" ,
475+ "handler.advice(\" datadog/trace/plugin/csi/impl/AdviceGeneratorTest$MultiplePartialArgumentsBeforeAdvice\" , \" before\" , \" (Ljava/util/Locale;Ljava/lang/String;)V\" );" ,
476+ "handler.method(opcode, owner, name, descriptor, isInterface);" );
477+ });
478+ // Reversed capture: {1, 0} follows the advice signature, not the pointcut index order {0, 1}
479+ asserter .advices (
480+ 1 ,
481+ advice -> {
482+ advice .pointcut (
483+ "java/lang/String" ,
484+ "format" ,
485+ "(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;" );
486+ advice .statements (
487+ "int[] parameterIndices = new int[] { 1, 0 };" ,
488+ "handler.dupParameters(descriptor, parameterIndices, null);" ,
489+ "handler.advice(\" datadog/trace/plugin/csi/impl/AdviceGeneratorTest$MultiplePartialArgumentsBeforeAdvice\" , \" beforeReversed\" , \" (Ljava/lang/String;Ljava/util/Locale;)V\" );" ,
490+ "handler.method(opcode, owner, name, descriptor, isInterface);" );
491+ });
492+ }
493+
429494 @ CallSite (spi = CallSites .class )
430495 public static class SuperTypeReturnAdvice {
431496 @ CallSite .After ("void java.lang.StringBuilder.<init>(java.lang.String)" )
0 commit comments