java-topology/defects/xonotic-0004/test/test_s_findname.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

142 lines
3.7 KiB
C

/**
* Unit test for xonotic-0004: S_FindName O(N) linked list scan.
*
* Defect: snd_main.c S_FindName() traverses a linked list of known sfx entries
* to find a sound by name. The code itself has a "TODO: hash table search?"
* comment. Called every time a sound is played, precached, or referenced.
* With many sounds loaded, each lookup is O(N).
*
* Fix: hash table on sfx name for O(1) average lookup.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define MAX_SFX 2000
#define NUM_LOOKUPS 100000
#define SFX_HASHSIZE 256
typedef struct sfx_s {
char name[64];
struct sfx_s *next;
struct sfx_s *hashnext;
} sfx_t;
static sfx_t sfx_pool[MAX_SFX];
static sfx_t *known_sfx = NULL;
static sfx_t *sfx_hashtable[SFX_HASHSIZE];
static unsigned sfx_hash(const char *name)
{
unsigned hash = 0;
const unsigned char *p;
for (p = (const unsigned char *)name; *p; p++)
hash = hash * 31 + *p;
return hash % SFX_HASHSIZE;
}
/* Unpatched: O(N) linked list scan */
static sfx_t *find_sfx_linear(const char *name)
{
sfx_t *sfx;
for (sfx = known_sfx; sfx != NULL; sfx = sfx->next)
if (!strcmp(sfx->name, name))
return sfx;
return NULL;
}
/* Patched: O(1) hash lookup */
static sfx_t *find_sfx_hash(const char *name)
{
unsigned h = sfx_hash(name);
sfx_t *sfx;
for (sfx = sfx_hashtable[h]; sfx != NULL; sfx = sfx->hashnext)
if (!strcmp(sfx->name, name))
return sfx;
return NULL;
}
int main(void)
{
int i, pass = 1;
clock_t start, end;
double linear_time, hash_time, ratio;
/* Build linked list and hash table */
memset(sfx_hashtable, 0, sizeof(sfx_hashtable));
known_sfx = NULL;
for (i = 0; i < MAX_SFX; i++)
{
sfx_t *s = &sfx_pool[i];
snprintf(s->name, sizeof(s->name), "sounds/fx/effect_%04d.ogg", i);
s->next = known_sfx;
known_sfx = s;
unsigned h = sfx_hash(s->name);
s->hashnext = sfx_hashtable[h];
sfx_hashtable[h] = s;
}
/* Correctness */
for (i = 0; i < MAX_SFX; i++)
{
sfx_t *lin = find_sfx_linear(sfx_pool[i].name);
sfx_t *hsh = find_sfx_hash(sfx_pool[i].name);
if (lin != hsh)
{
printf("FAIL: mismatch at sfx %d: linear=%p hash=%p\n", i, (void*)lin, (void*)hsh);
pass = 0;
}
}
/* Not-found */
if (find_sfx_hash("nonexistent.wav") != NULL)
{
printf("FAIL: hash found nonexistent sfx\n");
pass = 0;
}
if (find_sfx_linear("nonexistent.wav") != NULL)
{
printf("FAIL: linear found nonexistent sfx\n");
pass = 0;
}
/* Benchmark */
start = clock();
for (i = 0; i < NUM_LOOKUPS; i++)
{
int idx = i % MAX_SFX;
volatile sfx_t *r = find_sfx_linear(sfx_pool[idx].name);
(void)r;
}
end = clock();
linear_time = (double)(end - start) / CLOCKS_PER_SEC;
start = clock();
for (i = 0; i < NUM_LOOKUPS; i++)
{
int idx = i % MAX_SFX;
volatile sfx_t *r = find_sfx_hash(sfx_pool[idx].name);
(void)r;
}
end = clock();
hash_time = (double)(end - start) / CLOCKS_PER_SEC;
ratio = linear_time / (hash_time > 0 ? hash_time : 0.0001);
printf("xonotic-0004: S_FindName linked list scan defect\n");
printf(" SFX entries: %d, Lookups: %d\n", MAX_SFX, 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;
}