Skip to content

Commit 2ffa640

Browse files
authored
Fix Exception Replay in Lambda (#10505)
Fix Exception Replay in Lambda In Lambda, the inner stack trace is truncated -- stack frames from fileName='AWSLambda.java' are removed. Therefore, in the original code, we would end up with a negative currentIdx, resulting in an error occurring in sanityCheckSnapshotAssignment. This PR adds a fallback to fix the mismatched pointer. Co-authored-by: jean-philippe.bempel <[email protected]>
1 parent ab02a41 commit 2ffa640

File tree

2 files changed

+43
-0
lines changed

2 files changed

+43
-0
lines changed

dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/exception/AbstractExceptionDebugger.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,11 @@ private void processSnapshotsAndSetTags(
160160
int[] mapping = createThrowableMapping(currentEx, t);
161161
StackTraceElement[] innerTrace = currentEx.getStackTrace();
162162
int currentIdx = innerTrace.length - snapshot.getStack().size();
163+
if (currentIdx < 0) {
164+
// This means the innerTrace was truncated by the underlying environment.
165+
// This is known to happen in AWS Lambda, but may also happen elsewhere.
166+
currentIdx = i;
167+
}
163168
if (!sanityCheckSnapshotAssignment(snapshot, innerTrace, currentIdx)) {
164169
continue;
165170
}

dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,44 @@ public void filteringOutErrors() {
278278
verify(manager, times(0)).isAlreadyInstrumented(any());
279279
}
280280

281+
@Test
282+
public void lambdaTruncatedInnerTraceFallback() {
283+
RuntimeException exception =
284+
new RuntimeException("lambda") {
285+
// mock the stacktrace to simulate a truncated one
286+
@Override
287+
public StackTraceElement[] getStackTrace() {
288+
return new StackTraceElement[] {
289+
new StackTraceElement("Main", "handleRequest", "Main.java", 11),
290+
new StackTraceElement(
291+
"jdk.internal.reflect.DirectMethodHandleAccessor",
292+
"invoke",
293+
"Unknown Source",
294+
-1),
295+
new StackTraceElement("java.lang.reflect.Method", "invoke", "Unknown Source", -1)
296+
};
297+
}
298+
};
299+
String fingerprint = Fingerprinter.fingerprint(exception, classNameFiltering);
300+
AgentSpan span = mock(AgentSpan.class);
301+
doAnswer(this::recordTags).when(span).setTag(anyString(), anyString());
302+
when(span.getTag(anyString())).thenAnswer(inv -> spanTags.get(inv.getArgument(0)));
303+
when(span.getTags()).thenReturn(spanTags);
304+
exceptionDebugger.handleException(exception, span);
305+
assertWithTimeout(
306+
() -> exceptionDebugger.getExceptionProbeManager().isAlreadyInstrumented(fingerprint),
307+
Duration.ofSeconds(30));
308+
generateSnapshots(exception);
309+
ExceptionProbeManager.ThrowableState state =
310+
exceptionDebugger.getExceptionProbeManager().getStateByThrowable(exception);
311+
List<Snapshot> snapshots = state.getSnapshots();
312+
// This should hit the `currentIdx < 0` branch and fallback to i=0
313+
exceptionDebugger.handleException(exception, span);
314+
String tagName = String.format(SNAPSHOT_ID_TAG_FMT, 0);
315+
assertTrue(spanTags.containsKey(tagName));
316+
assertEquals(snapshots.get(0).getId(), spanTags.get(tagName));
317+
}
318+
281319
private Object recordTags(InvocationOnMock invocationOnMock) {
282320
Object[] args = invocationOnMock.getArguments();
283321
String key = (String) args[0];

0 commit comments

Comments
 (0)