int prvm_type_size[8] = {1,sizeof(string_t)/4,1,3,1,1,sizeof(func_t)/4,sizeof(void *)/4};
ddef_t *PRVM_ED_FieldAtOfs(int ofs);
-qboolean PRVM_ED_ParseEpair(prvm_edict_t *ent, ddef_t *key, const char *s);
+qboolean PRVM_ED_ParseEpair(prvm_edict_t *ent, ddef_t *key, const char *s, qboolean parsebackslash);
-// LordHavoc: optional runtime bounds checking (speed drain, but worth it for security, on by default - breaks most QCCX features (used by CRMod and others))
-cvar_t prvm_boundscheck = {0, "prvm_boundscheck", "1", "enables detection of out of bounds memory access in the QuakeC code being run (in other words, prevents really exceedingly bad QuakeC code from doing nasty things to your computer)"};
// LordHavoc: prints every opcode as it executes - warning: this is significant spew
cvar_t prvm_traceqc = {0, "prvm_traceqc", "0", "prints every QuakeC statement as it is executed (only for really thorough debugging!)"};
// 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"};
+
+qboolean prvm_runawaycheck = true;
+
+// LordHavoc: optional runtime bounds checking (speed drain, but worth it for security, on by default - breaks most QCCX features (used by CRMod and others))
+// enables detection of out of bounds memory access in the QuakeC code being run (in other words, prevents really exceedingly bad QuakeC code from doing nasty things to your computer)
+qboolean prvm_boundscheck = true;
extern sizebuf_t vm_tempstringsbuf;
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
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;
}
}
e = PRVM_EDICT_NUM(i);
PRVM_ED_ClearEdict (e);
+ e->priv.required->allocation_origin = PRVM_AllocationOrigin();
+
return e;
}
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;
+ }
}
//===========================================================================
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;
}
line[i++] = '\\';
line[i++] = 'r';
}
+ else if (*s == '\\')
+ {
+ line[i++] = '\\';
+ line[i++] = '\\';
+ }
+ else if (*s == '"')
+ {
+ line[i++] = '\\';
+ line[i++] = '"';
+ }
else
line[i++] = *s;
s++;
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);
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++)
}
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 ; i<prog->progs->numfielddefs ; i++)
{
d = &prog->fielddefs[i];
For savegames
=============
*/
+extern cvar_t developer_entityparsing;
void PRVM_ED_Write (qfile_t *f, prvm_edict_t *ed)
{
ddef_t *d;
{
d = &prog->fielddefs[i];
name = PRVM_GetString(d->s_name);
+
+ if(developer_entityparsing.integer)
+ Con_Printf("PRVM_ED_Write: at entity %d field %s\n", PRVM_NUM_FOR_EDICT(ed), name);
+
if (name[strlen(name)-2] == '_')
continue; // skip _x, _y, _z vars
continue;
name = PRVM_GetString(def->s_name);
+
+ if(developer_entityparsing.integer)
+ Con_Printf("PRVM_ED_WriteGlobals: at global %s\n", name);
+
FS_Printf(f,"\"%s\" ", name);
FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString((etype_t)type, (prvm_eval_t *)&prog->globals.generic[def->ofs]));
}
while (1)
{
// parse key
- if (!COM_ParseToken_Simple(&data, false))
+ if (!COM_ParseToken_Simple(&data, false, false))
PRVM_ERROR ("PRVM_ED_ParseGlobals: EOF without closing brace");
if (com_token[0] == '}')
break;
+ if (developer_entityparsing.integer)
+ Con_Printf("Key: \"%s\"", com_token);
+
strlcpy (keyname, com_token, sizeof(keyname));
// parse value
- if (!COM_ParseToken_Simple(&data, false))
+ if (!COM_ParseToken_Simple(&data, false, true))
PRVM_ERROR ("PRVM_ED_ParseGlobals: EOF without closing brace");
+ if (developer_entityparsing.integer)
+ Con_Printf(" \"%s\"\n", com_token);
+
if (com_token[0] == '}')
PRVM_ERROR ("PRVM_ED_ParseGlobals: closing brace without data");
continue;
}
- if (!PRVM_ED_ParseEpair(NULL, key, com_token))
+ if (!PRVM_ED_ParseEpair(NULL, key, com_token, true))
PRVM_ERROR ("PRVM_ED_ParseGlobals: parse error");
}
}
returns false if error
=============
*/
-qboolean PRVM_ED_ParseEpair(prvm_edict_t *ent, ddef_t *key, const char *s)
+qboolean PRVM_ED_ParseEpair(prvm_edict_t *ent, ddef_t *key, const char *s, qboolean parsebackslash)
{
int i, l;
char *new_p;
val->string = PRVM_AllocString(l, &new_p);
for (i = 0;i < l;i++)
{
- if (s[i] == '\\' && i < l-1)
+ if (s[i] == '\\' && s[i+1] && parsebackslash)
{
i++;
if (s[i] == 'n')
break;
case ev_float:
- while (*s && *s <= ' ')
+ while (*s && ISWHITESPACE(*s))
s++;
val->_float = atof(s);
break;
case ev_vector:
for (i = 0;i < 3;i++)
{
- while (*s && *s <= ' ')
+ while (*s && ISWHITESPACE(*s))
s++;
if (!*s)
break;
val->vector[i] = atof(s);
- while (*s > ' ')
+ while (!ISWHITESPACE(*s))
s++;
if (!*s)
break;
break;
case ev_entity:
- while (*s && *s <= ' ')
+ while (*s && ISWHITESPACE(*s))
s++;
i = atoi(s);
if (i >= prog->limit_edicts)
break;
case ev_field:
- def = PRVM_ED_FindField(s);
+ if (*s != '.')
+ {
+ Con_DPrintf("PRVM_ED_ParseEpair: Bogus field name %s in %s\n", s, PRVM_NAME);
+ return false;
+ }
+ def = PRVM_ED_FindField(s + 1);
if (!def)
{
Con_DPrintf("PRVM_ED_ParseEpair: Can't find field %s in %s\n", s, PRVM_NAME);
if((key = PRVM_ED_FindField(Cmd_Argv(3))) == 0)
Con_Printf("Key %s not found !\n", Cmd_Argv(3));
else
- PRVM_ED_ParseEpair(ed, key, Cmd_Argv(4));
+ PRVM_ED_ParseEpair(ed, key, Cmd_Argv(4), true);
PRVM_End;
}
Used for initial level load and for savegames.
====================
*/
-extern cvar_t developer_entityparsing;
const char *PRVM_ED_ParseEdict (const char *data, prvm_edict_t *ent)
{
ddef_t *key;
while (1)
{
// parse key
- if (!COM_ParseToken_Simple(&data, false))
+ if (!COM_ParseToken_Simple(&data, false, false))
PRVM_ERROR ("PRVM_ED_ParseEdict: EOF without closing brace");
if (developer_entityparsing.integer)
Con_Printf("Key: \"%s\"", com_token);
}
// parse value
- if (!COM_ParseToken_Simple(&data, false))
+ if (!COM_ParseToken_Simple(&data, false, false))
PRVM_ERROR ("PRVM_ED_ParseEdict: EOF without closing brace");
if (developer_entityparsing.integer)
Con_Printf(" \"%s\"\n", com_token);
{
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))
+ if (!PRVM_ED_ParseEpair(ent, key, com_token, strcmp(keyname, "wad") != 0))
PRVM_ERROR ("PRVM_ED_ParseEdict: parse error");
}
{
prvm_edict_t *ent;
int parsed, inhibited, spawned, died;
+ const char *funcname;
mfunction_t *func;
parsed = 0;
while (1)
{
// parse the opening brace
- if (!COM_ParseToken_Simple(&data, false))
+ if (!COM_ParseToken_Simple(&data, false, false))
break;
if (com_token[0] != '{')
PRVM_ERROR ("PRVM_ED_LoadFromFile: %s: found %s when expecting {", PRVM_NAME, com_token);
}
// look for the spawn function
- func = PRVM_ED_FindFunction (PRVM_GetString(handle));
+ funcname = PRVM_GetString(handle);
+ func = PRVM_ED_FindFunction (va("spawnfunc_%s", funcname));
+ if(!func)
+ if(prog->globaloffsets.require_spawnfunc_prefix < 0)
+ func = PRVM_ED_FindFunction (funcname);
if (!func)
{
// 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");
prog->fieldoffsets.message = PRVM_ED_FindFieldOffset("message");
prog->fieldoffsets.modelflags = PRVM_ED_FindFieldOffset("modelflags");
prog->fieldoffsets.movement = PRVM_ED_FindFieldOffset("movement");
+ prog->fieldoffsets.movetypesteplandevent = PRVM_ED_FindFieldOffset("movetypesteplandevent");
prog->fieldoffsets.netaddress = PRVM_ED_FindFieldOffset("netaddress");
prog->fieldoffsets.nextthink = PRVM_ED_FindFieldOffset("nextthink");
prog->fieldoffsets.nodrawtoclient = PRVM_ED_FindFieldOffset("nodrawtoclient");
prog->fieldoffsets.viewmodelforclient = PRVM_ED_FindFieldOffset("viewmodelforclient");
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");
+ prog->funcoffsets.CSQC_Ent_Spawn = PRVM_ED_FindFunctionOffset("CSQC_Ent_Spawn");
prog->funcoffsets.CSQC_Event = PRVM_ED_FindFunctionOffset("CSQC_Event");
+ prog->funcoffsets.CSQC_Event_Sound = PRVM_ED_FindFunctionOffset("CSQC_Event_Sound");
prog->funcoffsets.CSQC_Init = PRVM_ED_FindFunctionOffset("CSQC_Init");
prog->funcoffsets.CSQC_InputEvent = PRVM_ED_FindFunctionOffset("CSQC_InputEvent");
prog->funcoffsets.CSQC_Parse_CenterPrint = PRVM_ED_FindFunctionOffset("CSQC_Parse_CenterPrint");
prog->funcoffsets.CSQC_Parse_TempEntity = PRVM_ED_FindFunctionOffset("CSQC_Parse_TempEntity");
prog->funcoffsets.CSQC_Shutdown = PRVM_ED_FindFunctionOffset("CSQC_Shutdown");
prog->funcoffsets.CSQC_UpdateView = PRVM_ED_FindFunctionOffset("CSQC_UpdateView");
+ prog->funcoffsets.Gecko_Query = PRVM_ED_FindFunctionOffset("Gecko_Query");
prog->funcoffsets.EndFrame = PRVM_ED_FindFunctionOffset("EndFrame");
prog->funcoffsets.RestoreGame = PRVM_ED_FindFunctionOffset("RestoreGame");
prog->funcoffsets.SV_ChangeTeam = PRVM_ED_FindFunctionOffset("SV_ChangeTeam");
prog->funcoffsets.SV_PlayerPhysics = PRVM_ED_FindFunctionOffset("SV_PlayerPhysics");
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");
prog->globaloffsets.dmg_save = PRVM_ED_FindGlobalOffset("dmg_save");
prog->globaloffsets.dmg_origin = PRVM_ED_FindGlobalOffset("dmg_origin");
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_display = PRVM_ED_FindFunctionOffset("m_display");
prog->funcoffsets.m_draw = PRVM_ED_FindFunctionOffset("m_draw");
- prog->funcoffsets.m_hide = PRVM_ED_FindFunctionOffset("m_hide");
prog->funcoffsets.m_init = PRVM_ED_FindFunctionOffset("m_init");
prog->funcoffsets.m_keydown = PRVM_ED_FindFunctionOffset("m_keydown");
prog->funcoffsets.m_keyup = PRVM_ED_FindFunctionOffset("m_keyup");
===============
*/
+void PRVM_LeakTest();
void PRVM_ResetProg()
{
+ PRVM_LeakTest();
PRVM_GCALL(reset_cmd)();
Mem_FreePool(&prog->progs_mempool);
memset(prog,0,sizeof(prvm_prog_t));
+ prog->starttime = Sys_DoubleTime();
}
/*
if (prog->progs->version != PROG_VERSION)
PRVM_ERROR ("%s: %s has wrong version number (%i should be %i)", PRVM_NAME, filename, prog->progs->version, PROG_VERSION);
- if (prog->progs->crc != prog->headercrc)
- PRVM_ERROR ("%s: %s system vars have been modified, progdefs.h is out of date", PRVM_NAME, filename);
+ if (prog->progs->crc != prog->headercrc && prog->progs->crc != prog->headercrc2)
+ PRVM_ERROR ("%s: %s system vars have been modified (CRC of progs.dat systemvars %i != engine %i), progdefs.h is out of date", PRVM_NAME, filename, prog->progs->crc, prog->headercrc);
//prog->functions = (dfunction_t *)((unsigned char *)progs + progs->ofs_functions);
dfunctions = (dfunction_t *)((unsigned char *)prog->progs + prog->progs->ofs_functions);
prog->knownstrings = NULL;
prog->knownstrings_freeable = NULL;
+ Mem_ExpandableArray_NewArray(&prog->stringbuffersarray, prog->progs_mempool, sizeof(prvm_stringbuffer_t), 64);
+
prog->globaldefs = (ddef_t *)((unsigned char *)prog->progs + prog->progs->ofs_globaldefs);
// we need to expand the fielddefs list to include all the engine fields,
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;
}
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)
if( !global )
Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(2), Cmd_Argv(1) );
else
- PRVM_ED_ParseEpair( NULL, global, Cmd_Argv(3) );
+ PRVM_ED_ParseEpair( NULL, global, Cmd_Argv(3), true );
PRVM_End;
}
Cmd_AddCommand ("prvm_edicts", PRVM_ED_PrintEdicts_f, "prints all data about all entities in the selected VM (server, client, menu)");
Cmd_AddCommand ("prvm_edictcount", PRVM_ED_Count_f, "prints number of active entities in the selected VM (server, client, menu)");
Cmd_AddCommand ("prvm_profile", PRVM_Profile_f, "prints execution statistics about the most used QuakeC functions in the selected VM (server, client, menu)");
+ Cmd_AddCommand ("prvm_callprofile", PRVM_CallProfile_f, "prints execution statistics about the most time consuming QuakeC calls from the engine in the selected VM (server, client, menu)");
Cmd_AddCommand ("prvm_fields", PRVM_Fields_f, "prints usage statistics on properties (how many entities have non-zero values) in the selected VM (server, client, menu)");
Cmd_AddCommand ("prvm_globals", PRVM_Globals_f, "prints all global variables in the selected VM (server, client, menu)");
Cmd_AddCommand ("prvm_global", PRVM_Global_f, "prints value of a specified global variable in the selected VM (server, client, menu)");
Cmd_AddCommand ("cl_cmd", PRVM_GameCommand_Client_f, "calls the client QC function GameCommand with the supplied string as argument");
Cmd_AddCommand ("menu_cmd", PRVM_GameCommand_Menu_f, "calls the menu QC function GameCommand with the supplied string as argument");
Cmd_AddCommand ("sv_cmd", PRVM_GameCommand_Server_f, "calls the server QC function GameCommand with the supplied string as argument");
- // LordHavoc: optional runtime bounds checking (speed drain, but worth it for security, on by default - breaks most QCCX features (used by CRMod and others))
- Cvar_RegisterVariable (&prvm_boundscheck);
+
+ // COMMANDLINEOPTION: PRVM: -noboundscheck disables the bounds checks (security hole if CSQC is in use!)
+ prvm_boundscheck = !COM_CheckParm("-noboundscheck");
+
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);
+
+ // COMMANDLINEOPTION: PRVM: -norunaway disables the runaway loop check (it might be impossible to exit DarkPlaces if used!)
+ prvm_runawaycheck = !COM_CheckParm("-norunaway");
//VM_Cmd_Init();
}
PRVM_ResetProg();
memset(prog, 0, sizeof(prvm_prog_t));
+ prog->starttime = Sys_DoubleTime();
prog->error_cmd = Host_Error;
+ prog->leaktest_active = prvm_leaktest.integer;
}
int PRVM_GetProgNr()
}
// LordHavoc: turned PRVM_EDICT_NUM into a #define for speed reasons
-prvm_edict_t *PRVM_EDICT_NUM_ERROR(int n, char *filename, int fileline)
+unsigned int PRVM_EDICT_NUM_ERROR(unsigned int n, char *filename, int fileline)
{
PRVM_ERROR ("PRVM_EDICT_NUM: %s: bad number %i (called at %s:%i)", PRVM_NAME, n, filename, fileline);
- return NULL;
+ return 0;
}
/*
return (char *)vm_tempstringsbuf.data + num;
else
{
- VM_Warning("PRVM_GetString: Invalid temp-string offset (%i >= %i vm_tempstringsbuf.cursize)", num, vm_tempstringsbuf.cursize);
+ 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)", num, prog->stringssize);
+ VM_Warning("PRVM_GetString: Invalid constant-string offset (%i >= %i prog->stringssize)\n", num, prog->stringssize);
return "";
}
}
return (char *)vm_tempstringsbuf.data + num;
else
{
- VM_Warning("PRVM_GetString: Invalid temp-string offset (%i >= %i vm_tempstringsbuf.cursize)", num, vm_tempstringsbuf.cursize);
+ VM_Warning("PRVM_GetString: Invalid temp-string offset (%i >= %i vm_tempstringsbuf.cursize)\n", num, vm_tempstringsbuf.cursize);
return "";
}
}
if (num < prog->numknownstrings)
{
if (!prog->knownstrings[num])
- VM_Warning("PRVM_GetString: Invalid zone-string offset (%i has been freed)", 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)", num, prog->numknownstrings);
+ VM_Warning("PRVM_GetString: Invalid zone-string offset (%i >= %i)\n", num, prog->numknownstrings);
return "";
}
}
{
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;
}
{
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;
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);
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");
+}