From 5c974dd60cf00c2c78374e93c9ccd27a7df3d2c0 Mon Sep 17 00:00:00 2001 From: kyyril Date: Thu, 21 May 2026 06:31:58 +0700 Subject: [PATCH 1/2] fix: enforce last-value-wins in AttributesMap for duplicate keys of different types Fixes #7897 --- .../sdk/common/internal/AttributesMap.java | 17 +++++++++++++++++ .../sdk/common/internal/AttributesMapTest.java | 11 +++++++++++ 2 files changed, 28 insertions(+) diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/common/internal/AttributesMap.java b/sdk/common/src/main/java/io/opentelemetry/sdk/common/internal/AttributesMap.java index 0ddf9599752..729c2921d6f 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/common/internal/AttributesMap.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/common/internal/AttributesMap.java @@ -63,12 +63,29 @@ public Object put(AttributeKey key, @Nullable Object value) { return null; } totalAddedValues++; + AttributeKey existingKey = null; + for (AttributeKey k : keySet()) { + if (k.getKey().equals(key.getKey())) { + existingKey = k; + break; + } + } + if (existingKey != null && !existingKey.equals(key)) { + super.remove(existingKey); + } if (size() >= capacity && !containsKey(key)) { return null; } return super.put(key, AttributeUtil.applyAttributeLengthLimit(value, lengthLimit)); } + @Override + public void putAll(Map, ?> m) { + for (Map.Entry, ?> entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + /** Generic overload of {@link #put(AttributeKey, Object)}. */ public void putIfCapacity(AttributeKey key, @Nullable T value) { put(key, value); diff --git a/sdk/common/src/test/java/io/opentelemetry/sdk/common/internal/AttributesMapTest.java b/sdk/common/src/test/java/io/opentelemetry/sdk/common/internal/AttributesMapTest.java index a7a1f8ecb2e..20598ca2a45 100644 --- a/sdk/common/src/test/java/io/opentelemetry/sdk/common/internal/AttributesMapTest.java +++ b/sdk/common/src/test/java/io/opentelemetry/sdk/common/internal/AttributesMapTest.java @@ -5,7 +5,9 @@ package io.opentelemetry.sdk.common.internal; +import static io.opentelemetry.api.common.AttributeKey.booleanKey; import static io.opentelemetry.api.common.AttributeKey.longKey; +import static io.opentelemetry.api.common.AttributeKey.stringKey; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; @@ -22,4 +24,13 @@ void asMap() { assertThat(attributesMap.asMap()) .containsOnly(entry(longKey("one"), 1L), entry(longKey("two"), 2L)); } + + @Test + void put_lastValueWins_differentTypes() { + AttributesMap attributesMap = AttributesMap.create(10, Integer.MAX_VALUE); + attributesMap.put(stringKey("key"), "value"); + attributesMap.put(booleanKey("key"), false); + + assertThat(attributesMap.asMap()).containsOnly(entry(booleanKey("key"), false)); + } } From 4c3817adf26540580401b9bde9bc36e666851b76 Mon Sep 17 00:00:00 2001 From: kyyril Date: Thu, 21 May 2026 06:57:51 +0700 Subject: [PATCH 2/2] test(common): add test cases to ensure 100% coverage on AttributesMap --- .../common/internal/AttributesMapTest.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/sdk/common/src/test/java/io/opentelemetry/sdk/common/internal/AttributesMapTest.java b/sdk/common/src/test/java/io/opentelemetry/sdk/common/internal/AttributesMapTest.java index 20598ca2a45..5ae6afd7167 100644 --- a/sdk/common/src/test/java/io/opentelemetry/sdk/common/internal/AttributesMapTest.java +++ b/sdk/common/src/test/java/io/opentelemetry/sdk/common/internal/AttributesMapTest.java @@ -11,6 +11,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; +import io.opentelemetry.api.common.AttributeKey; +import java.util.HashMap; +import java.util.Map; import org.junit.jupiter.api.Test; class AttributesMapTest { @@ -33,4 +36,25 @@ void put_lastValueWins_differentTypes() { assertThat(attributesMap.asMap()).containsOnly(entry(booleanKey("key"), false)); } + + @Test + void put_lastValueWins_sameType() { + AttributesMap attributesMap = AttributesMap.create(10, Integer.MAX_VALUE); + attributesMap.put(stringKey("key"), "value1"); + attributesMap.put(stringKey("key"), "value2"); + + assertThat(attributesMap.asMap()).containsOnly(entry(stringKey("key"), "value2")); + } + + @Test + void putAll() { + AttributesMap attributesMap = AttributesMap.create(10, Integer.MAX_VALUE); + Map, Object> newAttrs = new HashMap<>(); + newAttrs.put(stringKey("key1"), "value1"); + newAttrs.put(booleanKey("key2"), true); + attributesMap.putAll(newAttrs); + + assertThat(attributesMap.asMap()) + .containsOnly(entry(stringKey("key1"), "value1"), entry(booleanKey("key2"), true)); + } }