Don't allow shortcuts with Ctrl+Alt because on Windows they can be used as the AltGr...
[xonotic/darkplaces.git] / prvm_edict.c
index 882a558..8633fdf 100644 (file)
@@ -35,8 +35,10 @@ 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_timeprofiling = {0, "prvm_timeprofiling", "0", "counts how long each function has been executed, these counts are displayed in prvm_profile output (if enabled)"};
+cvar_t prvm_coverage = {0, "prvm_coverage", "0", "report and count coverage events (1: per-function, 2: coverage() builtin, 4: per-statement)"};
 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_follow_targetname = {0, "prvm_leaktest_follow_targetname", "0", "if set, target/targetname links are considered when leak testing; this should normally not be required, as entities created during startup - e.g. info_notnull - are never considered leaky"};
 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"};
 cvar_t prvm_breakpointdump = {0, "prvm_breakpointdump", "0", "write a savegame on breakpoint to breakpoint-server.dmp"};
@@ -188,13 +190,20 @@ prvm_prog_t *PRVM_FriendlyProgFromString(const char *str)
 =================
 PRVM_ED_ClearEdict
 
-Sets everything to NULL
+Sets everything to NULL.
+
+Nota bene: this also marks the entity as allocated if it has been previously
+freed and sets the allocation origin.
 =================
 */
 void PRVM_ED_ClearEdict(prvm_prog_t *prog, prvm_edict_t *e)
 {
        memset(e->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
        e->priv.required->free = false;
+       e->priv.required->freetime = realtime;
+       if(e->priv.required->allocation_origin)
+               Mem_Free((char *)e->priv.required->allocation_origin);
+       e->priv.required->allocation_origin = PRVM_AllocationOrigin(prog);
 
        // AK: Let the init_edict function determine if something needs to be initialized
        prog->init_edict(prog, e);
@@ -206,8 +215,8 @@ const char *PRVM_AllocationOrigin(prvm_prog_t *prog)
        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(prog, buf, 128);
+               buf = (char *)PRVM_Alloc(256);
+               PRVM_ShortStackTrace(prog, buf, 256);
        }
        return buf;
 }
@@ -261,7 +270,6 @@ prvm_edict_t *PRVM_ED_Alloc(prvm_prog_t *prog)
                if(PRVM_ED_CanAlloc(prog, e))
                {
                        PRVM_ED_ClearEdict (prog, e);
-                       e->priv.required->allocation_origin = PRVM_AllocationOrigin(prog);
                        return e;
                }
        }
@@ -274,10 +282,8 @@ prvm_edict_t *PRVM_ED_Alloc(prvm_prog_t *prog)
                PRVM_MEM_IncreaseEdicts(prog);
 
        e = PRVM_EDICT_NUM(i);
-       PRVM_ED_ClearEdict(prog, e);
-
-       e->priv.required->allocation_origin = PRVM_AllocationOrigin(prog);
 
+       PRVM_ED_ClearEdict(prog, e);
        return e;
 }
 
@@ -515,7 +521,8 @@ char *PRVM_UglyValueString (prvm_prog_t *prog, etype_t type, prvm_eval_t *val, c
                line[i] = '\0';
                break;
        case ev_entity:
-               dpsnprintf (line, linelength, "%i", val->edict);
+               i = val->edict;
+               dpsnprintf (line, linelength, "%i", i);
                break;
        case ev_function:
                f = prog->functions + val->function;
@@ -1316,8 +1323,10 @@ const char *PRVM_ED_ParseEdict (prvm_prog_t *prog, const char *data, prvm_edict_
                        prog->error_cmd("PRVM_ED_ParseEdict: parse error");
        }
 
-       if (!init)
+       if (!init) {
                ent->priv.required->free = true;
+               ent->priv.required->freetime = realtime;
+       }
 
        return data;
 }
@@ -1930,6 +1939,7 @@ void PRVM_Prog_Load(prvm_prog_t *prog, const char * filename, unsigned char * da
        unsigned int d;
        char vabuf[1024];
        char vabuf2[1024];
+       cvar_t *cvar;
 
        if (prog->loaded)
                prog->error_cmd("PRVM_LoadProgs: there is already a %s program loaded!", prog->name );
@@ -2010,6 +2020,7 @@ void PRVM_Prog_Load(prvm_prog_t *prog, const char * filename, unsigned char * da
        prog->statements = (mstatement_t *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(mstatement_t));
        // allocate space for profiling statement usage
        prog->statement_profile = (double *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(*prog->statement_profile));
+       prog->explicit_profile = (double *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(*prog->statement_profile));
        // functions need to be converted to the memory format
        prog->functions = (mfunction_t *)Mem_Alloc(prog->progs_mempool, sizeof(mfunction_t) * prog->progs_numfunctions);
 
@@ -2215,6 +2226,11 @@ void PRVM_Prog_Load(prvm_prog_t *prog, const char * filename, unsigned char * da
                        break;
                // 1 global
                case OP_CALL0:
+                       if ( a < prog->progs_numglobals)
+                               if ( prog->globals.ip[remapglobal(a)] >= 0 )
+                                       if ( prog->globals.ip[remapglobal(a)] < prog->progs_numfunctions )
+                                               if ( prog->functions[prog->globals.ip[remapglobal(a)]].first_statement == -642 )
+                                                       ++prog->numexplicitcoveragestatements;
                case OP_CALL1:
                case OP_CALL2:
                case OP_CALL3:
@@ -2339,6 +2355,9 @@ void PRVM_Prog_Load(prvm_prog_t *prog, const char * filename, unsigned char * da
                }
        }
 
+       for (cvar = cvar_vars; cvar; cvar = cvar->next)
+               cvar->globaldefindex[prog - prvm_prog_list] = -1;
+
        for (i=0 ; i<prog->numglobaldefs ; i++)
        {
                const char *name;
@@ -2350,7 +2369,7 @@ void PRVM_Prog_Load(prvm_prog_t *prog, const char * filename, unsigned char * da
                )
                {
                        prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
-                       cvar_t *cvar = Cvar_FindVar(name + 9);
+                       cvar = Cvar_FindVar(name + 9);
                        //Con_Printf("PRVM_LoadProgs: autocvar global %s in %s, processing...\n", name, prog->name);
                        if(!cvar)
                        {
@@ -2384,7 +2403,6 @@ void PRVM_Prog_Load(prvm_prog_t *prog, const char * filename, unsigned char * da
                                }
                                if(!cvar)
                                        prog->error_cmd("PRVM_LoadProgs: could not create cvar for autocvar global %s in %s", name, prog->name);
-                               cvar->globaldefindex_progid[prog - prvm_prog_list] = prog->id;
                                cvar->globaldefindex[prog - prvm_prog_list] = i;
                        }
                        else if((cvar->flags & CVAR_PRIVATE) == 0)
@@ -2421,7 +2439,6 @@ void PRVM_Prog_Load(prvm_prog_t *prog, const char * filename, unsigned char * da
                                                Con_Printf("PRVM_LoadProgs: invalid type of autocvar global %s in %s\n", name, prog->name);
                                                goto fail;
                                }
-                               cvar->globaldefindex_progid[prog - prvm_prog_list] = prog->id;
                                cvar->globaldefindex[prog - prvm_prog_list] = i;
                        }
                        else
@@ -2445,6 +2462,10 @@ fail:
 
        // init mempools
        PRVM_MEM_Alloc(prog);
+
+       // Inittime is at least the time when this function finished. However,
+       // later events may bump it.
+       prog->inittime = realtime;
 }
 
 
@@ -2901,8 +2922,10 @@ void PRVM_Init (void)
        Cvar_RegisterVariable (&prvm_traceqc);
        Cvar_RegisterVariable (&prvm_statementprofiling);
        Cvar_RegisterVariable (&prvm_timeprofiling);
+       Cvar_RegisterVariable (&prvm_coverage);
        Cvar_RegisterVariable (&prvm_backtraceforwarnings);
        Cvar_RegisterVariable (&prvm_leaktest);
+       Cvar_RegisterVariable (&prvm_leaktest_follow_targetname);
        Cvar_RegisterVariable (&prvm_leaktest_ignore_classnames);
        Cvar_RegisterVariable (&prvm_errordump);
        Cvar_RegisterVariable (&prvm_breakpointdump);
@@ -3079,10 +3102,12 @@ int PRVM_SetTempString(prvm_prog_t *prog, const char *s)
                {
                        Con_DPrintf("PRVM_SetTempString: enlarging tempstrings buffer (%iKB -> %iKB)\n", old.maxsize/1024, prog->tempstringsbuf.maxsize/1024);
                        prog->tempstringsbuf.data = (unsigned char *) Mem_Alloc(prog->progs_mempool, prog->tempstringsbuf.maxsize);
-                       if (old.cursize)
-                               memcpy(prog->tempstringsbuf.data, old.data, old.cursize);
                        if (old.data)
+                       {
+                               if (old.cursize)
+                                       memcpy(prog->tempstringsbuf.data, old.data, old.cursize);
                                Mem_Free(old.data);
+                       }
                }
        }
        t = (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.cursize;
@@ -3095,7 +3120,11 @@ int PRVM_AllocString(prvm_prog_t *prog, size_t bufferlength, char **pointer)
 {
        int i;
        if (!bufferlength)
+       {
+               if (pointer)
+                       *pointer = NULL;
                return 0;
+       }
        for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++)
                if (!prog->knownstrings[i])
                        break;
@@ -3199,6 +3228,8 @@ static qboolean PRVM_IsEdictRelevant(prvm_prog_t *prog, prvm_edict_t *edict)
        char vabuf2[1024];
        if(PRVM_NUM_FOR_EDICT(edict) <= prog->reserved_edicts)
                return true; // world or clients
+       if (edict->priv.required->freetime <= prog->inittime)
+               return true; // created during startup
        if (prog == SVVM_prog)
        {
                if(PRVM_serveredictfloat(edict, solid)) // can block other stuff, or is a trigger?
@@ -3249,25 +3280,13 @@ static qboolean PRVM_IsEdictReferenced(prvm_prog_t *prog, prvm_edict_t *edict, i
        int edictnum = PRVM_NUM_FOR_EDICT(edict);
        const char *targetname = NULL;
 
-       if (prog == SVVM_prog)
+       if (prog == SVVM_prog && prvm_leaktest_follow_targetname.integer)
                targetname = PRVM_GetString(prog, PRVM_serveredictstring(edict, targetname));
 
        if(targetname)
                if(!*targetname) // ""
                        targetname = NULL;
 
-       if(mark == 0)
-       {
-               for (i = 0;i < prog->numglobaldefs;i++)
-               {
-                       ddef_t *d = &prog->globaldefs[i];
-                       if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity)
-                               continue;
-                       if(edictnum == PRVM_GLOBALFIELDEDICT(d->ofs))
-                               return true;
-               }
-       }
-
        for(j = 0; j < prog->num_edicts; ++j)
        {
                prvm_edict_t *ed = PRVM_EDICT_NUM(j);
@@ -3297,19 +3316,35 @@ static qboolean PRVM_IsEdictReferenced(prvm_prog_t *prog, prvm_edict_t *edict, i
 
 static void PRVM_MarkReferencedEdicts(prvm_prog_t *prog)
 {
-       int j;
+       int i, j;
        qboolean found_new;
        int stage;
 
+       // Stage 1: world, all entities that are relevant, and all entities that are referenced by globals.
+       stage = 1;
        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(prog, ed) ? 1 : 0;
+               ed->priv.required->mark = PRVM_IsEdictRelevant(prog, ed) ? stage : 0;
+       }
+       for (i = 0;i < prog->numglobaldefs;i++)
+       {
+               ddef_t *d = &prog->globaldefs[i];
+               prvm_edict_t *ed;
+               if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity)
+                       continue;
+               j = PRVM_GLOBALFIELDEDICT(d->ofs);
+               if (i < 0 || j >= prog->max_edicts) {
+                       Con_Printf("Invalid entity reference from global %s.\n", PRVM_GetString(prog, d->s_name));
+                       continue;
+               }
+               ed = PRVM_EDICT_NUM(j);;
+               ed->priv.required->mark = stage;
        }
 
-       stage = 1;
+       // Future stages: all entities that are referenced by an entity of the previous stage.
        do
        {
                found_new = false;