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))
-#ifdef PRVM_BOUNDSCHECK_CVAR
-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)"};
-#endif
// 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;
FS_Printf(f,"\"%s\" ",name);
+ prog->statestring = va("PRVM_ED_Write, ent=%d, name=%s", i, name);
FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString((etype_t)d->type, (prvm_eval_t *)v));
+ prog->statestring = NULL;
}
FS_Print(f, "}\n");
continue;
name = PRVM_GetString(def->s_name);
+
+ if(developer_entityparsing.integer)
+ Con_Printf("PRVM_ED_WriteGlobals: at global %s\n", name);
+
+ prog->statestring = va("PRVM_ED_WriteGlobals, name=%s", name);
FS_Printf(f,"\"%s\" ", name);
FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString((etype_t)type, (prvm_eval_t *)&prog->globals.generic[def->ofs]));
+ prog->statestring = NULL;
}
FS_Print(f,"}\n");
}
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, 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)
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;
}
// parse value
- if (!COM_ParseToken_Simple(&data, false, true))
+ 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");
}
continue;
}
+ if (prog->funcoffsets.SV_OnEntityPreSpawnFunction)
+ {
+ // self = ent
+ PRVM_GLOBALFIELDVALUE(prog->globaloffsets.self)->edict = PRVM_EDICT_TO_PROG(ent);
+ PRVM_ExecuteProgram (prog->funcoffsets.SV_OnEntityPreSpawnFunction, "QC function SV_OnEntityPreSpawnFunction is missing");
+ }
+
+ if(ent->priv.required->free)
+ {
+ inhibited++;
+ continue;
+ }
+
//
// immediately call spawn function, but only if there is a self global and a classname
//
+ if(!ent->priv.required->free)
if(prog->globaloffsets.self >= 0 && prog->fieldoffsets.classname >= 0)
{
string_t handle = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.classname)->string;
funcname = PRVM_GetString(handle);
func = PRVM_ED_FindFunction (va("spawnfunc_%s", funcname));
if(!func)
- func = PRVM_ED_FindFunction (funcname);
+ if(prog->globaloffsets.require_spawnfunc_prefix < 0)
+ func = PRVM_ED_FindFunction (funcname);
if (!func)
{
PRVM_ED_Print(ent, NULL);
}
PRVM_ED_Free (ent);
- continue;
+ continue; // not included in "inhibited" count
}
}
else
}
}
+ if(!ent->priv.required->free)
+ if (prog->funcoffsets.SV_OnEntityPostSpawnFunction)
+ {
+ // self = ent
+ PRVM_GLOBALFIELDVALUE(prog->globaloffsets.self)->edict = PRVM_EDICT_TO_PROG(ent);
+ PRVM_ExecuteProgram (prog->funcoffsets.SV_OnEntityPostSpawnFunction, "QC function SV_OnEntityPostSpawnFunction is missing");
+ }
+
spawned++;
if (ent->priv.required->free)
died++;
// 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.buttonuse = PRVM_ED_FindFieldOffset("buttonuse");
prog->fieldoffsets.chain = PRVM_ED_FindFieldOffset("chain");
prog->fieldoffsets.classname = PRVM_ED_FindFieldOffset("classname");
+ prog->fieldoffsets.clientcamera = PRVM_ED_FindFieldOffset("clientcamera");
prog->fieldoffsets.clientcolors = PRVM_ED_FindFieldOffset("clientcolors");
+ prog->fieldoffsets.clientstatus = PRVM_ED_FindFieldOffset("clientstatus");
prog->fieldoffsets.color = PRVM_ED_FindFieldOffset("color");
prog->fieldoffsets.colormod = PRVM_ED_FindFieldOffset("colormod");
prog->fieldoffsets.contentstransition = PRVM_ED_FindFieldOffset("contentstransition");
prog->fieldoffsets.frame1time = PRVM_ED_FindFieldOffset("frame1time");
prog->fieldoffsets.frame2 = PRVM_ED_FindFieldOffset("frame2");
prog->fieldoffsets.frame2time = PRVM_ED_FindFieldOffset("frame2time");
+ prog->fieldoffsets.frame3 = PRVM_ED_FindFieldOffset("frame3");
+ prog->fieldoffsets.frame3time = PRVM_ED_FindFieldOffset("frame3time");
+ prog->fieldoffsets.frame4 = PRVM_ED_FindFieldOffset("frame4");
+ prog->fieldoffsets.frame4time = PRVM_ED_FindFieldOffset("frame4time");
prog->fieldoffsets.fullbright = PRVM_ED_FindFieldOffset("fullbright");
prog->fieldoffsets.glow_color = PRVM_ED_FindFieldOffset("glow_color");
prog->fieldoffsets.glow_size = PRVM_ED_FindFieldOffset("glow_size");
prog->fieldoffsets.idealpitch = PRVM_ED_FindFieldOffset("idealpitch");
prog->fieldoffsets.items2 = PRVM_ED_FindFieldOffset("items2");
prog->fieldoffsets.lerpfrac = PRVM_ED_FindFieldOffset("lerpfrac");
+ prog->fieldoffsets.lerpfrac3 = PRVM_ED_FindFieldOffset("lerpfrac3");
+ prog->fieldoffsets.lerpfrac4 = PRVM_ED_FindFieldOffset("lerpfrac4");
prog->fieldoffsets.light_lev = PRVM_ED_FindFieldOffset("light_lev");
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.renderflags = PRVM_ED_FindFieldOffset("renderflags");
prog->fieldoffsets.rendermode = PRVM_ED_FindFieldOffset("rendermode"); // HalfLife support
prog->fieldoffsets.scale = PRVM_ED_FindFieldOffset("scale");
+ prog->fieldoffsets.shadertime = PRVM_ED_FindFieldOffset("shadertime");
prog->fieldoffsets.style = PRVM_ED_FindFieldOffset("style");
prog->fieldoffsets.tag_entity = PRVM_ED_FindFieldOffset("tag_entity");
prog->fieldoffsets.tag_index = PRVM_ED_FindFieldOffset("tag_index");
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->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_Ent_Update = PRVM_ED_FindFunctionOffset("CSQC_Ent_Update");
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_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.GameCommand = PRVM_ED_FindFunctionOffset("GameCommand");
+ prog->funcoffsets.Gecko_Query = PRVM_ED_FindFunctionOffset("Gecko_Query");
prog->funcoffsets.RestoreGame = PRVM_ED_FindFunctionOffset("RestoreGame");
prog->funcoffsets.SV_ChangeTeam = PRVM_ED_FindFunctionOffset("SV_ChangeTeam");
+ prog->funcoffsets.SV_OnEntityNoSpawnFunction = PRVM_ED_FindFunctionOffset("SV_OnEntityNoSpawnFunction");
+ prog->funcoffsets.SV_OnEntityPostSpawnFunction = PRVM_ED_FindFunctionOffset("SV_OnEntityPostSpawnFunction");
+ prog->funcoffsets.SV_OnEntityPreSpawnFunction = PRVM_ED_FindFunctionOffset("SV_OnEntityPreSpawnFunction");
prog->funcoffsets.SV_ParseClientCommand = PRVM_ED_FindFunctionOffset("SV_ParseClientCommand");
+ prog->funcoffsets.SV_PausedTic = PRVM_ED_FindFunctionOffset("SV_PausedTic");
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.coop = PRVM_ED_FindGlobalOffset("coop");
+ prog->globaloffsets.deathmatch = PRVM_ED_FindGlobalOffset("deathmatch");
+ prog->globaloffsets.dmg_origin = PRVM_ED_FindGlobalOffset("dmg_origin");
+ prog->globaloffsets.dmg_save = PRVM_ED_FindGlobalOffset("dmg_save");
+ prog->globaloffsets.dmg_take = PRVM_ED_FindGlobalOffset("dmg_take");
+ prog->globaloffsets.drawfont = PRVM_ED_FindGlobalOffset("drawfont");
+ prog->globaloffsets.gettaginfo_forward = PRVM_ED_FindGlobalOffset("gettaginfo_forward");
+ prog->globaloffsets.gettaginfo_name = PRVM_ED_FindGlobalOffset("gettaginfo_name");
+ prog->globaloffsets.gettaginfo_offset = PRVM_ED_FindGlobalOffset("gettaginfo_offset");
+ prog->globaloffsets.gettaginfo_parent = PRVM_ED_FindGlobalOffset("gettaginfo_parent");
+ prog->globaloffsets.gettaginfo_right = PRVM_ED_FindGlobalOffset("gettaginfo_right");
+ prog->globaloffsets.gettaginfo_up = PRVM_ED_FindGlobalOffset("gettaginfo_up");
+ prog->globaloffsets.intermission = PRVM_ED_FindGlobalOffset("intermission");
+ prog->globaloffsets.require_spawnfunc_prefix = PRVM_ED_FindGlobalOffset("require_spawnfunc_prefix");
+ prog->globaloffsets.sb_showscores = PRVM_ED_FindGlobalOffset("sb_showscores");
prog->globaloffsets.self = PRVM_ED_FindGlobalOffset("self");
+ prog->globaloffsets.serverdeltatime = PRVM_ED_FindGlobalOffset("serverdeltatime");
+ prog->globaloffsets.serverprevtime = PRVM_ED_FindGlobalOffset("serverprevtime");
+ prog->globaloffsets.servertime = PRVM_ED_FindGlobalOffset("servertime");
prog->globaloffsets.time = PRVM_ED_FindGlobalOffset("time");
- prog->globaloffsets.v_forward = PRVM_ED_FindGlobalOffset("v_forward");
- prog->globaloffsets.v_right = PRVM_ED_FindGlobalOffset("v_right");
- prog->globaloffsets.v_up = PRVM_ED_FindGlobalOffset("v_up");
- prog->globaloffsets.view_angles = PRVM_ED_FindGlobalOffset("view_angles");
prog->globaloffsets.trace_allsolid = PRVM_ED_FindGlobalOffset("trace_allsolid");
- prog->globaloffsets.trace_startsolid = PRVM_ED_FindGlobalOffset("trace_startsolid");
- prog->globaloffsets.trace_fraction = PRVM_ED_FindGlobalOffset("trace_fraction");
- prog->globaloffsets.trace_inwater = PRVM_ED_FindGlobalOffset("trace_inwater");
- prog->globaloffsets.trace_inopen = PRVM_ED_FindGlobalOffset("trace_inopen");
- prog->globaloffsets.trace_endpos = PRVM_ED_FindGlobalOffset("trace_endpos");
- prog->globaloffsets.trace_plane_normal = PRVM_ED_FindGlobalOffset("trace_plane_normal");
- prog->globaloffsets.trace_plane_dist = PRVM_ED_FindGlobalOffset("trace_plane_dist");
- prog->globaloffsets.trace_ent = PRVM_ED_FindGlobalOffset("trace_ent");
prog->globaloffsets.trace_dphitcontents = PRVM_ED_FindGlobalOffset("trace_dphitcontents");
prog->globaloffsets.trace_dphitq3surfaceflags = PRVM_ED_FindGlobalOffset("trace_dphitq3surfaceflags");
prog->globaloffsets.trace_dphittexturename = PRVM_ED_FindGlobalOffset("trace_dphittexturename");
prog->globaloffsets.trace_dpstartcontents = PRVM_ED_FindGlobalOffset("trace_dpstartcontents");
- prog->globaloffsets.intermission = PRVM_ED_FindGlobalOffset("intermission");
- prog->globaloffsets.coop = PRVM_ED_FindGlobalOffset("coop");
- prog->globaloffsets.deathmatch = PRVM_ED_FindGlobalOffset("deathmatch");
- prog->globaloffsets.dmg_take = PRVM_ED_FindGlobalOffset("dmg_take");
- 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.trace_endpos = PRVM_ED_FindGlobalOffset("trace_endpos");
+ prog->globaloffsets.trace_ent = PRVM_ED_FindGlobalOffset("trace_ent");
+ prog->globaloffsets.trace_fraction = PRVM_ED_FindGlobalOffset("trace_fraction");
+ prog->globaloffsets.trace_inopen = PRVM_ED_FindGlobalOffset("trace_inopen");
+ prog->globaloffsets.trace_inwater = PRVM_ED_FindGlobalOffset("trace_inwater");
+ prog->globaloffsets.trace_networkentity = PRVM_ED_FindGlobalOffset("trace_networkentity");
+ prog->globaloffsets.trace_plane_dist = PRVM_ED_FindGlobalOffset("trace_plane_dist");
+ prog->globaloffsets.trace_plane_normal = PRVM_ED_FindGlobalOffset("trace_plane_normal");
+ prog->globaloffsets.trace_startsolid = PRVM_ED_FindGlobalOffset("trace_startsolid");
+ prog->globaloffsets.v_forward = PRVM_ED_FindGlobalOffset("v_forward");
+ prog->globaloffsets.v_right = PRVM_ED_FindGlobalOffset("v_right");
+ prog->globaloffsets.v_up = PRVM_ED_FindGlobalOffset("v_up");
+ prog->globaloffsets.view_angles = PRVM_ED_FindGlobalOffset("view_angles");
+ prog->globaloffsets.worldstatus = PRVM_ED_FindGlobalOffset("worldstatus");
// menu qc only uses some functions, nothing else
prog->funcoffsets.m_draw = PRVM_ED_FindFunctionOffset("m_draw");
===============
*/
+void PRVM_LeakTest();
void PRVM_ResetProg()
{
+ PRVM_LeakTest();
PRVM_GCALL(reset_cmd)();
Mem_FreePool(&prog->progs_mempool);
memset(prog,0,sizeof(prvm_prog_t));
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 ("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))
-#ifdef PRVM_BOUNDSCHECK_CVAR
- Cvar_RegisterVariable (&prvm_boundscheck);
-#endif
+
+ // 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();
}
prog->starttime = Sys_DoubleTime();
prog->error_cmd = Host_Error;
+ prog->leaktest_active = prvm_leaktest.integer;
}
int PRVM_GetProgNr()
{
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;
}
{
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);
+ vm_tempstringsbuf.data = (unsigned char *) Mem_Alloc(sv_mempool, vm_tempstringsbuf.maxsize);
if (old.cursize)
memcpy(vm_tempstringsbuf.data, old.data, old.cursize);
if (old.data)
{
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 = (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");
+}