+static qboolean PRVM_IsStringReferenced(string_t string)
+{
+ int i, j;
+
+ for (i = 0;i < prog->progs->numglobaldefs;i++)
+ {
+ ddef_t *d = &prog->globaldefs[i];
+ if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string)
+ continue;
+ if(string == ((prvm_eval_t *) &prog->globals.generic[d->ofs])->string)
+ return true;
+ }
+
+ for(j = 0; j < prog->num_edicts; ++j)
+ {
+ prvm_edict_t *ed = PRVM_EDICT_NUM(j);
+ if (ed->priv.required->free)
+ continue;
+ for (i=0; i<prog->progs->numfielddefs; ++i)
+ {
+ ddef_t *d = &prog->fielddefs[i];
+ if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string)
+ continue;
+ if(string == ((prvm_eval_t *) &ed->fields.vp[d->ofs])->string)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static qboolean PRVM_IsEdictRelevant(prvm_edict_t *edict)
+{
+ if(PRVM_NUM_FOR_EDICT(edict) <= prog->reserved_edicts)
+ return true; // world or clients
+ switch(prog - prog_list)
+ {
+ case PRVM_SERVERPROG:
+ {
+ entvars_t *ev = edict->fields.server;
+ if(ev->solid) // can block other stuff, or is a trigger?
+ return true;
+ if(ev->modelindex) // visible ent?
+ return true;
+ if(ev->effects) // particle effect?
+ return true;
+ if(ev->think) // has a think function?
+ if(ev->nextthink > 0) // that actually will eventually run?
+ return true;
+ if(ev->takedamage)
+ return true;
+ if(*prvm_leaktest_ignore_classnames.string)
+ {
+ if(strstr(va(" %s ", prvm_leaktest_ignore_classnames.string), va(" %s ", PRVM_GetString(ev->classname))))
+ return true;
+ }
+ }
+ break;
+ case PRVM_CLIENTPROG:
+ {
+ // TODO someone add more stuff here
+ cl_entvars_t *ev = edict->fields.client;
+ if(ev->entnum) // csqc networked
+ return true;
+ if(ev->modelindex) // visible ent?
+ return true;
+ if(ev->effects) // particle effect?
+ return true;
+ if(ev->think) // has a think function?
+ if(ev->nextthink > 0) // that actually will eventually run?
+ return true;
+ if(*prvm_leaktest_ignore_classnames.string)
+ {
+ if(strstr(va(" %s ", prvm_leaktest_ignore_classnames.string), va(" %s ", PRVM_GetString(ev->classname))))
+ return true;
+ }
+ }
+ break;
+ case PRVM_MENUPROG:
+ // menu prog does not have classnames
+ break;
+ }
+ return false;
+}
+
+static qboolean PRVM_IsEdictReferenced(prvm_edict_t *edict, int mark)
+{
+ int i, j;
+ int edictnum = PRVM_NUM_FOR_EDICT(edict);
+ const char *targetname = NULL;
+
+ switch(prog - prog_list)
+ {
+ case PRVM_SERVERPROG:
+ targetname = PRVM_GetString(edict->fields.server->targetname);
+ break;
+ }
+
+ if(targetname)
+ if(!*targetname) // ""
+ targetname = NULL;
+
+ for (i = 0;i < prog->progs->numglobaldefs;i++)
+ {
+ ddef_t *d = &prog->globaldefs[i];
+ if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity)
+ continue;
+ if(edictnum == ((prvm_eval_t *) &prog->globals.generic[d->ofs])->edict)
+ return true;
+ }
+
+ for(j = 0; j < prog->num_edicts; ++j)
+ {
+ prvm_edict_t *ed = PRVM_EDICT_NUM(j);
+ if (ed->priv.required->mark < mark)
+ continue;
+ if(ed == edict)
+ continue;
+ if(targetname)
+ {
+ const char *target = PRVM_GetString(ed->fields.server->target);
+ if(target)
+ if(!strcmp(target, targetname))
+ return true;
+ }
+ for (i=0; i<prog->progs->numfielddefs; ++i)
+ {
+ ddef_t *d = &prog->fielddefs[i];
+ if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity)
+ continue;
+ if(edictnum == ((prvm_eval_t *) &ed->fields.vp[d->ofs])->edict)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void PRVM_MarkReferencedEdicts(void)
+{
+ int j;
+ qboolean found_new;
+ int stage;
+
+ for(j = 0; j < prog->num_edicts; ++j)
+ {
+ prvm_edict_t *ed = PRVM_EDICT_NUM(j);
+ if(ed->priv.required->free)
+ continue;
+ ed->priv.required->mark = PRVM_IsEdictRelevant(ed) ? 1 : 0;
+ }
+
+ stage = 1;
+ do
+ {
+ found_new = false;
+ for(j = 0; j < prog->num_edicts; ++j)
+ {
+ prvm_edict_t *ed = PRVM_EDICT_NUM(j);
+ if(ed->priv.required->free)
+ continue;
+ if(ed->priv.required->mark)
+ continue;
+ if(PRVM_IsEdictReferenced(ed, stage))
+ {
+ ed->priv.required->mark = stage + 1;
+ found_new = true;
+ }
+ }
+ ++stage;
+ }
+ while(found_new);
+ Con_DPrintf("leak check used %d stages to find all references\n", stage);
+}
+
+void PRVM_LeakTest(void)
+{
+ int i, j;
+ qboolean leaked = false;
+
+ if(!prog->leaktest_active)
+ return;
+
+ // 1. Strings
+ for (i = 0; i < prog->numknownstrings; ++i)
+ {
+ if(prog->knownstrings[i])
+ if(prog->knownstrings_freeable[i])
+ if(prog->knownstrings_origin[i])
+ if(!PRVM_IsStringReferenced(-1 - i))
+ {
+ Con_Printf("Unreferenced string found!\n Value: %s\n Origin: %s\n", prog->knownstrings[i], prog->knownstrings_origin[i]);
+ leaked = true;
+ }
+ }
+
+ // 2. Edicts
+ PRVM_MarkReferencedEdicts();
+ for(j = 0; j < prog->num_edicts; ++j)
+ {
+ prvm_edict_t *ed = PRVM_EDICT_NUM(j);
+ if(ed->priv.required->free)
+ continue;
+ if(!ed->priv.required->mark)
+ if(ed->priv.required->allocation_origin)
+ {
+ Con_Printf("Unreferenced edict found!\n Allocated at: %s\n", ed->priv.required->allocation_origin);
+ PRVM_ED_Print(ed, NULL);
+ Con_Print("\n");
+ leaked = true;
+ }
+ }
+
+ for (i = 0; i < (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); ++i)
+ {
+ prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
+ if(stringbuffer)
+ if(stringbuffer->origin)
+ {
+ Con_Printf("Open string buffer handle found!\n Allocated at: %s\n", stringbuffer->origin);
+ leaked = true;
+ }
+ }
+
+ for(i = 0; i < PRVM_MAX_OPENFILES; ++i)
+ {
+ if(prog->openfiles[i])
+ if(prog->openfiles_origin[i])
+ {
+ Con_Printf("Open file handle found!\n Allocated at: %s\n", prog->openfiles_origin[i]);
+ leaked = true;
+ }
+ }
+
+ for(i = 0; i < PRVM_MAX_OPENSEARCHES; ++i)
+ {
+ if(prog->opensearches[i])
+ if(prog->opensearches_origin[i])
+ {
+ Con_Printf("Open search handle found!\n Allocated at: %s\n", prog->opensearches_origin[i]);
+ leaked = true;
+ }
+ }
+
+ if(!leaked)
+ Con_Printf("Congratulations. No leaks found.\n");
+}