java-topology/defects/xenia-0001/test/XeniaTest.java
russell@unturf.com d668589698 xenia: 1 CWE-407 defect (xenia-0001), MOADs 0002-0005 CLEAN
xenia-0001: ObjectTable::GetAllObjects() in
src/xenia/kernel/util/object_table.cc uses std::find on a growing results
vector to deduplicate XObject pointers while iterating all 16,384+ table
slots. Each slot incurs an O(results.size()) linear scan, giving O(S*R)
total where S = slot count and R = unique object count. Fix: unordered_set
seen-pointer set reduces membership test to O(1). Measured 4.3x speedup.

MOAD-0002 Intertangle: CLEAN
MOAD-0003 Leaked Context: CLEAN (TLS vars are thread-scoped, not request-scoped)
MOAD-0004 CWE-312: CLEAN (no credentials or key bytes logged)
MOAD-0005 Thundering Herd: CLEAN (all caches use global_critical_region_ lock)
2026-03-31 19:40:18 -04:00

144 lines
5.8 KiB
Java

import java.util.*;
/**
* XeniaTest: unit test for xenia-0001
*
* Defect: ObjectTable::GetAllObjects() in
* src/xenia/kernel/util/object_table.cc uses std::find on a growing
* std::vector<object_ref<XObject>> to deduplicate objects while iterating
* over all table slots. Each slot check is O(results.size()), making the
* full scan O(S * R) where S = table slot count (starting at 16,384) and R
* = unique object count (up to S/2 in the worst case). At S=16384 with a
* full table of unique objects this yields ~134 million pointer comparisons.
*
* Fix: replace the results vector linear scan with an unordered_set<XObject*>
* seen-pointer set, reducing each membership test from O(R) to O(1) and the
* full scan from O(S*R) to O(S).
*
* This test simulates both implementations and confirms:
* 1. Functional equivalence: same objects returned in same order.
* 2. Performance ratio: patched version is faster by the expected factor.
*/
public class XeniaTest {
// Simulates an XObject pointer (just an Integer ID here).
static class XObject {
final int id;
XObject(int id) { this.id = id; }
}
// --- DEFECTIVE IMPLEMENTATION ---
// Mirrors ObjectTable::GetAllObjects() before the patch.
// table[slot] may hold duplicate pointers (multiple handles to same object).
// Dedup is done via List.contains() -- O(results.size()) per slot.
static List<XObject> getAllObjectsDefective(XObject[] table) {
List<XObject> results = new ArrayList<>();
for (XObject entry : table) {
if (entry != null && !results.contains(entry)) { // O(R) scan
results.add(entry);
}
}
return results;
}
// --- PATCHED IMPLEMENTATION ---
// Mirrors ObjectTable::GetAllObjects() after the patch.
// Dedup via HashSet<XObject> seen -- O(1) per slot.
static List<XObject> getAllObjectsPatched(XObject[] table) {
List<XObject> results = new ArrayList<>();
Set<XObject> seen = new HashSet<>();
for (XObject entry : table) {
if (entry != null && seen.add(entry)) { // O(1) hash test+insert
results.add(entry);
}
}
return results;
}
// Build a table where each object is referenced by DUPLICATE_FACTOR slots.
// This matches xenia's handle table: one object can have multiple handles.
static XObject[] buildTable(int uniqueObjects, int duplicateFactor) {
XObject[] objs = new XObject[uniqueObjects];
for (int i = 0; i < uniqueObjects; i++) {
objs[i] = new XObject(i);
}
XObject[] table = new XObject[uniqueObjects * duplicateFactor];
for (int slot = 0; slot < table.length; slot++) {
table[slot] = objs[slot % uniqueObjects];
}
return table;
}
public static void main(String[] args) {
// --- Correctness test ---
// Small table: 8 unique objects, each referenced 3 times -> 24 slots
XObject[] smallTable = buildTable(8, 3);
List<XObject> defResult = getAllObjectsDefective(smallTable);
List<XObject> patResult = getAllObjectsPatched(smallTable);
assert defResult.size() == 8 :
"defective: expected 8 unique objects, got " + defResult.size();
assert patResult.size() == 8 :
"patched: expected 8 unique objects, got " + patResult.size();
for (int i = 0; i < defResult.size(); i++) {
assert defResult.get(i) == patResult.get(i) :
"result mismatch at index " + i;
}
System.out.println("PASS correctness: 8 unique objects, 24 slots, results match");
// --- Empty table ---
XObject[] emptyTable = new XObject[0];
assert getAllObjectsDefective(emptyTable).isEmpty() : "defective: empty table non-empty";
assert getAllObjectsPatched(emptyTable).isEmpty() : "patched: empty table non-empty";
System.out.println("PASS correctness: empty table");
// --- All-null table ---
XObject[] nullTable = new XObject[100];
assert getAllObjectsDefective(nullTable).isEmpty() : "defective: all-null table non-empty";
assert getAllObjectsPatched(nullTable).isEmpty() : "patched: all-null table non-empty";
System.out.println("PASS correctness: all-null table");
// --- Performance benchmark ---
// xenia initial table_capacity = 16 * 1024 = 16384 slots.
// Simulate a loaded game: 1000 unique objects, each with 4 handle slots -> 4000 slots.
// Then scale to 4000 unique objects in 16384 slots for O(N^2) stress.
int UNIQUE = 4000;
int SLOTS = 16384;
XObject[] bigTable = new XObject[SLOTS];
XObject[] objs = new XObject[UNIQUE];
for (int i = 0; i < UNIQUE; i++) objs[i] = new XObject(i);
for (int s = 0; s < SLOTS; s++) bigTable[s] = objs[s % UNIQUE];
int REPS = 20;
long tDef = 0;
for (int r = 0; r < REPS; r++) {
long t0 = System.nanoTime();
getAllObjectsDefective(bigTable);
tDef += System.nanoTime() - t0;
}
long tPat = 0;
for (int r = 0; r < REPS; r++) {
long t0 = System.nanoTime();
getAllObjectsPatched(bigTable);
tPat += System.nanoTime() - t0;
}
double defMs = tDef / 1e6 / REPS;
double patMs = tPat / 1e6 / REPS;
double ratio = defMs / patMs;
System.out.printf(
"BENCH defective=%.3f ms patched=%.3f ms ratio=%.1fx%n",
defMs, patMs, ratio);
assert ratio >= 2.0 :
"expected speedup >= 2x, got " + String.format("%.2f", ratio) + "x";
System.out.println("PASS performance: patched is " +
String.format("%.1f", ratio) + "x faster");
System.out.println("ALL TESTS PASSED");
}
}