Skip to content

Commit c99c2c2

Browse files
authored
Fix var hoisting issue when no previous store (#8122)
In some cases the range of a local var may not have previous init in the immediate neighborhood because of basic block arrangement. Limit the rewirite of previous instruction for store only and looking back only 10 instructions
1 parent da26e28 commit c99c2c2

File tree

4 files changed

+33
-14
lines changed

4 files changed

+33
-14
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,10 @@ public static String extractSuperClass(ClassNode classNode) {
340340
return Strings.getClassName(classNode.superName);
341341
}
342342

343+
public static boolean isStore(int opcode) {
344+
return opcode >= Opcodes.ISTORE && opcode <= Opcodes.ASTORE;
345+
}
346+
343347
/** Wraps ASM's {@link org.objectweb.asm.Type} with associated generic types */
344348
public static class Type {
345349
private final org.objectweb.asm.Type mainType;

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

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import static com.datadog.debugger.instrumentation.ASMHelper.invokeVirtual;
88
import static com.datadog.debugger.instrumentation.ASMHelper.isFinalField;
99
import static com.datadog.debugger.instrumentation.ASMHelper.isStaticField;
10+
import static com.datadog.debugger.instrumentation.ASMHelper.isStore;
1011
import static com.datadog.debugger.instrumentation.ASMHelper.ldc;
1112
import static com.datadog.debugger.instrumentation.ASMHelper.newInstance;
1213
import static com.datadog.debugger.instrumentation.Types.CAPTURED_CONTEXT_TYPE;
@@ -68,7 +69,7 @@
6869
import org.slf4j.LoggerFactory;
6970

7071
public class CapturedContextInstrumentor extends Instrumentor {
71-
private static final Logger log = LoggerFactory.getLogger(CapturedContextInstrumentor.class);
72+
private static final Logger LOGGER = LoggerFactory.getLogger(CapturedContextInstrumentor.class);
7273
private final boolean captureSnapshot;
7374
private final Limits limits;
7475
private final LabelNode contextInitLabel = new LabelNode();
@@ -639,8 +640,8 @@ private void rewriteLocalVarInsn(LocalVariableNode localVar, int oldSlot, int ne
639640
// 10: astore_1
640641
// 11: aload_1
641642
// range for slot 1 starts at 11
642-
// javac always starts the range right after the init of the local var, so we can just look for
643-
// the previous instruction
643+
// javac often starts the range right after the init of the local var, so we can just look for
644+
// the previous instruction. But not always, and we put an arbitrary limit to 10 instructions
644645
// for kotlinc, many instructions can separate the init and the range start
645646
// ex:
646647
// LocalVariableTable:
@@ -656,18 +657,26 @@ private void rewriteLocalVarInsn(LocalVariableNode localVar, int oldSlot, int ne
656657
private static void rewritePreviousStoreInsn(
657658
LocalVariableNode localVar, int oldSlot, int newSlot) {
658659
AbstractInsnNode previous = localVar.start.getPrevious();
659-
while (previous != null
660-
&& (!(previous instanceof VarInsnNode) || ((VarInsnNode) previous).var != oldSlot)) {
660+
int processed = 0;
661+
// arbitrary fixing limit to 10 previous instructions to look at
662+
while (previous != null && !isVarStoreForSlot(previous, oldSlot) && processed < 10) {
661663
previous = previous.getPrevious();
664+
processed++;
662665
}
663-
if (previous != null) {
666+
if (isVarStoreForSlot(previous, oldSlot)) {
664667
VarInsnNode varInsnNode = (VarInsnNode) previous;
665668
if (varInsnNode.var == oldSlot) {
666669
varInsnNode.var = newSlot;
667670
}
668671
}
669672
}
670673

674+
private static boolean isVarStoreForSlot(AbstractInsnNode node, int slotIdx) {
675+
return node instanceof VarInsnNode
676+
&& isStore(node.getOpcode())
677+
&& ((VarInsnNode) node).var == slotIdx;
678+
}
679+
671680
private void createInProbeFinallyHandler(LabelNode inProbeStartLabel, LabelNode inProbeEndLabel) {
672681
LabelNode handlerLabel = new LabelNode();
673682
InsnList handler = new InsnList();
@@ -1234,7 +1243,7 @@ private static void addInheritedStaticFields(
12341243
FieldNode fieldNode =
12351244
new FieldNode(field.getModifiers(), field.getName(), desc, null, field);
12361245
results.add(fieldNode);
1237-
log.debug("Adding static inherited field {}", fieldNode.name);
1246+
LOGGER.debug("Adding static inherited field {}", fieldNode.name);
12381247
fieldCount++;
12391248
if (fieldCount > limits.maxFieldCount) {
12401249
return;

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

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@
6666
import java.lang.reflect.Modifier;
6767
import java.net.URISyntaxException;
6868
import java.net.URL;
69-
import java.nio.file.Paths;
7069
import java.util.ArrayList;
7170
import java.util.Arrays;
7271
import java.util.Collections;
@@ -134,6 +133,17 @@ public void methodProbe() throws IOException, URISyntaxException {
134133
assertEquals("CapturedSnapshot01.main", snapshot.getStack().get(0).getFunction());
135134
}
136135

136+
@Test
137+
public void localVarHoistingNoPreviousStore() throws IOException, URISyntaxException {
138+
final String CLASS_NAME = "com.fasterxml.jackson.core.json.ByteSourceJsonBootstrapper";
139+
TestSnapshotListener listener = installSingleProbe(CLASS_NAME, "detectEncoding", null);
140+
Class<?> testClass =
141+
loadClass(
142+
CLASS_NAME,
143+
getClass().getResource("/classfiles/ByteSourceJsonBootstrapper.classfile").getFile());
144+
assertNotNull(testClass);
145+
}
146+
137147
@Test
138148
public void singleLineProbe() throws IOException, URISyntaxException {
139149
final String CLASS_NAME = "CapturedSnapshot01";
@@ -219,12 +229,8 @@ public void oldClass1_1() throws Exception {
219229
when(config.isDebuggerVerifyByteCode()).thenReturn(true);
220230
Class<?> testClass =
221231
loadClass(
222-
CLASS_NAME,
223-
Paths.get(
224-
CapturedSnapshotTest.class
225-
.getResource("/classfiles/BooleanUtils.classfile")
226-
.toURI())
227-
.toString());
232+
CLASS_NAME, getClass().getResource("/classfiles/BooleanUtils.classfile").getFile());
233+
228234
boolean result = Reflect.onClass(testClass).call("toBoolean", Boolean.TRUE).get();
229235
assertTrue(result);
230236
assertOneSnapshot(listener);

0 commit comments

Comments
 (0)