66import static com .datadog .debugger .instrumentation .ASMHelper .invokeVirtual ;
77import static com .datadog .debugger .instrumentation .ASMHelper .isFinalField ;
88import static com .datadog .debugger .instrumentation .ASMHelper .isStaticField ;
9- import static com .datadog .debugger .instrumentation .ASMHelper .isStore ;
109import static com .datadog .debugger .instrumentation .ASMHelper .ldc ;
1110import static com .datadog .debugger .instrumentation .ASMHelper .newInstance ;
1211import static com .datadog .debugger .instrumentation .Types .CAPTURED_CONTEXT_TYPE ;
4039import java .util .ArrayList ;
4140import java .util .Collection ;
4241import java .util .Collections ;
43- import java .util .HashMap ;
44- import java .util .HashSet ;
4542import java .util .List ;
46- import java .util .Map ;
4743import java .util .Set ;
4844import java .util .stream .Collectors ;
4945import org .objectweb .asm .Opcodes ;
5248import org .objectweb .asm .tree .ClassNode ;
5349import org .objectweb .asm .tree .FieldInsnNode ;
5450import org .objectweb .asm .tree .FieldNode ;
55- import org .objectweb .asm .tree .IincInsnNode ;
5651import org .objectweb .asm .tree .InsnList ;
5752import org .objectweb .asm .tree .InsnNode ;
5853import org .objectweb .asm .tree .JumpInsnNode ;
@@ -75,7 +70,7 @@ public class CapturedContextInstrumentor extends Instrumentor {
7570 private int exitContextVar = -1 ;
7671 private int timestampStartVar = -1 ;
7772 private int throwableListVar = -1 ;
78- private Collection <LocalVariableNode > unscopedLocalVars ;
73+ private Collection <LocalVariableNode > hoistedLocalVars = Collections . emptyList () ;
7974
8075 public CapturedContextInstrumentor (
8176 ProbeDefinition definition ,
@@ -413,12 +408,7 @@ private void instrumentMethodEnter() {
413408 if (methodNode .tryCatchBlocks .size () > 0 ) {
414409 throwableListVar = declareThrowableList (insnList );
415410 }
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 );
422412 insnList .add (contextInitLabel );
423413 if (definition instanceof SpanDecorationProbe
424414 && definition .getEvaluateAt () == MethodLocation .EXIT ) {
@@ -485,212 +475,18 @@ private void instrumentMethodEnter() {
485475
486476 // Initialize and hoist local variables to the top of the method
487477 // 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
490482 return Collections .emptyList ();
491483 }
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 ();
687486 }
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 );
694490 }
695491
696492 private void createInProbeFinallyHandler (LabelNode inProbeStartLabel , LabelNode inProbeEndLabel ) {
@@ -916,20 +712,18 @@ private void collectLocalVariables(AbstractInsnNode location, InsnList insnList)
916712 return ;
917713 }
918714 Collection <LocalVariableNode > localVarNodes ;
919- boolean isLocalVarHoistingEnabled =
920- Config .get ().isDynamicInstrumentationHoistLocalVarsEnabled ();
921- if (definition .isLineProbe () || !isLocalVarHoistingEnabled ) {
715+ if (definition .isLineProbe () || hoistedLocalVars .isEmpty ()) {
922716 localVarNodes = methodNode .localVariables ;
923717 } else {
924- localVarNodes = unscopedLocalVars ;
718+ localVarNodes = hoistedLocalVars ;
925719 }
926720 List <LocalVariableNode > applicableVars = new ArrayList <>();
927721 boolean isLineProbe = definition .isLineProbe ();
928722 for (LocalVariableNode variableNode : localVarNodes ) {
929723 int idx = variableNode .index - localVarBaseOffset ;
930724 if (idx >= argOffset ) {
931725 // var is local not arg
932- if (isLineProbe || ! isLocalVarHoistingEnabled ) {
726+ if (isLineProbe || hoistedLocalVars . isEmpty () ) {
933727 if (ASMHelper .isInScope (methodNode , variableNode , location )) {
934728 applicableVars .add (variableNode );
935729 }
0 commit comments