6
6
import static com .datadog .debugger .instrumentation .ASMHelper .invokeVirtual ;
7
7
import static com .datadog .debugger .instrumentation .ASMHelper .isFinalField ;
8
8
import static com .datadog .debugger .instrumentation .ASMHelper .isStaticField ;
9
- import static com .datadog .debugger .instrumentation .ASMHelper .isStore ;
10
9
import static com .datadog .debugger .instrumentation .ASMHelper .ldc ;
11
10
import static com .datadog .debugger .instrumentation .ASMHelper .newInstance ;
12
11
import static com .datadog .debugger .instrumentation .Types .CAPTURED_CONTEXT_TYPE ;
40
39
import java .util .ArrayList ;
41
40
import java .util .Collection ;
42
41
import java .util .Collections ;
43
- import java .util .HashMap ;
44
- import java .util .HashSet ;
45
42
import java .util .List ;
46
- import java .util .Map ;
47
43
import java .util .Set ;
48
44
import java .util .stream .Collectors ;
49
45
import org .objectweb .asm .Opcodes ;
52
48
import org .objectweb .asm .tree .ClassNode ;
53
49
import org .objectweb .asm .tree .FieldInsnNode ;
54
50
import org .objectweb .asm .tree .FieldNode ;
55
- import org .objectweb .asm .tree .IincInsnNode ;
56
51
import org .objectweb .asm .tree .InsnList ;
57
52
import org .objectweb .asm .tree .InsnNode ;
58
53
import org .objectweb .asm .tree .JumpInsnNode ;
@@ -75,7 +70,7 @@ public class CapturedContextInstrumentor extends Instrumentor {
75
70
private int exitContextVar = -1 ;
76
71
private int timestampStartVar = -1 ;
77
72
private int throwableListVar = -1 ;
78
- private Collection <LocalVariableNode > unscopedLocalVars ;
73
+ private Collection <LocalVariableNode > hoistedLocalVars = Collections . emptyList () ;
79
74
80
75
public CapturedContextInstrumentor (
81
76
ProbeDefinition definition ,
@@ -413,12 +408,7 @@ private void instrumentMethodEnter() {
413
408
if (methodNode .tryCatchBlocks .size () > 0 ) {
414
409
throwableListVar = declareThrowableList (insnList );
415
410
}
416
- unscopedLocalVars = Collections .emptyList ();
417
- if (Config .get ().isDynamicInstrumentationHoistLocalVarsEnabled ()
418
- && language == JvmLanguage .JAVA ) {
419
- // for now, only hoist local vars for Java
420
- unscopedLocalVars = initAndHoistLocalVars (insnList );
421
- }
411
+ hoistedLocalVars = initAndHoistLocalVars (methodNode );
422
412
insnList .add (contextInitLabel );
423
413
if (definition instanceof SpanDecorationProbe
424
414
&& definition .getEvaluateAt () == MethodLocation .EXIT ) {
@@ -485,212 +475,18 @@ private void instrumentMethodEnter() {
485
475
486
476
// Initialize and hoist local variables to the top of the method
487
477
// if there is name/slot conflict, do nothing for the conflicting local variable
488
- private Collection <LocalVariableNode > initAndHoistLocalVars (InsnList insnList ) {
489
- if (methodNode .localVariables == null || methodNode .localVariables .isEmpty ()) {
478
+ private Collection <LocalVariableNode > initAndHoistLocalVars (MethodNode methodNode ) {
479
+ int hoistingLevel = Config .get ().getDynamicInstrumentationLocalVarHoistingLevel ();
480
+ if (hoistingLevel == 0 || language != JvmLanguage .JAVA ) {
481
+ // for now, only hoist local vars for Java
490
482
return Collections .emptyList ();
491
483
}
492
- Map <String , LocalVariableNode > localVarsByName = new HashMap <>();
493
- Map <Integer , LocalVariableNode > localVarsBySlot = new HashMap <>();
494
- Map <String , Set <LocalVariableNode >> hoistableVarByName = new HashMap <>();
495
- for (LocalVariableNode localVar : methodNode .localVariables ) {
496
- int idx = localVar .index - localVarBaseOffset ;
497
- if (idx < argOffset ) {
498
- // this is an argument
499
- continue ;
500
- }
501
- checkHoistableLocalVar (localVar , localVarsByName , localVarsBySlot , hoistableVarByName );
502
- }
503
- removeDuplicatesFromArgs (hoistableVarByName , localVarsBySlotArray );
504
- // hoist vars
505
- List <LocalVariableNode > results = new ArrayList <>();
506
- for (Map .Entry <String , Set <LocalVariableNode >> entry : hoistableVarByName .entrySet ()) {
507
- Set <LocalVariableNode > hoistableVars = entry .getValue ();
508
- LocalVariableNode newVarNode ;
509
- if (hoistableVars .size () > 1 ) {
510
- // merge variables
511
- LocalVariableNode firstHoistableVar = hoistableVars .iterator ().next ();
512
- String name = firstHoistableVar .name ;
513
- String desc = firstHoistableVar .desc ;
514
- Type localVarType = getType (desc );
515
- int newSlot = newVar (localVarType ); // new slot for the local variable
516
- newVarNode = new LocalVariableNode (name , desc , null , null , null , newSlot );
517
- Set <LabelNode > endLabels = new HashSet <>();
518
- for (LocalVariableNode localVar : hoistableVars ) {
519
- // rewrite each usage of the old var to the new slot
520
- rewriteLocalVarInsn (localVar , localVar .index , newSlot );
521
- endLabels .add (localVar .end );
522
- }
523
- hoistVar (insnList , newVarNode );
524
- newVarNode .end = findLatestLabel (methodNode .instructions , endLabels );
525
- // remove previous local variables
526
- methodNode .localVariables .removeIf (hoistableVars ::contains );
527
- methodNode .localVariables .add (newVarNode );
528
- } else {
529
- // hoist the single variable and rewrite all its local var instructions
530
- newVarNode = hoistableVars .iterator ().next ();
531
- int oldIndex = newVarNode .index ;
532
- newVarNode .index = newVar (getType (newVarNode .desc )); // new slot for the local variable
533
- rewriteLocalVarInsn (newVarNode , oldIndex , newVarNode .index );
534
- hoistVar (insnList , newVarNode );
535
- }
536
- results .add (newVarNode );
537
- }
538
- return results ;
539
- }
540
-
541
- private void removeDuplicatesFromArgs (
542
- Map <String , Set <LocalVariableNode >> hoistableVarByName ,
543
- LocalVariableNode [] localVarsBySlotArray ) {
544
- for (int idx = 0 ; idx < argOffset ; idx ++) {
545
- LocalVariableNode localVar = localVarsBySlotArray [idx ];
546
- if (localVar == null ) {
547
- continue ;
548
- }
549
- // we are removing local variables that are arguments
550
- hoistableVarByName .remove (localVar .name );
551
- }
552
- }
553
-
554
- private LabelNode findLatestLabel (InsnList instructions , Set <LabelNode > endLabels ) {
555
- for (AbstractInsnNode insn = instructions .getLast (); insn != null ; insn = insn .getPrevious ()) {
556
- if (insn instanceof LabelNode && endLabels .contains (insn )) {
557
- return (LabelNode ) insn ;
558
- }
559
- }
560
- return null ;
561
- }
562
-
563
- private void hoistVar (InsnList insnList , LocalVariableNode varNode ) {
564
- LabelNode labelNode = new LabelNode (); // new start label for the local variable
565
- insnList .add (labelNode );
566
- varNode .start = labelNode ; // update the start label of the local variable
567
- Type localVarType = getType (varNode .desc );
568
- addStore0Insn (insnList , varNode , localVarType );
569
- }
570
-
571
- private static void addStore0Insn (
572
- InsnList insnList , LocalVariableNode localVar , Type localVarType ) {
573
- switch (localVarType .getSort ()) {
574
- case Type .BOOLEAN :
575
- case Type .CHAR :
576
- case Type .BYTE :
577
- case Type .SHORT :
578
- case Type .INT :
579
- insnList .add (new InsnNode (Opcodes .ICONST_0 ));
580
- break ;
581
- case Type .LONG :
582
- insnList .add (new InsnNode (Opcodes .LCONST_0 ));
583
- break ;
584
- case Type .FLOAT :
585
- insnList .add (new InsnNode (Opcodes .FCONST_0 ));
586
- break ;
587
- case Type .DOUBLE :
588
- insnList .add (new InsnNode (Opcodes .DCONST_0 ));
589
- break ;
590
- default :
591
- insnList .add (new InsnNode (Opcodes .ACONST_NULL ));
592
- break ;
593
- }
594
- insnList .add (new VarInsnNode (localVarType .getOpcode (Opcodes .ISTORE ), localVar .index ));
595
- }
596
-
597
- private void checkHoistableLocalVar (
598
- LocalVariableNode localVar ,
599
- Map <String , LocalVariableNode > localVarsByName ,
600
- Map <Integer , LocalVariableNode > localVarsBySlot ,
601
- Map <String , Set <LocalVariableNode >> hoistableVarByName ) {
602
- LocalVariableNode previousVarBySlot = localVarsBySlot .putIfAbsent (localVar .index , localVar );
603
- LocalVariableNode previousVarByName = localVarsByName .putIfAbsent (localVar .name , localVar );
604
- if (previousVarBySlot != null ) {
605
- // there are multiple local variables with the same slot but different names
606
- // by hoisting in a new slot, we can avoid the conflict
607
- hoistableVarByName .computeIfAbsent (localVar .name , k -> new HashSet <>()).add (localVar );
608
- }
609
- if (previousVarByName != null ) {
610
- // there are multiple local variables with the same name
611
- // checking type to see if they are compatible
612
- Type previousType = getType (previousVarByName .desc );
613
- Type currentType = getType (localVar .desc );
614
- if (!ASMHelper .isStoreCompatibleType (previousType , currentType )) {
615
- reportWarning (
616
- "Local variable "
617
- + localVar .name
618
- + " has multiple definitions with incompatible types: "
619
- + previousVarByName .desc
620
- + " and "
621
- + localVar .desc );
622
- // remove name from hoistable
623
- hoistableVarByName .remove (localVar .name );
624
- return ;
625
- }
626
- // Merge variables because compatible type
627
- }
628
- // by default, there is no conflict => hoistable
629
- hoistableVarByName .computeIfAbsent (localVar .name , k -> new HashSet <>()).add (localVar );
630
- }
631
-
632
- private void rewriteLocalVarInsn (LocalVariableNode localVar , int oldSlot , int newSlot ) {
633
- rewritePreviousStoreInsn (localVar , oldSlot , newSlot );
634
- for (AbstractInsnNode insn = localVar .start ;
635
- insn != null && insn != localVar .end ;
636
- insn = insn .getNext ()) {
637
- if (insn instanceof VarInsnNode ) {
638
- VarInsnNode varInsnNode = (VarInsnNode ) insn ;
639
- if (varInsnNode .var == oldSlot ) {
640
- varInsnNode .var = newSlot ;
641
- }
642
- }
643
- if (insn instanceof IincInsnNode ) {
644
- IincInsnNode iincInsnNode = (IincInsnNode ) insn ;
645
- if (iincInsnNode .var == oldSlot ) {
646
- iincInsnNode .var = newSlot ;
647
- }
648
- }
649
- }
650
- }
651
-
652
- // Previous insn(s) to local var range could be a store to index that need to be rewritten as well
653
- // LocalVariableTable ranges starts after the init of the local var.
654
- // ex:
655
- // 9: iconst_0
656
- // 10: astore_1
657
- // 11: aload_1
658
- // range for slot 1 starts at 11
659
- // javac often starts the range right after the init of the local var, so we can just look for
660
- // the previous instruction. But not always, and we put an arbitrary limit to 10 instructions
661
- // for kotlinc, many instructions can separate the init and the range start
662
- // ex:
663
- // LocalVariableTable:
664
- // Start Length Slot Name Signature
665
- // 70 121 4 $i$f$map I
666
- //
667
- // 64: istore 4
668
- // 66: aload_2
669
- // 67: iconst_2
670
- // 68: iconst_1
671
- // 69: bastore
672
- // 70: aload_3
673
- private static void rewritePreviousStoreInsn (
674
- LocalVariableNode localVar , int oldSlot , int newSlot ) {
675
- AbstractInsnNode previous = localVar .start .getPrevious ();
676
- int processed = 0 ;
677
- // arbitrary fixing limit to 10 previous instructions to look at
678
- while (previous != null && !isVarStoreForSlot (previous , oldSlot ) && processed < 10 ) {
679
- previous = previous .getPrevious ();
680
- processed ++;
681
- }
682
- if (isVarStoreForSlot (previous , oldSlot )) {
683
- VarInsnNode varInsnNode = (VarInsnNode ) previous ;
684
- if (varInsnNode .var == oldSlot ) {
685
- varInsnNode .var = newSlot ;
686
- }
484
+ if (methodNode .localVariables == null || methodNode .localVariables .isEmpty ()) {
485
+ return Collections .emptyList ();
687
486
}
688
- }
689
-
690
- private static boolean isVarStoreForSlot (AbstractInsnNode node , int slotIdx ) {
691
- return node instanceof VarInsnNode
692
- && isStore (node .getOpcode ())
693
- && ((VarInsnNode ) node ).var == slotIdx ;
487
+ LOGGER .debug (
488
+ "Hoisting local variables level={} for method: {}" , hoistingLevel , methodNode .name );
489
+ return LocalVarHoisting .processMethod (methodNode , hoistingLevel );
694
490
}
695
491
696
492
private void createInProbeFinallyHandler (LabelNode inProbeStartLabel , LabelNode inProbeEndLabel ) {
@@ -916,20 +712,18 @@ private void collectLocalVariables(AbstractInsnNode location, InsnList insnList)
916
712
return ;
917
713
}
918
714
Collection <LocalVariableNode > localVarNodes ;
919
- boolean isLocalVarHoistingEnabled =
920
- Config .get ().isDynamicInstrumentationHoistLocalVarsEnabled ();
921
- if (definition .isLineProbe () || !isLocalVarHoistingEnabled ) {
715
+ if (definition .isLineProbe () || hoistedLocalVars .isEmpty ()) {
922
716
localVarNodes = methodNode .localVariables ;
923
717
} else {
924
- localVarNodes = unscopedLocalVars ;
718
+ localVarNodes = hoistedLocalVars ;
925
719
}
926
720
List <LocalVariableNode > applicableVars = new ArrayList <>();
927
721
boolean isLineProbe = definition .isLineProbe ();
928
722
for (LocalVariableNode variableNode : localVarNodes ) {
929
723
int idx = variableNode .index - localVarBaseOffset ;
930
724
if (idx >= argOffset ) {
931
725
// var is local not arg
932
- if (isLineProbe || ! isLocalVarHoistingEnabled ) {
726
+ if (isLineProbe || hoistedLocalVars . isEmpty () ) {
933
727
if (ASMHelper .isInScope (methodNode , variableNode , location )) {
934
728
applicableVars .add (variableNode );
935
729
}
0 commit comments