+
+sizebuf_t vm_tempstringsbuf;
+
+const char *PRVM_GetString(int num)
+{
+ if (num >= 0)
+ {
+ if (num < prog->stringssize)
+ return prog->strings + num;
+ else
+#if 1
+ if (num <= prog->stringssize + vm_tempstringsbuf.maxsize)
+ {
+ num -= prog->stringssize;
+ if (num < vm_tempstringsbuf.cursize)
+ return (char *)vm_tempstringsbuf.data + num;
+ else
+ {
+ VM_Warning("PRVM_GetString: Invalid temp-string offset (%i >= %i vm_tempstringsbuf.cursize)\n", num, vm_tempstringsbuf.cursize);
+ return "";
+ }
+ }
+ else
+#endif
+ {
+ VM_Warning("PRVM_GetString: Invalid constant-string offset (%i >= %i prog->stringssize)\n", num, prog->stringssize);
+ return "";
+ }
+ }
+ else
+ {
+ num = -1 - num;
+#if 0
+ if (num >= (1<<30))
+ {
+ // special range reserved for tempstrings
+ num -= (1<<30);
+ if (num < vm_tempstringsbuf.cursize)
+ return (char *)vm_tempstringsbuf.data + num;
+ else
+ {
+ VM_Warning("PRVM_GetString: Invalid temp-string offset (%i >= %i vm_tempstringsbuf.cursize)\n", num, vm_tempstringsbuf.cursize);
+ return "";
+ }
+ }
+ else
+#endif
+ if (num < prog->numknownstrings)
+ {
+ if (!prog->knownstrings[num])
+ VM_Warning("PRVM_GetString: Invalid zone-string offset (%i has been freed)\n", num);
+ return prog->knownstrings[num];
+ }
+ else
+ {
+ VM_Warning("PRVM_GetString: Invalid zone-string offset (%i >= %i)\n", num, prog->numknownstrings);
+ return "";
+ }
+ }
+}
+
+int PRVM_SetEngineString(const char *s)
+{
+ int i;
+ if (!s)
+ return 0;
+ if (s >= prog->strings && s <= prog->strings + prog->stringssize)
+ PRVM_ERROR("PRVM_SetEngineString: s in prog->strings area");
+ // if it's in the tempstrings area, use a reserved range
+ // (otherwise we'd get millions of useless string offsets cluttering the database)
+ if (s >= (char *)vm_tempstringsbuf.data && s < (char *)vm_tempstringsbuf.data + vm_tempstringsbuf.maxsize)
+#if 1
+ return prog->stringssize + (s - (char *)vm_tempstringsbuf.data);
+#else
+ return -1 - ((1<<30) + (s - (char *)vm_tempstringsbuf.data));
+#endif
+ // see if it's a known string address
+ for (i = 0;i < prog->numknownstrings;i++)
+ if (prog->knownstrings[i] == s)
+ return -1 - i;
+ // new unknown engine string
+ if (developer.integer >= 200)
+ Con_Printf("new engine string %p = \"%s\"\n", s, s);
+ for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++)
+ if (!prog->knownstrings[i])
+ break;
+ if (i >= prog->numknownstrings)
+ {
+ if (i >= prog->maxknownstrings)
+ {
+ const char **oldstrings = prog->knownstrings;
+ const unsigned char *oldstrings_freeable = prog->knownstrings_freeable;
+ const char **oldstrings_origin = prog->knownstrings_origin;
+ prog->maxknownstrings += 128;
+ prog->knownstrings = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *));
+ prog->knownstrings_freeable = (unsigned char *)PRVM_Alloc(prog->maxknownstrings * sizeof(unsigned char));
+ if(prog->leaktest_active)
+ prog->knownstrings_origin = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *));
+ if (prog->numknownstrings)
+ {
+ memcpy((char **)prog->knownstrings, oldstrings, prog->numknownstrings * sizeof(char *));
+ memcpy((char **)prog->knownstrings_freeable, oldstrings_freeable, prog->numknownstrings * sizeof(unsigned char));
+ if(prog->leaktest_active)
+ memcpy((char **)prog->knownstrings_origin, oldstrings_origin, prog->numknownstrings * sizeof(char *));
+ }
+ }
+ prog->numknownstrings++;
+ }
+ prog->firstfreeknownstring = i + 1;
+ prog->knownstrings[i] = s;
+ prog->knownstrings_freeable[i] = false;
+ if(prog->leaktest_active)
+ prog->knownstrings_origin[i] = NULL;
+ return -1 - i;
+}
+
+// temp string handling
+
+// all tempstrings go into this buffer consecutively, and it is reset
+// whenever PRVM_ExecuteProgram returns to the engine
+// (technically each PRVM_ExecuteProgram call saves the cursize value and
+// restores it on return, so multiple recursive calls can share the same
+// buffer)
+// the buffer size is automatically grown as needed
+
+int PRVM_SetTempString(const char *s)
+{
+ int size;
+ char *t;
+ if (!s)
+ return 0;
+ size = (int)strlen(s) + 1;
+ if (developer.integer >= 300)
+ Con_Printf("PRVM_SetTempString: cursize %i, size %i\n", vm_tempstringsbuf.cursize, size);
+ if (vm_tempstringsbuf.maxsize < vm_tempstringsbuf.cursize + size)
+ {
+ sizebuf_t old = vm_tempstringsbuf;
+ if (vm_tempstringsbuf.cursize + size >= 1<<28)
+ PRVM_ERROR("PRVM_SetTempString: ran out of tempstring memory! (refusing to grow tempstring buffer over 256MB, cursize %i, size %i)\n", vm_tempstringsbuf.cursize, size);
+ vm_tempstringsbuf.maxsize = max(vm_tempstringsbuf.maxsize, 65536);
+ while (vm_tempstringsbuf.maxsize < vm_tempstringsbuf.cursize + size)
+ vm_tempstringsbuf.maxsize *= 2;
+ if (vm_tempstringsbuf.maxsize != old.maxsize || vm_tempstringsbuf.data == NULL)
+ {
+ if (developer.integer >= 100)
+ Con_Printf("PRVM_SetTempString: enlarging tempstrings buffer (%iKB -> %iKB)\n", old.maxsize/1024, vm_tempstringsbuf.maxsize/1024);
+ vm_tempstringsbuf.data = Mem_Alloc(sv_mempool, vm_tempstringsbuf.maxsize);
+ if (old.cursize)
+ memcpy(vm_tempstringsbuf.data, old.data, old.cursize);
+ if (old.data)
+ Mem_Free(old.data);
+ }
+ }
+ t = (char *)vm_tempstringsbuf.data + vm_tempstringsbuf.cursize;
+ memcpy(t, s, size);
+ vm_tempstringsbuf.cursize += size;
+ return PRVM_SetEngineString(t);
+}
+
+int PRVM_AllocString(size_t bufferlength, char **pointer)
+{
+ int i;
+ if (!bufferlength)
+ return 0;
+ for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++)
+ if (!prog->knownstrings[i])
+ break;
+ if (i >= prog->numknownstrings)
+ {
+ if (i >= prog->maxknownstrings)
+ {
+ const char **oldstrings = prog->knownstrings;
+ const unsigned char *oldstrings_freeable = prog->knownstrings_freeable;
+ const char **oldstrings_origin = prog->knownstrings_origin;
+ prog->maxknownstrings += 128;
+ prog->knownstrings = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *));
+ prog->knownstrings_freeable = (unsigned char *)PRVM_Alloc(prog->maxknownstrings * sizeof(unsigned char));
+ if(prog->leaktest_active)
+ prog->knownstrings_origin = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *));
+ if (prog->numknownstrings)
+ {
+ memcpy((char **)prog->knownstrings, oldstrings, prog->numknownstrings * sizeof(char *));
+ memcpy((char **)prog->knownstrings_freeable, oldstrings_freeable, prog->numknownstrings * sizeof(unsigned char));
+ if(prog->leaktest_active)
+ memcpy((char **)prog->knownstrings_origin, oldstrings_origin, prog->numknownstrings * sizeof(char *));
+ }
+ // TODO why not Mem_Free the old ones?
+ }
+ prog->numknownstrings++;
+ }
+ prog->firstfreeknownstring = i + 1;
+ prog->knownstrings[i] = (char *)PRVM_Alloc(bufferlength);
+ prog->knownstrings_freeable[i] = true;
+ if(prog->leaktest_active)
+ prog->knownstrings_origin[i] = PRVM_AllocationOrigin();
+ if (pointer)
+ *pointer = (char *)(prog->knownstrings[i]);
+ return -1 - i;
+}
+
+void PRVM_FreeString(int num)
+{
+ if (num == 0)
+ PRVM_ERROR("PRVM_FreeString: attempt to free a NULL string");
+ else if (num >= 0 && num < prog->stringssize)
+ PRVM_ERROR("PRVM_FreeString: attempt to free a constant string");
+ else if (num < 0 && num >= -prog->numknownstrings)
+ {
+ num = -1 - num;
+ if (!prog->knownstrings[num])
+ PRVM_ERROR("PRVM_FreeString: attempt to free a non-existent or already freed string");
+ if (!prog->knownstrings_freeable[num])
+ PRVM_ERROR("PRVM_FreeString: attempt to free a string owned by the engine");
+ PRVM_Free((char *)prog->knownstrings[num]);
+ if(prog->leaktest_active)
+ if(prog->knownstrings_origin[num])
+ PRVM_Free((char *)prog->knownstrings_origin[num]);
+ prog->knownstrings[num] = NULL;
+ prog->knownstrings_freeable[num] = false;
+ prog->firstfreeknownstring = min(prog->firstfreeknownstring, num);
+ }
+ else
+ PRVM_ERROR("PRVM_FreeString: invalid string offset %i", num);
+}
+
+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 *) &((float*)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 *) &((float*)ed->fields.vp)[d->ofs])->edict)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void PRVM_MarkReferencedEdicts()
+{
+ 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()
+{
+ 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 = 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");
+}