From 0dd0abed2ab706a47824bbc96ae3dd96f6a65b9e Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 1 Sep 2026 17:40:30 -0400 Subject: [PATCH] Fix NPE in DatabaseClientDecorator when JDBC connection has no db type processDatabaseType/dbService dereferenced the NamingEntry returned by CACHE.computeIfAbsent(dbType, NamingEntry::new) without checking for null. FixedSizeCache.computeIfAbsent returns null for a null key without invoking the producer, so a null dbType() (e.g. an undetermined DBInfo.getType()) led to a NullPointerException in tracing instrumentation instead of a graceful no-op. Guard on dbType == null directly instead of inferring it from the cache's return value, and add a test exercising both methods with a null dbType. Co-Authored-By: Claude Sonnet 5 --- .../decorator/DatabaseClientDecorator.java | 12 +++- ...DatabaseClientDecoratorNullDbTypeTest.java | 70 +++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/decorator/DatabaseClientDecoratorNullDbTypeTest.java diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/DatabaseClientDecorator.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/DatabaseClientDecorator.java index fa105776113..79d08f4139a 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/DatabaseClientDecorator.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/DatabaseClientDecorator.java @@ -96,8 +96,10 @@ public String dbService(final String dbType, final String instanceName) { if (instanceName != null && Config.get().isDbClientSplitByInstance()) { return dbClientService(instanceName); } - final NamingEntry entry = CACHE.computeIfAbsent(dbType, NamingEntry::new); - return entry.getService(); + if (dbType == null) { + return null; + } + return CACHE.computeIfAbsent(dbType, NamingEntry::new).getService(); } public String dbClientService(final String instanceName) { @@ -144,11 +146,15 @@ public void onRawStatement(AgentSpan span, String sql) { } protected void processDatabaseType(AgentSpan span, String dbType) { + if (dbType == null) { + return; + } + final NamingEntry namingEntry = CACHE.computeIfAbsent(dbType, NamingEntry::new); span.setTag(DB_TYPE, namingEntry.dbType); postProcessServiceAndOperationName(span, namingEntry); - if (Config.get().isAppSecRaspEnabled() && dbType != null) { + if (Config.get().isAppSecRaspEnabled()) { BiConsumer connectDbCallback = AgentTracer.get() .getCallbackProvider(RequestContextSlot.APPSEC) diff --git a/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/decorator/DatabaseClientDecoratorNullDbTypeTest.java b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/decorator/DatabaseClientDecoratorNullDbTypeTest.java new file mode 100644 index 00000000000..681ceb460a2 --- /dev/null +++ b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/decorator/DatabaseClientDecoratorNullDbTypeTest.java @@ -0,0 +1,70 @@ +package datadog.trace.bootstrap.instrumentation.decorator; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import org.junit.jupiter.api.Test; + +class DatabaseClientDecoratorNullDbTypeTest { + + private final AgentSpan span = mock(AgentSpan.class); + private final DatabaseClientDecorator decorator = + new DatabaseClientDecorator() { + @Override + protected String[] instrumentationNames() { + return new String[] {"test"}; + } + + @Override + protected CharSequence spanType() { + return "test-type"; + } + + @Override + protected CharSequence component() { + return "test-component"; + } + + @Override + protected String service() { + return "test-service"; + } + + @Override + protected String dbType() { + return null; + } + + @Override + protected String dbUser(Object connection) { + return null; + } + + @Override + protected String dbInstance(Object connection) { + return null; + } + + @Override + protected CharSequence dbHostname(Object connection) { + return null; + } + }; + + @Test + void processDatabaseTypeWithNullDbTypeDoesNotThrowOrTag() { + assertDoesNotThrow(() -> decorator.processDatabaseType(span, null)); + + verify(span, never()).setTag(anyString(), anyString()); + } + + @Test + void dbServiceWithNullDbTypeReturnsNull() { + assertNull(decorator.dbService(null, null)); + } +}