X-Git-Url: http://de.git.xonotic.org/?a=blobdiff_plain;f=prvm_edict.c;h=7c6e16ce3eff009f21b462068d8f9c46c1cbc51e;hb=5090f6da276d673f6ee0b149ee2f83ef82f62902;hp=876a9bbd49eebe7c550dae9c94d1e77692d200ef;hpb=3a55bf49edf17edc0e08424f575aeb9d8c376e78;p=xonotic%2Fdarkplaces.git diff --git a/prvm_edict.c b/prvm_edict.c index 876a9bbd..7c6e16ce 100644 --- a/prvm_edict.c +++ b/prvm_edict.c @@ -40,6 +40,9 @@ cvar_t prvm_traceqc = {0, "prvm_traceqc", "0", "prints every QuakeC statement as // LordHavoc: counts usage of each QuakeC statement cvar_t prvm_statementprofiling = {0, "prvm_statementprofiling", "0", "counts how many times each QuakeC statement has been executed, these counts are displayed in prvm_printfunction output (if enabled)"}; cvar_t prvm_backtraceforwarnings = {0, "prvm_backtraceforwarnings", "0", "print a backtrace for warnings too"}; +cvar_t prvm_leaktest = {0, "prvm_leaktest", "0", "try to detect memory leaks in strings or entities"}; +cvar_t prvm_leaktest_ignore_classnames = {0, "prvm_leaktest_ignore_classnames", "", "classnames of entities to NOT leak check because they are found by find(world, classname, ...) but are actually spawned by QC code (NOT map entities)"}; +cvar_t prvm_errordump = {0, "prvm_errordump", "0", "write a savegame on crash to crash-server.dmp"}; extern sizebuf_t vm_tempstringsbuf; @@ -219,6 +222,18 @@ void PRVM_ED_ClearEdict (prvm_edict_t *e) PRVM_GCALL(init_edict)(e); } +const char *PRVM_AllocationOrigin() +{ + char *buf = NULL; + if(prog->leaktest_active) + if(prog->depth > 0) // actually in QC code and not just parsing the entities block of a map/savegame + { + buf = (char *)PRVM_Alloc(128); + PRVM_ShortStackTrace(buf, 128); + } + return buf; +} + /* ================= PRVM_ED_Alloc @@ -248,6 +263,7 @@ prvm_edict_t *PRVM_ED_Alloc (void) if (e->priv.required->free && ( e->priv.required->freetime < 2 || prog->globaloffsets.time < 0 || (PRVM_GLOBALFIELDVALUE(prog->globaloffsets.time)->_float - e->priv.required->freetime) > 0.5 ) ) { PRVM_ED_ClearEdict (e); + e->priv.required->allocation_origin = PRVM_AllocationOrigin(); return e; } } @@ -262,6 +278,8 @@ prvm_edict_t *PRVM_ED_Alloc (void) e = PRVM_EDICT_NUM(i); PRVM_ED_ClearEdict (e); + e->priv.required->allocation_origin = PRVM_AllocationOrigin(); + return e; } @@ -283,6 +301,11 @@ void PRVM_ED_Free (prvm_edict_t *ed) ed->priv.required->free = true; ed->priv.required->freetime = prog->globaloffsets.time >= 0 ? PRVM_GLOBALFIELDVALUE(prog->globaloffsets.time)->_float : 0; + if(ed->priv.required->allocation_origin) + { + PRVM_Free((char *)ed->priv.required->allocation_origin); + ed->priv.required->allocation_origin = NULL; + } } //=========================================================================== @@ -408,34 +431,34 @@ char *PRVM_ValueString (etype_t type, prvm_eval_t *val) case ev_entity: n = val->edict; if (n < 0 || n >= prog->limit_edicts) - sprintf (line, "entity %i (invalid!)", n); + dpsnprintf (line, sizeof(line), "entity %i (invalid!)", n); else - sprintf (line, "entity %i", n); + dpsnprintf (line, sizeof(line), "entity %i", n); break; case ev_function: f = prog->functions + val->function; - sprintf (line, "%s()", PRVM_GetString(f->s_name)); + dpsnprintf (line, sizeof(line), "%s()", PRVM_GetString(f->s_name)); break; case ev_field: def = PRVM_ED_FieldAtOfs ( val->_int ); - sprintf (line, ".%s", PRVM_GetString(def->s_name)); + dpsnprintf (line, sizeof(line), ".%s", PRVM_GetString(def->s_name)); break; case ev_void: - sprintf (line, "void"); + dpsnprintf (line, sizeof(line), "void"); break; case ev_float: // LordHavoc: changed from %5.1f to %10.4f - sprintf (line, "%10.4f", val->_float); + dpsnprintf (line, sizeof(line), "%10.4f", val->_float); break; case ev_vector: // LordHavoc: changed from %5.1f to %10.4f - sprintf (line, "'%10.4f %10.4f %10.4f'", val->vector[0], val->vector[1], val->vector[2]); + dpsnprintf (line, sizeof(line), "'%10.4f %10.4f %10.4f'", val->vector[0], val->vector[1], val->vector[2]); break; case ev_pointer: - sprintf (line, "pointer"); + dpsnprintf (line, sizeof(line), "pointer"); break; default: - sprintf (line, "bad type %i", (int) type); + dpsnprintf (line, sizeof(line), "bad type %i", (int) type); break; } @@ -537,11 +560,11 @@ char *PRVM_GlobalString (int ofs) val = (void *)&prog->globals.generic[ofs]; def = PRVM_ED_GlobalAtOfs(ofs); if (!def) - sprintf (line,"GLOBAL%i", ofs); + dpsnprintf (line, sizeof(line), "GLOBAL%i", ofs); else { s = PRVM_ValueString ((etype_t)def->type, (prvm_eval_t *)val); - sprintf (line,"%s (=%s)", PRVM_GetString(def->s_name), s); + dpsnprintf (line, sizeof(line), "%s (=%s)", PRVM_GetString(def->s_name), s); } //i = strlen(line); @@ -560,9 +583,9 @@ char *PRVM_GlobalStringNoContents (int ofs) def = PRVM_ED_GlobalAtOfs(ofs); if (!def) - sprintf (line,"GLOBAL%i", ofs); + dpsnprintf (line, sizeof(line), "GLOBAL%i", ofs); else - sprintf (line,"%s", PRVM_GetString(def->s_name)); + dpsnprintf (line, sizeof(line), "%s", PRVM_GetString(def->s_name)); //i = strlen(line); //for ( ; i<20 ; i++) @@ -599,7 +622,7 @@ void PRVM_ED_Print(prvm_edict_t *ed, const char *wildcard_fieldname) } tempstring[0] = 0; - sprintf(tempstring, "\n%s EDICT %i:\n", PRVM_NAME, PRVM_NUM_FOR_EDICT(ed)); + dpsnprintf(tempstring, sizeof(tempstring), "\n%s EDICT %i:\n", PRVM_NAME, PRVM_NUM_FOR_EDICT(ed)); for (i=1 ; iprogs->numfielddefs ; i++) { d = &prog->fielddefs[i]; @@ -1204,7 +1227,7 @@ const char *PRVM_ED_ParseEdict (const char *data, prvm_edict_t *ent) { char temp[32]; strlcpy (temp, com_token, sizeof(temp)); - sprintf (com_token, "0 %s 0", temp); + dpsnprintf (com_token, sizeof(com_token), "0 %s 0", temp); } if (!PRVM_ED_ParseEpair(ent, key, com_token, strcmp(keyname, "wad") != 0)) @@ -1346,6 +1369,7 @@ void PRVM_FindOffsets(void) // server and client qc use a lot of similar fields, so this is combined prog->fieldoffsets.SendEntity = PRVM_ED_FindFieldOffset("SendEntity"); + prog->fieldoffsets.SendFlags = PRVM_ED_FindFieldOffset("SendFlags"); prog->fieldoffsets.Version = PRVM_ED_FindFieldOffset("Version"); prog->fieldoffsets.alpha = PRVM_ED_FindFieldOffset("alpha"); prog->fieldoffsets.ammo_cells1 = PRVM_ED_FindFieldOffset("ammo_cells1"); @@ -1434,6 +1458,7 @@ void PRVM_FindOffsets(void) prog->fieldoffsets.viewzoom = PRVM_ED_FindFieldOffset("viewzoom"); prog->fieldoffsets.yaw_speed = PRVM_ED_FindFieldOffset("yaw_speed"); prog->fieldoffsets.clientcamera = PRVM_ED_FindFieldOffset("clientcamera"); + prog->fieldoffsets.clientstatus = PRVM_ED_FindFieldOffset("clientstatus"); prog->funcoffsets.CSQC_ConsoleCommand = PRVM_ED_FindFunctionOffset("CSQC_ConsoleCommand"); prog->funcoffsets.CSQC_Ent_Remove = PRVM_ED_FindFunctionOffset("CSQC_Ent_Remove"); prog->funcoffsets.CSQC_Ent_Update = PRVM_ED_FindFunctionOffset("CSQC_Ent_Update"); @@ -1457,6 +1482,7 @@ void PRVM_FindOffsets(void) prog->funcoffsets.SV_OnEntityNoSpawnFunction = PRVM_ED_FindFunctionOffset("SV_OnEntityNoSpawnFunction"); prog->funcoffsets.GameCommand = PRVM_ED_FindFunctionOffset("GameCommand"); prog->funcoffsets.SV_Shutdown = PRVM_ED_FindFunctionOffset("SV_Shutdown"); + prog->funcoffsets.URI_Get_Callback = PRVM_ED_FindFunctionOffset("URI_Get_Callback"); prog->globaloffsets.SV_InitCmd = PRVM_ED_FindGlobalOffset("SV_InitCmd"); prog->globaloffsets.self = PRVM_ED_FindGlobalOffset("self"); prog->globaloffsets.time = PRVM_ED_FindGlobalOffset("time"); @@ -1486,6 +1512,7 @@ void PRVM_FindOffsets(void) prog->globaloffsets.sb_showscores = PRVM_ED_FindGlobalOffset("sb_showscores"); prog->globaloffsets.drawfont = PRVM_ED_FindGlobalOffset("drawfont"); prog->globaloffsets.require_spawnfunc_prefix = PRVM_ED_FindGlobalOffset("require_spawnfunc_prefix"); + prog->globaloffsets.worldstatus = PRVM_ED_FindGlobalOffset("worldstatus"); // menu qc only uses some functions, nothing else prog->funcoffsets.m_draw = PRVM_ED_FindFunctionOffset("m_draw"); @@ -1518,8 +1545,10 @@ PRVM_ResetProg =============== */ +void PRVM_LeakTest(); void PRVM_ResetProg() { + PRVM_LeakTest(); PRVM_GCALL(reset_cmd)(); Mem_FreePool(&prog->progs_mempool); memset(prog,0,sizeof(prvm_prog_t)); @@ -1915,7 +1944,7 @@ void PRVM_Fields_f (void) strlcat(tempstring, "pointer ", sizeof(tempstring)); break; default: - sprintf (tempstring2, "bad type %i ", d->type & ~DEF_SAVEGLOBAL); + dpsnprintf (tempstring2, sizeof(tempstring2), "bad type %i ", d->type & ~DEF_SAVEGLOBAL); strlcat(tempstring, tempstring2, sizeof(tempstring)); break; } @@ -1929,7 +1958,7 @@ void PRVM_Fields_f (void) strlcat(tempstring, name, sizeof(tempstring)); for (j = (int)strlen(name);j < 25;j++) strlcat(tempstring, " ", sizeof(tempstring)); - sprintf(tempstring2, "%5d", counts[i]); + dpsnprintf(tempstring2, sizeof(tempstring2), "%5d", counts[i]); strlcat(tempstring, tempstring2, sizeof(tempstring)); strlcat(tempstring, "\n", sizeof(tempstring)); if (strlen(tempstring) >= sizeof(tempstring)/2) @@ -2071,6 +2100,9 @@ void PRVM_Init (void) Cvar_RegisterVariable (&prvm_traceqc); Cvar_RegisterVariable (&prvm_statementprofiling); Cvar_RegisterVariable (&prvm_backtraceforwarnings); + Cvar_RegisterVariable (&prvm_leaktest); + Cvar_RegisterVariable (&prvm_leaktest_ignore_classnames); + Cvar_RegisterVariable (&prvm_errordump); //VM_Cmd_Init(); } @@ -2094,6 +2126,7 @@ void PRVM_InitProg(int prognr) prog->starttime = Sys_DoubleTime(); prog->error_cmd = Host_Error; + prog->leaktest_active = prvm_leaktest.integer; } int PRVM_GetProgNr() @@ -2259,19 +2292,27 @@ int PRVM_SetEngineString(const char *s) { 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; } @@ -2332,20 +2373,28 @@ int PRVM_AllocString(size_t bufferlength, char **pointer) { 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; @@ -2362,9 +2411,12 @@ void PRVM_FreeString(int num) num = -1 - num; if (!prog->knownstrings[num]) PRVM_ERROR("PRVM_FreeString: attempt to free a non-existent or already freed string"); - if (!prog->knownstrings[num]) + 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); @@ -2373,3 +2425,250 @@ void PRVM_FreeString(int num) 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; iprogs->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; iprogs->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"); +}