Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ protected static Map<AbstractInsnNode, Frame<BasicValue>> computeFrames(
methodNode.desc,
ex);
// when running tests, fails if an exception is thrown during analysis
// Gradle is running tests with assertions enbabled
// Gradle is running tests with assertions enabled
assert false;
break;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.datadog.debugger.instrumentation;

import static com.datadog.debugger.instrumentation.ASMHelper.computeFrames;
import static com.datadog.debugger.instrumentation.ASMHelper.decodeSignature;
import static com.datadog.debugger.instrumentation.ASMHelper.emitReflectiveCall;
import static com.datadog.debugger.instrumentation.ASMHelper.ensureSafeClassLoad;
Expand Down Expand Up @@ -124,7 +125,7 @@ public InstrumentationResult.Status instrument() {
case EXIT:
{
Map<AbstractInsnNode, Frame<BasicValue>> frames =
ASMHelper.computeFrames(classNode.name, methodNode);
computeFrames(classNode.name, methodNode);
processInstructions(frames);
addFinallyHandler(returnHandlerLabel);
installFinallyBlocks();
Expand Down Expand Up @@ -152,12 +153,38 @@ private void addFinallyHandler(LabelNode endLabel) {
createFinallyHandler(durationStartLabel, endLabel, wrapTryCatch(insnList));
}

/**
* Checks that a metric call can be inserted at the line label. If the line label is in the middle
* of an expression, the operand stack is not empty and may hold uninitialized references that
* cannot be saved in locals: jumping from the exception handler of {@link
* #wrapLineTryCatch(InsnList, LabelNode)} back to the line with an empty stack would fail
* verification. The start of a catch handler is accepted, the caught exception on the stack is
* handled by {@link #wrapLineTryCatch(InsnList, LabelNode)}.
*
* @return the reason why the line cannot be instrumented, or null if it can
*/
private String checkLineLabel(
LabelNode lineLabel, Map<AbstractInsnNode, Frame<BasicValue>> frames) {
if (lineLabel == null || findCatchHandler(lineLabel) != null) {
return null;
}
Frame<BasicValue> frame = frames.get(lineLabel);
if (frame == null) {
return "operand stack state cannot be computed";
}
if (frame.getStackSize() > 0) {
return "operand stack is not empty, line is probably in the middle of a statement or expression";
}
return null;
}

/**
* Wraps the metric call in a try/catch like {@link #wrapTryCatch(InsnList)}. If the line label is
* the start of a catch handler, the caught exception is still on the operand stack. The exception
* handler of the try/catch clears the stack, so the exception is saved in a temporary local
* before the call and loaded back after it. This keeps the stack empty at both ends of the
* try/catch range.
* try/catch range. Other line labels must have an empty operand stack, see {@link
* #checkLineLabel(LabelNode, Map)}.
*/
private InsnList wrapLineTryCatch(InsnList insnList, LabelNode lineLabel) {
InsnList wrapped = wrapTryCatch(insnList);
Expand Down Expand Up @@ -410,13 +437,22 @@ private InstrumentationResult.Status addLineMetric(ClassFileLines classFileLines
reportError("Missing line debug information.");
return InstrumentationResult.Status.ERROR;
}
// all lines are checked before any insertion: frames are keyed by the original line labels
// and a rejected line must not leave the method partially instrumented
Map<AbstractInsnNode, Frame<BasicValue>> frames = computeFrames(classNode.name, methodNode);
for (Where.SourceLine sourceLine : targetLines) {
int from = sourceLine.getFrom();
int till = sourceLine.getTill();
LabelNode beforeLabel = classFileLines.getLineLabel(from);
// single line N capture translates to line range (N, N+1)
LabelNode afterLabel =
classFileLines.getLineLabel(till + (sourceLine.isSingleLine() ? 1 : 0));
String error = checkLineLabel(getBeforeLabel(classFileLines, sourceLine), frames);
if (error == null && !sourceLine.isSingleLine()) {
error = checkLineLabel(getAfterLabel(classFileLines, sourceLine), frames);
}
if (error != null) {
reportError("Cannot instrument line " + sourceLine + ": " + error);
return InstrumentationResult.Status.ERROR;
}
}
for (Where.SourceLine sourceLine : targetLines) {
LabelNode beforeLabel = getBeforeLabel(classFileLines, sourceLine);
LabelNode afterLabel = getAfterLabel(classFileLines, sourceLine);
if (beforeLabel == null && afterLabel == null) {
reportError(
"No line info for " + (sourceLine.isSingleLine() ? "line " : "range ") + sourceLine);
Expand All @@ -433,6 +469,17 @@ private InstrumentationResult.Status addLineMetric(ClassFileLines classFileLines
return InstrumentationResult.Status.INSTALLED;
}

private static LabelNode getBeforeLabel(
ClassFileLines classFileLines, Where.SourceLine sourceLine) {
return classFileLines.getLineLabel(sourceLine.getFrom());
}

private static LabelNode getAfterLabel(
ClassFileLines classFileLines, Where.SourceLine sourceLine) {
// single line N capture translates to line range (N, N+1)
return classFileLines.getLineLabel(sourceLine.getTill() + (sourceLine.isSingleLine() ? 1 : 0));
}

private static class VisitorResult {
final ASMHelper.Type type;
final InsnList insnList;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ private void addDiagnostics(ProbeStatus message) {
return;
}
ProbeId probeId = message.getDiagnostics().getProbeId();
TimedMessage current = probeStatuses.get(probeId.getId());
TimedMessage current = probeStatuses.get(probeId.getEncodedId());
if (current == null || shouldOverwrite(current.getMessage(), message)) {
TimedMessage newMessage = new TimedMessage(message);
probeStatuses.put(probeId.getEncodedId(), newMessage);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,28 @@ public void lineCatchHandlerCountMetric() throws IOException, URISyntaxException
verify(probeStatusSink, times(0)).addError(any(), anyString());
}

@Test
public void lineMultiLineExpressionCountMetric() throws IOException, URISyntaxException {
final String CLASS_NAME = "CapturedSnapshot05";
String METRIC_NAME = "expr_count";
// line in the middle of a constructor call: operand stack holds uninitialized references
int line = getLineForLineProbe(CLASS_NAME, LINE_METRIC_ID4);
MetricProbe metricProbe =
createLineMetric(LINE_METRIC_ID4, METRIC_NAME, COUNT, CLASS_NAME, null, line);
MetricForwarderListener listener = installMetricProbes(metricProbe);
Class<?> testClass = compileAndLoadClass(CLASS_NAME);
String result = Reflect.on(testClass).call("multiLineExpression", "foo").get();
assertEquals("f", result);
assertFalse(listener.counters.containsKey(METRIC_NAME));
verify(probeStatusSink)
.addError(
eq(LINE_METRIC_ID4),
eq(
"Cannot instrument line "
+ line
+ ": operand stack is not empty, line is probably in the middle of a statement or expression"));
}

@Test
public void invalidNameArgumentRefValueCountMetric() throws IOException, URISyntaxException {
final String CLASS_NAME = "CapturedSnapshot03";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,16 @@ void doNotReemitRemovedMessage() {
assertEquals(Collections.emptyList(), probeStatusSink.getDiagnostics());
}

@Test
void doNotReemitSameStatusAfterSentIfIntervalHasNotPassed() {
probeStatusSink.addInstalled(PROBE_ID);
assertEquals(
Collections.singletonList(builder.installedMessage(PROBE_ID)),
probeStatusSink.getDiagnostics());
probeStatusSink.addInstalled(PROBE_ID);
assertEquals(Collections.emptyList(), probeStatusSink.getDiagnostics());
}

@Test
void dropRepeatingDiagnostics() {
probeStatusSink.addReceived(PROBE_ID);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,10 @@ public CustomException(String message, String additionalMsg) {
this.additionalMsg = additionalMsg;
}
}

static String multiLineExpression(String arg) {
StringBuilder sb = new StringBuilder(
arg.substring(0, 1)); // beae1817-f3b0-4ea8-a74f-000000000004
return sb.toString();
}
}
Loading