# Lucene CWE-407 Scan — CLEAN

**Date:** 2026-03-29
**Target:** Apache Lucene (`~/git/lucene/`)
**Language:** Java
**Version:** main branch (depth-1 clone)

## Scan Scope

| Area | Files Checked |
|------|--------------|
| `lucene/core/src/java/org/apache/lucene/index/` | IndexWriter, TieredMergePolicy, LogMergePolicy, TemporalMergePolicy, SegmentInfos, ReaderPool, DocumentsWriterPerThreadPool, BufferedUpdatesStream, UpgradeIndexMergePolicy, ConcurrentMergeScheduler, PerFieldMergeState |
| `lucene/core/src/java/org/apache/lucene/search/` | BooleanQuery |
| `lucene/core/src/java/org/apache/lucene/util/hnsw/` | UpdateGraphsUtils |
| `lucene/core/src/java/org/apache/lucene/codecs/perfield/` | PerFieldMergeState |

## Methodology

Searched for `List.contains()`, `ArrayList.contains()`, `Collection.contains()`, and `.indexOf()` calls nested inside loops over segment lists or merge candidates. Cross-referenced the declared type of each collection receiving a `.contains()` call.

## Findings

All `.contains()` calls in hot paths use hash-backed collections:

| Location | Collection | Type | Verdict |
|----------|-----------|------|---------|
| `TieredMergePolicy.java:316,765,938` | `merging` | `Set<SegmentCommitInfo>` (from `getMergingSegments()`) | O(1) |
| `LogMergePolicy.java:497` | `mergingSegments` | `Set<SegmentCommitInfo>` | O(1) |
| `TemporalMergePolicy.java` | `merging` | `Set<SegmentCommitInfo>` | O(1) |
| `IndexWriter.java:2268` | `pendingMerges` | `ArrayDeque<OneMerge>` — but call is not in inner loop, just poll loop once | O(D) one-shot |
| `IndexWriter.java:2268` | `runningMerges` | `HashSet<OneMerge>` | O(1) |
| `IndexWriter.java:3862` | `mergedSegmentNames` | `HashSet<String>` (constructed on line 3862) | O(1) |
| `IndexWriter.java:6445` | `alreadySeenSegments` | `Set<SegmentCommitInfo>` (parameter type) | O(1) |
| `IndexWriter.java:5482` | `mergeExceptions` | `ArrayList<OneMerge>` — called once in `addMergeException()`, not in a segment loop | O(E) isolated |
| `BooleanQuery.java:404,412` | `intersection` | `HashSet<Query>` | O(1) |
| `BooleanQuery.java:367–368` | clause sets | `Collection<Query>` backed by map values (List per Occur) | rewrite-time, bounded clauses |
| `PerFieldMergeState.java:105,108` | `filteredNames` | `HashSet<String>` | O(1) |
| `UpdateGraphsUtils.java:45,74` | `j` | `IntHashSet` | O(1) |

## Notable Non-Defects

- **`IndexWriter.java:5482` `mergeExceptions.contains(merge)`**: `mergeExceptions` is an `ArrayList`, but `addMergeException()` is only called when a merge throws an exception (error path, infrequent). It is not called inside a per-segment loop. Not a hot path.

- **`IndexWriter.java:2268` `pendingMerges.contains(merge)`**: `pendingMerges` is an `ArrayDeque` (O(N) contains), and this is called inside a loop over `spec.merges` (up to N merges, each checked). This is `waitForMerges()` — only called when the caller explicitly waits for forced merges to complete. Not a per-document or per-query hot path. Excluded.

## Result

**CLEAN** — no CWE-407 defects confirmed in Lucene core.

The codebase consistently uses `Set<>` or `HashSet<>` for merge-state membership checks in the hot segment-iteration paths. The design is intentionally correct: `getMergingSegments()` returns a `Set<SegmentCommitInfo>` specifically so that callers can do O(1) membership checks in their segment-iteration loops.
