Skip to content

Commit 1292144

Browse files
fix: capture real line numbers in tracer and track dropped captures (TODO-34, TODO-38)
TODO-34: TracingClassVisitor hardcoded line number to 0 because ASM's visitMethod() doesn't provide line info. Added a pre-scan pass in TracingTransformer.instrumentClass() that collects first line numbers via visitLineNumber() before the instrumentation pass. TODO-38: Serialization timeouts/failures silently dropped captures with no visibility. Added AtomicInteger droppedCaptures counter and included it in flush() metadata output. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5942ae9 commit 1292144

4 files changed

Lines changed: 48 additions & 4 deletions

File tree

codeflash-java-runtime/src/main/java/com/codeflash/tracer/TraceRecorder.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public final class TraceRecorder {
2222
private final TracerConfig config;
2323
private final TraceWriter writer;
2424
private final ConcurrentHashMap<String, AtomicInteger> functionCounts = new ConcurrentHashMap<>();
25+
private final AtomicInteger droppedCaptures = new AtomicInteger(0);
2526
private final int maxFunctionCount;
2627
private final ExecutorService serializerExecutor;
2728

@@ -82,11 +83,13 @@ private void onEntryImpl(String className, String methodName, String descriptor,
8283
argsBlob = future.get(SERIALIZATION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
8384
} catch (TimeoutException e) {
8485
future.cancel(true);
86+
droppedCaptures.incrementAndGet();
8587
System.err.println("[codeflash-tracer] Serialization timed out for " + className + "."
8688
+ methodName);
8789
return;
8890
} catch (Exception e) {
8991
Throwable cause = e.getCause() != null ? e.getCause() : e;
92+
droppedCaptures.incrementAndGet();
9093
System.err.println("[codeflash-tracer] Serialization failed for " + className + "."
9194
+ methodName + ": " + cause.getClass().getSimpleName() + ": " + cause.getMessage());
9295
return;
@@ -113,11 +116,15 @@ public void flush() {
113116
}
114117
metadata.put("totalCaptures", String.valueOf(totalCaptures));
115118

119+
int dropped = droppedCaptures.get();
120+
metadata.put("droppedCaptures", String.valueOf(dropped));
121+
116122
writer.writeMetadata(metadata);
117123
writer.flush();
118124
writer.close();
119125

120126
System.err.println("[codeflash-tracer] Captured " + totalCaptures
121-
+ " invocations across " + functionCounts.size() + " methods");
127+
+ " invocations across " + functionCounts.size() + " methods"
128+
+ (dropped > 0 ? " (" + dropped + " dropped due to serialization timeout/failure)" : ""));
122129
}
123130
}

codeflash-java-runtime/src/main/java/com/codeflash/tracer/TracingClassVisitor.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,20 @@
44
import org.objectweb.asm.MethodVisitor;
55
import org.objectweb.asm.Opcodes;
66

7+
import java.util.Collections;
8+
import java.util.Map;
9+
710
public class TracingClassVisitor extends ClassVisitor {
811

912
private final String internalClassName;
13+
private final Map<String, Integer> methodLineNumbers;
1014
private String sourceFile;
1115

12-
public TracingClassVisitor(ClassVisitor classVisitor, String internalClassName) {
16+
public TracingClassVisitor(ClassVisitor classVisitor, String internalClassName,
17+
Map<String, Integer> methodLineNumbers) {
1318
super(Opcodes.ASM9, classVisitor);
1419
this.internalClassName = internalClassName;
20+
this.methodLineNumbers = methodLineNumbers != null ? methodLineNumbers : Collections.emptyMap();
1521
}
1622

1723
@Override
@@ -37,7 +43,8 @@ public MethodVisitor visitMethod(int access, String name, String descriptor,
3743
return mv;
3844
}
3945

46+
int lineNumber = methodLineNumbers.getOrDefault(name + descriptor, 0);
4047
return new TracingMethodAdapter(mv, access, name, descriptor,
41-
internalClassName, 0, sourceFile != null ? sourceFile : "");
48+
internalClassName, lineNumber, sourceFile != null ? sourceFile : "");
4249
}
4350
}

codeflash-java-runtime/src/main/java/com/codeflash/tracer/TracingTransformer.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
package com.codeflash.tracer;
22

33
import org.objectweb.asm.ClassReader;
4+
import org.objectweb.asm.ClassVisitor;
45
import org.objectweb.asm.ClassWriter;
6+
import org.objectweb.asm.Label;
7+
import org.objectweb.asm.MethodVisitor;
8+
import org.objectweb.asm.Opcodes;
59

610
import java.lang.instrument.ClassFileTransformer;
711
import java.security.ProtectionDomain;
12+
import java.util.HashMap;
13+
import java.util.Map;
814

915
public class TracingTransformer implements ClassFileTransformer {
1016

@@ -46,14 +52,38 @@ public byte[] transform(ClassLoader loader, String className,
4652

4753
private byte[] instrumentClass(String internalClassName, byte[] bytecode) {
4854
ClassReader cr = new ClassReader(bytecode);
55+
56+
// Pre-scan: collect the first source line number for each method.
57+
// ASM's visitMethod() doesn't provide line info — it arrives later via visitLineNumber().
58+
// We do a lightweight read pass first so the instrumentation pass has accurate line numbers.
59+
Map<String, Integer> methodLineNumbers = new HashMap<>();
60+
cr.accept(new ClassVisitor(Opcodes.ASM9) {
61+
@Override
62+
public MethodVisitor visitMethod(int access, String name, String descriptor,
63+
String signature, String[] exceptions) {
64+
String key = name + descriptor;
65+
return new MethodVisitor(Opcodes.ASM9) {
66+
private boolean captured = false;
67+
68+
@Override
69+
public void visitLineNumber(int line, Label start) {
70+
if (!captured) {
71+
methodLineNumbers.put(key, line);
72+
captured = true;
73+
}
74+
}
75+
};
76+
}
77+
}, ClassReader.SKIP_FRAMES);
78+
4979
// Use COMPUTE_MAXS only (not COMPUTE_FRAMES) to preserve original stack map frames.
5080
// COMPUTE_FRAMES recomputes all frames and calls getCommonSuperClass() which either
5181
// triggers classloader deadlocks or produces incorrect frames when returning "java/lang/Object".
5282
// With COMPUTE_MAXS + ClassReader passed to constructor, ASM copies original frames and
5383
// adjusts offsets for injected code. Our AdviceAdapter only injects at method entry
5484
// (before any branch points), so existing frames remain valid.
5585
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
56-
TracingClassVisitor cv = new TracingClassVisitor(cw, internalClassName);
86+
TracingClassVisitor cv = new TracingClassVisitor(cw, internalClassName, methodLineNumbers);
5787
cr.accept(cv, ClassReader.EXPAND_FRAMES);
5888
return cw.toByteArray();
5989
}
2.68 KB
Binary file not shown.

0 commit comments

Comments
 (0)