java-topology/defects/xonotic-0002/test/test_sv_model_index.c
russell@unturf.com 806713a90c xonotic: 4 CWE-407 defects in DarkPlaces engine, MOAD 0002-0005 CLEAN
xonotic-0001: Mod_Mesh_GetTexture O(T) linear scan per draw call, 43x
  model_shared.c:4481 scans all textures for every quad/char/image drawn.
  Fix: hash table keyed on (name, drawflag, texflags, matflags).

xonotic-0002: SV_ModelIndex O(M) linear scan, 81x
  sv_main.c:1421 scans model_precache[] (up to 8192) on every model ref.
  Fix: hash table on model filename.

xonotic-0003: SV_SoundIndex O(S) linear scan, 66x
  sv_main.c:1484 scans sound_precache[] (up to 4096) on every sound ref.
  Fix: hash table on sound filename.

xonotic-0004: S_FindName O(N) linked list scan, 72x
  snd_main.c:913 traverses linked list (has "TODO: hash table search?").
  Fix: hash chain on sfx name.

MOAD-0002 (Intertangle): CLEAN, god objects are idiomatic Quake engine.
MOAD-0003 (Leaked Context): CLEAN, no thread_local usage.
MOAD-0004 (Logged Secret): CLEAN, rcon_password uses CF_PRIVATE flag.
MOAD-0005 (Thundering Herd): CLEAN, single-threaded cache access.
2026-03-31 12:43:16 -04:00

144 lines
3.7 KiB
C

/**
* Unit test for xonotic-0002: SV_ModelIndex O(M) linear scan.
*
* Defect: sv_main.c SV_ModelIndex() linearly scans the model_precache array
* (up to MAX_MODELS = 8192 entries) on every model reference. Called from
* entity spawning, setmodel, baseline setup, and weapon model lookups.
*
* Fix: hash table for O(1) average lookup.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define MAX_MODELS 8192
#define MAX_QPATH 128
#define NUM_LOOKUPS 100000
#define HASH_SIZE 512
static char model_precache[MAX_MODELS][MAX_QPATH];
static int num_models = 0;
/* Unpatched: O(M) linear scan */
static int find_model_linear(const char *filename)
{
int i;
for (i = 2; i < num_models; i++)
{
if (!model_precache[i][0])
return -1; /* empty slot = not found */
if (!strcmp(model_precache[i], filename))
return i;
}
return -1;
}
/* Patched: O(1) hash lookup */
static int model_hashtable[HASH_SIZE];
static int model_hashnext[MAX_MODELS];
static unsigned model_hash(const char *s)
{
unsigned hash = 0;
for (; *s; s++)
hash = hash * 31 + (unsigned char)*s;
return hash % HASH_SIZE;
}
static void model_hash_init(void)
{
int i;
for (i = 0; i < HASH_SIZE; i++)
model_hashtable[i] = -1;
for (i = 0; i < MAX_MODELS; i++)
model_hashnext[i] = -1;
for (i = 2; i < num_models; i++)
{
if (!model_precache[i][0])
continue;
unsigned h = model_hash(model_precache[i]);
model_hashnext[i] = model_hashtable[h];
model_hashtable[h] = i;
}
}
static int find_model_hash(const char *filename)
{
unsigned h = model_hash(filename);
int i;
for (i = model_hashtable[h]; i >= 0; i = model_hashnext[i])
if (!strcmp(model_precache[i], filename))
return i;
return -1;
}
int main(void)
{
int i, pass = 1;
clock_t start, end;
double linear_time, hash_time, ratio;
/* Populate: simulate a large map with many models */
num_models = 2000;
for (i = 2; i < num_models; i++)
snprintf(model_precache[i], MAX_QPATH, "progs/model_%04d.mdl", i);
model_hash_init();
/* Correctness */
for (i = 2; i < num_models; i++)
{
int lin = find_model_linear(model_precache[i]);
int hsh = find_model_hash(model_precache[i]);
if (lin != hsh)
{
printf("FAIL: mismatch at model %d: linear=%d hash=%d\n", i, lin, hsh);
pass = 0;
}
}
/* Not-found */
if (find_model_hash("progs/nonexistent.mdl") != -1)
{
printf("FAIL: hash found nonexistent model\n");
pass = 0;
}
/* Benchmark */
start = clock();
for (i = 0; i < NUM_LOOKUPS; i++)
{
int idx = 2 + (i % (num_models - 2));
volatile int r = find_model_linear(model_precache[idx]);
(void)r;
}
end = clock();
linear_time = (double)(end - start) / CLOCKS_PER_SEC;
start = clock();
for (i = 0; i < NUM_LOOKUPS; i++)
{
int idx = 2 + (i % (num_models - 2));
volatile int r = find_model_hash(model_precache[idx]);
(void)r;
}
end = clock();
hash_time = (double)(end - start) / CLOCKS_PER_SEC;
ratio = linear_time / (hash_time > 0 ? hash_time : 0.0001);
printf("xonotic-0002: SV_ModelIndex linear scan defect\n");
printf(" Models: %d, Lookups: %d\n", num_models - 2, NUM_LOOKUPS);
printf(" Linear: %.4fs, Hash: %.4fs, Ratio: %.1fx\n", linear_time, hash_time, ratio);
if (ratio < 2.0)
{
printf("FAIL: expected hash lookup to be at least 2x faster (got %.1fx)\n", ratio);
pass = 0;
}
printf("%s\n", pass ? "PASS" : "FAIL");
return pass ? 0 : 1;
}