java-topology/defects/libreoffice-0001/test/LibreOfficePivotMemberLookupTest.java
russell@unturf.com ad90dddbcc libreoffice+calligra: 2 CWE-407 defects, MOADs 0002-0005 CLEAN
libreoffice-0001: SavePivotTableXml member-to-cache O(M*C) std::find
  sc/source/filter/excel/xepivotxml.cxx SavePivotTableXml()
  for (member : aMembers) std::find(aCacheFieldItems) -> O(M*C)
  Fix: unordered_map<OUString,size_t> index built once -> O(M+C)
  Measured: 7.1x speedup at M=C=2000

calligra-0001: KoShapeManager addShape QList::contains O(N^2) dedup
  libs/flake/KoShapeManager.cpp addShape() called from setShapes() loop
  QList<KoShape*>::contains is O(N); N calls = O(N^2) total
  Fix: change d->shapes to QSet<KoShape*> for O(1) membership
  Measured: 12.6x speedup at N=5000

MOADs 0002-0005: CLEAN for both targets (see README.md per defect)
Both unit tests: PASS
2026-03-31 20:29:29 -04:00

130 lines
5.3 KiB
Java

import java.util.*;
/**
* Unit test for libreoffice-0001: SavePivotTableXml member-to-cache O(M*C) linear scan.
*
* Defect: sc/source/filter/excel/xepivotxml.cxx SavePivotTableXml()
* for (member : aMembers)
* std::find(aCacheFieldItems.begin(), aCacheFieldItems.end(), member.maName)
*
* Fix: build std::unordered_map<OUString, size_t> from aCacheFieldItems once,
* then look up each member in O(1).
*
* Complexity: O(M*C) -> O(M+C) where M = member count, C = cache item count.
*/
public class LibreOfficePivotMemberLookupTest {
// --- Defective implementation: std::find equivalent ---
static int linearFind(List<String> cacheItems, String name) {
return cacheItems.indexOf(name); // O(C)
}
static List<Integer> buildMemberSequenceLinear(List<String> members, List<String> cacheItems) {
List<Integer> result = new ArrayList<>();
Set<Integer> used = new HashSet<>();
for (String member : members) {
int pos = linearFind(cacheItems, member); // O(C) each -> O(M*C) total
if (pos >= 0 && used.add(pos)) {
result.add(pos);
}
}
return result;
}
// --- Fixed implementation: unordered_map equivalent ---
static List<Integer> buildMemberSequenceFixed(List<String> members, List<String> cacheItems) {
// Build O(1) lookup map once
Map<String, Integer> indexMap = new HashMap<>(cacheItems.size() * 2);
for (int i = 0; i < cacheItems.size(); i++) {
indexMap.put(cacheItems.get(i), i);
}
List<Integer> result = new ArrayList<>();
Set<Integer> used = new HashSet<>();
for (String member : members) {
Integer pos = indexMap.get(member); // O(1) each
if (pos != null && used.add(pos)) {
result.add(pos);
}
}
return result;
}
// --- Benchmark ---
static long benchmarkLinear(int M, int C) {
List<String> cacheItems = new ArrayList<>(C);
for (int i = 0; i < C; i++) cacheItems.add("item_" + i);
List<String> members = new ArrayList<>(M);
for (int i = 0; i < M; i++) members.add("item_" + (i % C));
long start = System.nanoTime();
buildMemberSequenceLinear(members, cacheItems);
return System.nanoTime() - start;
}
static long benchmarkFixed(int M, int C) {
List<String> cacheItems = new ArrayList<>(C);
for (int i = 0; i < C; i++) cacheItems.add("item_" + i);
List<String> members = new ArrayList<>(M);
for (int i = 0; i < M; i++) members.add("item_" + (i % C));
long start = System.nanoTime();
buildMemberSequenceFixed(members, cacheItems);
return System.nanoTime() - start;
}
public static void main(String[] args) {
System.out.println("=== libreoffice-0001: SavePivotTableXml pivot member lookup ===");
// Correctness test: both must produce same result
{
List<String> cache = Arrays.asList("alpha", "beta", "gamma", "delta");
List<String> members = Arrays.asList("gamma", "alpha", "delta", "alpha");
List<Integer> linearResult = buildMemberSequenceLinear(members, cache);
List<Integer> fixedResult = buildMemberSequenceFixed(members, cache);
if (!linearResult.equals(fixedResult)) {
System.err.println("FAIL: correctness mismatch: linear=" + linearResult + " fixed=" + fixedResult);
System.exit(1);
}
// gamma=2, alpha=0, delta=3 (alpha duplicate skipped)
List<Integer> expected = Arrays.asList(2, 0, 3);
if (!linearResult.equals(expected)) {
System.err.println("FAIL: wrong result: got=" + linearResult + " expected=" + expected);
System.exit(1);
}
System.out.println("PASS: correctness verified, result=" + fixedResult);
}
// Missing member test
{
List<String> cache = Arrays.asList("x", "y", "z");
List<String> members = Arrays.asList("y", "missing", "x");
List<Integer> linearResult = buildMemberSequenceLinear(members, cache);
List<Integer> fixedResult = buildMemberSequenceFixed(members, cache);
if (!linearResult.equals(fixedResult)) {
System.err.println("FAIL: missing-member mismatch");
System.exit(1);
}
System.out.println("PASS: missing-member handled correctly: " + fixedResult);
}
// Performance test: M=2000, C=2000
int M = 2000, C = 2000;
// Warmup
for (int w = 0; w < 3; w++) { benchmarkLinear(M/10, C/10); benchmarkFixed(M/10, C/10); }
long linearNs = benchmarkLinear(M, C);
long fixedNs = benchmarkFixed(M, C);
double ratio = (double) linearNs / fixedNs;
System.out.printf("Linear M=%d C=%d: %,d ns%n", M, C, linearNs);
System.out.printf("Fixed M=%d C=%d: %,d ns%n", M, C, fixedNs);
System.out.printf("Speedup: %.1fx%n", ratio);
if (ratio < 3.0) {
System.err.printf("FAIL: expected >= 3x speedup, got %.1fx%n", ratio);
System.exit(1);
}
System.out.println("PASS: speedup >= 3x");
System.out.println("PASS: all tests passed");
}
}