Skip to content

fix: return new BitList instance from emptyList() to prevent shared mutable state#16169

Open
daguimu wants to merge 1 commit intoapache:3.3from
daguimu:fix/bitlist-emptylist-shared-singleton-16131
Open

fix: return new BitList instance from emptyList() to prevent shared mutable state#16169
daguimu wants to merge 1 commit intoapache:3.3from
daguimu:fix/bitlist-emptylist-shared-singleton-16131

Conversation

@daguimu
Copy link
Copy Markdown

@daguimu daguimu commented Mar 25, 2026

Problem

BitList.emptyList() returns a shared static singleton instance. Since BitList is mutable (supports add(), addToTailList(), and(), etc.), any caller that modifies the returned "empty" list inadvertently mutates the singleton, contaminating all subsequent callers.

In AbstractDirectory, the invokers field is initialized with BitList.emptyList(). When the first interface's invokers are added, they pollute the shared singleton. When a second interface later calls BitList.emptyList(), it receives a list already containing the first interface's invokers, causing traffic to be routed to the wrong instances and resulting in NoSuchMethodError.

Root Cause

// Shared mutable singleton - any mutation affects all callers
private static final BitList emptyList = new BitList(Collections.emptyList());

public static <T> BitList<T> emptyList() {
    return emptyList; // returns the SAME instance every time
}

Fix

Return a new BitList instance on each call to emptyList(), consistent with how mutable collections should behave:

public static <T> BitList<T> emptyList() {
    return new BitList<>(Collections.emptyList());
}

Tests Added

  • testEmptyListReturnsIndependentInstances — Verifies each call returns a distinct object
  • testEmptyListMutationDoesNotAffectOtherEmptyList — Reproduces the exact bug: adding elements to one empty list must not affect another (the core scenario from [Bug] The traffic will be routed to instances of previous interface, which causes no such method error #16131)
  • testEmptyListTailListMutationDoesNotAffectOtherEmptyList — Verifies tail list mutations are also isolated
  • testEmptyListIsInitiallyEmpty — Confirms the returned list starts truly empty

All 26 tests in BitListTest pass.

Impact

  • Minimal change: removes 1 static field, modifies 1 method (3 lines changed in production code)
  • No behavioral change for callers — they get the same empty BitList, just a fresh instance
  • No reference equality (==) checks exist against BitList.emptyList() in the codebase
  • Fixes a critical production bug where cross-interface invoker leakage causes NoSuchMethodError

Fixes #16131

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Mar 25, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 60.83%. Comparing base (63fe8c3) to head (c862f97).
⚠️ Report is 1 commits behind head on 3.3.

Additional details and impacted files
@@             Coverage Diff              @@
##                3.3   #16169      +/-   ##
============================================
+ Coverage     60.81%   60.83%   +0.02%     
+ Complexity    11765    11752      -13     
============================================
  Files          1953     1953              
  Lines         89118    89117       -1     
  Branches      13444    13444              
============================================
+ Hits          54197    54217      +20     
+ Misses        29364    29347      -17     
+ Partials       5557     5553       -4     
Flag Coverage Δ
integration-tests-java21 32.17% <100.00%> (+<0.01%) ⬆️
integration-tests-java8 32.22% <100.00%> (-0.09%) ⬇️
samples-tests-java21 32.12% <100.00%> (-0.07%) ⬇️
samples-tests-java8 29.72% <100.00%> (-0.04%) ⬇️
unit-tests-java11 59.03% <100.00%> (-0.01%) ⬇️
unit-tests-java17 58.53% <100.00%> (-0.01%) ⬇️
unit-tests-java21 58.51% <100.00%> (-0.03%) ⬇️
unit-tests-java25 58.50% <100.00%> (-0.02%) ⬇️
unit-tests-java8 59.04% <100.00%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…utable state

BitList.emptyList() previously returned a shared static singleton instance.
Since BitList is mutable, modifications to one caller's "empty" list would
contaminate all other callers. This caused cross-interface invoker leakage
in AbstractDirectory, leading to NoSuchMethodError when traffic was routed
to instances of a previous interface.

The fix returns a new BitList instance on each call, consistent with how
mutable collections should behave.

Fixes apache#16131
@daguimu daguimu force-pushed the fix/bitlist-emptylist-shared-singleton-16131 branch from 711cfb3 to c862f97 Compare March 25, 2026 11:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] The traffic will be routed to instances of previous interface, which causes no such method error

2 participants