Skip to content

TraceStateBuilder.remove corrupts the builder when the same key is removed twice #8612

Description

@thswlsqls

Describe the bug

Removing the same key twice via TraceStateBuilder.remove(key) corrupts the builder: build() then leaks a null-valued entry, silently drops live entries, or throws ArrayIndexOutOfBoundsException.

Steps to reproduce

TraceState.builder()
    .put("a", "1").put("b", "2").put("c", "3")
    .remove("a").remove("a")
    .build(); // ArrayIndexOutOfBoundsException: Index 2 out of bounds for length 2

With a=1 alone, build() returns size=1, asMap={a=null}. With a=1,b=2, it returns an empty TraceState — the live b=2 is gone.

What did you expect to see?

Removing an already-removed key is a no-op, per the remove(String) Javadoc ("if it is present") and the TraceStateTest.removeNotPresent() contract. A caller cannot distinguish a key that was never present from one it already removed, yet only the latter breaks.

What did you see instead?

ArrayBasedTraceStateBuilder.remove() (api/all/src/main/java/io/opentelemetry/api/trace/ArrayBasedTraceStateBuilder.java line 95-96) decrements numEntries without checking whether the entry's value is already null, so the counter drifts below the real count. The sibling put() (line 74-78) performs the symmetric check. build() trusts numEntries for its early return, fast path, and array size.

What version and what artifacts are you using?
Artifacts: opentelemetry-api
Version: main @ 63ebd94
How did you reference these artifacts? N/A (source checkout)

Environment
Compiler: Temurin 21
OS: N/A

Additional context
Reachable through the builder-reuse pattern reuseBuilder() guarantees.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions