package unit; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * CWE-407 unit test: elasticsearch-002 * IngestDocument.java:960 — list.contains(val) (ArrayList) inside append loop * when allowDuplicates=false — O(n²) deduplication. * * Slow path: ArrayList.contains() per item — O(existing_size) each. * Fast path: HashSet.add() — returns false if duplicate, O(1) amortized. * * Compile: javac -d . IngestDocumentAppendContains.java * Run: java -ea unit.IngestDocumentAppendContains */ public class IngestDocumentAppendContains { /** * Simulates the defective appendValues logic. * Returns total comparison count across all contains() calls. */ static long slowAppend(List list, List valuesToAppend) { long comparisons = 0; for (Object val : valuesToAppend) { // Charge the cost of a linear scan through `list` at current size comparisons += list.size(); if (!list.contains(val)) { list.add(val); } } return comparisons; } /** * Simulates the fixed appendValues using HashSet for O(1) dedup. * Returns number of hash operations performed. */ static long fastAppend(List list, List valuesToAppend) { long operations = 0; Set seen = new HashSet<>(list); for (Object val : valuesToAppend) { operations++; // O(1) set.add() if (seen.add(val)) { list.add(val); } } return operations; } public static void main(String[] args) { int passed = 0; int total = 0; // Test 1: correctness — dedup behavior matches between paths { total++; List baseItems = new ArrayList<>(); baseItems.add("a"); baseItems.add("b"); baseItems.add("c"); List slowList = new ArrayList<>(baseItems); List fastList = new ArrayList<>(baseItems); List toAppend = new ArrayList<>(); toAppend.add("b"); // duplicate toAppend.add("d"); // new toAppend.add("a"); // duplicate toAppend.add("e"); // new slowAppend(slowList, toAppend); fastAppend(fastList, toAppend); assert slowList.equals(fastList) : "Dedup results differ: slow=" + slowList + " fast=" + fastList; assert slowList.size() == 5 : "Expected 5 elements (a,b,c,d,e), got " + slowList.size(); System.out.printf("Test 1 (correctness): both produced %s%n", slowList); passed++; } // Test 2: cost comparison — small N { total++; int existingN = 50; int appendM = 50; List existing = new ArrayList<>(); for (int i = 0; i < existingN; i++) existing.add("item" + i); // New unique values to append List toAppend = new ArrayList<>(); for (int i = existingN; i < existingN + appendM; i++) toAppend.add("item" + i); List slowList = new ArrayList<>(existing); List fastList = new ArrayList<>(existing); long slowCost = slowAppend(slowList, toAppend); long fastCost = fastAppend(fastList, toAppend); assert slowCost > fastCost : String.format("Expected slow > fast: slow=%d fast=%d", slowCost, fastCost); System.out.printf("Test 2 (N=%d M=%d): slow=%d, fast=%d, ratio=%.1fx%n", existingN, appendM, slowCost, fastCost, (double) slowCost / fastCost); passed++; } // Test 3: large duplicate-heavy case — worst case for the defect { total++; int n = 2000; List existing = new ArrayList<>(); for (int i = 0; i < n; i++) existing.add(Integer.valueOf(i)); // All duplicates — existing list never grows, but contains() still scans List toAppend = new ArrayList<>(); for (int i = 0; i < n; i++) toAppend.add(Integer.valueOf(i)); List slowList = new ArrayList<>(existing); List fastList = new ArrayList<>(existing); long slowCost = slowAppend(slowList, toAppend); long fastCost = fastAppend(fastList, toAppend); double ratio = (double) slowCost / fastCost; assert ratio > 50.0 : String.format("Expected >50x speedup at n=%d, got %.1fx (slow=%d fast=%d)", n, ratio, slowCost, fastCost); System.out.printf("Test 3 (N=%d all-dupes): slow=%d, fast=%d, speedup=%.1fx%n", n, slowCost, fastCost, ratio); passed++; } // Test 4: empty existing list { total++; List slowList = new ArrayList<>(); List fastList = new ArrayList<>(); List toAppend = new ArrayList<>(); for (int i = 0; i < 10; i++) toAppend.add("v" + i); slowAppend(slowList, toAppend); fastAppend(fastList, toAppend); assert slowList.equals(fastList) : "Empty-base results differ: slow=" + slowList + " fast=" + fastList; System.out.printf("Test 4 (empty base): both produced %d items%n", slowList.size()); passed++; } System.out.printf("%n%d/%d PASS%n", passed, total); } }