Skip to content

Commit 8802457

Browse files
authored
Fix hoisting local vars for Kotlin code (#7758)
javac begins the range of a local var in the LocalVariableTable just after the store instruction at the init. kotlinc is more free to instroduce nop/other instructions in-between. need to scan backward from the start of the range to find the last store of this slot to begin the rewrite of the slot needed to hoist correctly local vars.
1 parent eadf0cc commit 8802457

File tree

3 files changed

+80
-18
lines changed

3 files changed

+80
-18
lines changed

dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/CapturedContextInstrumentor.java

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ private Collection<LocalVariableNode> initAndHoistLocalVars(InsnList insnList) {
467467
}
468468
Map<String, LocalVariableNode> localVarsByName = new HashMap<>();
469469
Map<Integer, LocalVariableNode> localVarsBySlot = new HashMap<>();
470-
Map<String, List<LocalVariableNode>> hoistableVarByName = new HashMap<>();
470+
Map<String, Set<LocalVariableNode>> hoistableVarByName = new HashMap<>();
471471
for (LocalVariableNode localVar : methodNode.localVariables) {
472472
int idx = localVar.index - localVarBaseOffset;
473473
if (idx < argOffset) {
@@ -479,13 +479,14 @@ private Collection<LocalVariableNode> initAndHoistLocalVars(InsnList insnList) {
479479
removeDuplicatesFromArgs(hoistableVarByName, localVarsBySlotArray);
480480
// hoist vars
481481
List<LocalVariableNode> results = new ArrayList<>();
482-
for (Map.Entry<String, List<LocalVariableNode>> entry : hoistableVarByName.entrySet()) {
483-
List<LocalVariableNode> hoistableVars = entry.getValue();
482+
for (Map.Entry<String, Set<LocalVariableNode>> entry : hoistableVarByName.entrySet()) {
483+
Set<LocalVariableNode> hoistableVars = entry.getValue();
484484
LocalVariableNode newVarNode;
485485
if (hoistableVars.size() > 1) {
486486
// merge variables
487-
String name = hoistableVars.get(0).name;
488-
String desc = hoistableVars.get(0).desc;
487+
LocalVariableNode firstHoistableVar = hoistableVars.iterator().next();
488+
String name = firstHoistableVar.name;
489+
String desc = firstHoistableVar.desc;
489490
Type localVarType = getType(desc);
490491
int newSlot = newVar(localVarType); // new slot for the local variable
491492
newVarNode = new LocalVariableNode(name, desc, null, null, null, newSlot);
@@ -502,7 +503,7 @@ private Collection<LocalVariableNode> initAndHoistLocalVars(InsnList insnList) {
502503
methodNode.localVariables.add(newVarNode);
503504
} else {
504505
// hoist the single variable and rewrite all its local var instructions
505-
newVarNode = hoistableVars.get(0);
506+
newVarNode = hoistableVars.iterator().next();
506507
int oldIndex = newVarNode.index;
507508
newVarNode.index = newVar(getType(newVarNode.desc)); // new slot for the local variable
508509
rewriteLocalVarInsn(newVarNode, oldIndex, newVarNode.index);
@@ -514,7 +515,7 @@ private Collection<LocalVariableNode> initAndHoistLocalVars(InsnList insnList) {
514515
}
515516

516517
private void removeDuplicatesFromArgs(
517-
Map<String, List<LocalVariableNode>> hoistableVarByName,
518+
Map<String, Set<LocalVariableNode>> hoistableVarByName,
518519
LocalVariableNode[] localVarsBySlotArray) {
519520
for (int idx = 0; idx < argOffset; idx++) {
520521
LocalVariableNode localVar = localVarsBySlotArray[idx];
@@ -573,13 +574,13 @@ private void checkHoistableLocalVar(
573574
LocalVariableNode localVar,
574575
Map<String, LocalVariableNode> localVarsByName,
575576
Map<Integer, LocalVariableNode> localVarsBySlot,
576-
Map<String, List<LocalVariableNode>> hoistableVarByName) {
577+
Map<String, Set<LocalVariableNode>> hoistableVarByName) {
577578
LocalVariableNode previousVarBySlot = localVarsBySlot.putIfAbsent(localVar.index, localVar);
578579
LocalVariableNode previousVarByName = localVarsByName.putIfAbsent(localVar.name, localVar);
579580
if (previousVarBySlot != null) {
580581
// there are multiple local variables with the same slot but different names
581582
// by hoisting in a new slot, we can avoid the conflict
582-
hoistableVarByName.computeIfAbsent(localVar.name, k -> new ArrayList<>()).add(localVar);
583+
hoistableVarByName.computeIfAbsent(localVar.name, k -> new HashSet<>()).add(localVar);
583584
}
584585
if (previousVarByName != null) {
585586
// there are multiple local variables with the same name
@@ -601,18 +602,11 @@ private void checkHoistableLocalVar(
601602
// Merge variables because compatible type
602603
}
603604
// by default, there is no conflict => hoistable
604-
hoistableVarByName.computeIfAbsent(localVar.name, k -> new ArrayList<>()).add(localVar);
605+
hoistableVarByName.computeIfAbsent(localVar.name, k -> new HashSet<>()).add(localVar);
605606
}
606607

607608
private void rewriteLocalVarInsn(LocalVariableNode localVar, int oldSlot, int newSlot) {
608-
// previous insn could be a store to index that need to be rewritten as well
609-
AbstractInsnNode previous = localVar.start.getPrevious();
610-
if (previous instanceof VarInsnNode) {
611-
VarInsnNode varInsnNode = (VarInsnNode) previous;
612-
if (varInsnNode.var == oldSlot) {
613-
varInsnNode.var = newSlot;
614-
}
615-
}
609+
rewritePreviousStoreInsn(localVar, oldSlot, newSlot);
616610
for (AbstractInsnNode insn = localVar.start;
617611
insn != null && insn != localVar.end;
618612
insn = insn.getNext()) {
@@ -631,6 +625,42 @@ private void rewriteLocalVarInsn(LocalVariableNode localVar, int oldSlot, int ne
631625
}
632626
}
633627

628+
// Previous insn(s) to local var range could be a store to index that need to be rewritten as well
629+
// LocalVariableTable ranges starts after the init of the local var.
630+
// ex:
631+
// 9: iconst_0
632+
// 10: astore_1
633+
// 11: aload_1
634+
// range for slot 1 starts at 11
635+
// javac always starts the range right after the init of the local var, so we can just look for
636+
// the previous instruction
637+
// for kotlinc, many instructions can separate the init and the range start
638+
// ex:
639+
// LocalVariableTable:
640+
// Start Length Slot Name Signature
641+
// 70 121 4 $i$f$map I
642+
//
643+
// 64: istore 4
644+
// 66: aload_2
645+
// 67: iconst_2
646+
// 68: iconst_1
647+
// 69: bastore
648+
// 70: aload_3
649+
private static void rewritePreviousStoreInsn(
650+
LocalVariableNode localVar, int oldSlot, int newSlot) {
651+
AbstractInsnNode previous = localVar.start.getPrevious();
652+
while (previous != null
653+
&& (!(previous instanceof VarInsnNode) || ((VarInsnNode) previous).var != oldSlot)) {
654+
previous = previous.getPrevious();
655+
}
656+
if (previous != null) {
657+
VarInsnNode varInsnNode = (VarInsnNode) previous;
658+
if (varInsnNode.var == oldSlot) {
659+
varInsnNode.var = newSlot;
660+
}
661+
}
662+
}
663+
634664
private void createInProbeFinallyHandler(LabelNode inProbeStartLabel, LabelNode inProbeEndLabel) {
635665
LabelNode handlerLabel = new LabelNode();
636666
InsnList handler = new InsnList();

dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,27 @@ public void suspendKotlin() {
631631
}
632632
}
633633

634+
@Test
635+
public void hoistVarKotlin() {
636+
final String CLASS_NAME = "CapturedSnapshot303";
637+
TestSnapshotListener listener =
638+
installProbes(
639+
CLASS_NAME, createProbeAtExit(PROBE_ID, CLASS_NAME + "$Companion", "main", null));
640+
URL resource = CapturedSnapshotTest.class.getResource("/" + CLASS_NAME + ".kt");
641+
assertNotNull(resource);
642+
List<File> filesToDelete = new ArrayList<>();
643+
try {
644+
Class<?> testClass =
645+
KotlinHelper.compileAndLoad(CLASS_NAME, resource.getFile(), filesToDelete);
646+
Object companion = Reflect.onClass(testClass).get("Companion");
647+
int result = Reflect.on(companion).call("main", "").get();
648+
assertEquals(0, result);
649+
Snapshot snapshot = assertOneSnapshot(listener);
650+
} finally {
651+
filesToDelete.forEach(File::delete);
652+
}
653+
}
654+
634655
@Test
635656
public void fieldExtractor() throws IOException, URISyntaxException {
636657
final String CLASS_NAME = "CapturedSnapshot04";
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class CapturedSnapshot303 {
2+
companion object {
3+
fun main(arg: String): Int {
4+
listOf(1, 2, 3, 4, 5)
5+
.map { it * 2 }
6+
.filter { it % 2 == 0 }
7+
.forEach { println(it) }
8+
return arg.length
9+
}
10+
}
11+
}

0 commit comments

Comments
 (0)