/* This file is part of Warzone 2100. Copyright (C) 1999-2004 Eidos Interactive Copyright (C) 2005-2007 Warzone Resurrection Project Warzone 2100 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Warzone 2100 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Warzone 2100; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * Interp.c * * Execute the compiled version of a script * */ /* Control the execution trace printf's */ #define DEBUG_GROUP0 #include "lib/framework/frame.h" #include "interp.h" #include "stack.h" #include "codeprint.h" #include "script.h" #include "event.h" //needed for eventGetEventID() // the maximum number of instructions to execute before assuming // an infinite loop #define INTERP_MAXINSTRUCTIONS 250000 #define MAX_FUNC_CALLS 300 static INTERP_VAL *varEnvironment[MAX_FUNC_CALLS]; //environments for local variables of events/functions typedef struct { UDWORD CallerIndex; INTERP_VAL *ReturnAddress; } ReturnAddressStack_t; /** * Reset the return address stack */ static inline void retStackReset(void); /** * Check whether the return address stack is empty * * \return True when empty, false otherwise */ static inline BOOL retStackIsEmpty(void); /** * Check whether the return address stack is full * * \return True when full, false otherwise */ static inline BOOL retStackIsFull(void); /** * Push a new address/event pair on the return address stack * * \param CallerIndex Index of the calling function * \param ReturnAddress Address to return to * \return False on failure (stack full) */ static BOOL retStackPush(UDWORD CallerIndex, INTERP_VAL *ReturnAddress); /** * Pop an address/event pair from the return address stack * * \param CallerIndex Index of the calling function * \param ReturnAddress Address to return to * \return False on failure (stack empty) */ static BOOL retStackPop(UDWORD *CallerIndex, INTERP_VAL **ReturnAddress); /* Creates a new local var environment for a new function call */ static inline void createVarEnvironment(SCRIPT_CONTEXT *psContext, UDWORD eventIndex); /* Destroy all created variable environments */ static void cleanupVarEnvironments(void); static inline void destroyVarEnvironment(SCRIPT_CONTEXT *psContext, UDWORD envIndex, UDWORD eventIndex); /* The size of each opcode */ SDWORD aOpSize[] = { 2, // OP_PUSH | type, value 2, // OP_PUSHREF | type, value 1, // OP_POP 1, // OP_PUSHGLOBAL | var_num 1, // OP_POPGLOBAL | var_num 1, // OP_PUSHARRAYGLOBAL | array_dimensions | array_base 1, // OP_POPARRAYGLOBAL | array_dimensions | array_base 2, // OP_CALL | func_pointer 2, // OP_VARCALL | func_pointer 1, // OP_JUMP | offset 1, // OP_JUMPTRUE | offset 1, // OP_JUMPFALSE | offset 1, // OP_BINARYOP | secondary op 1, // OP_UNARYOP | secondary op 1, // OP_EXIT 1, // OP_PAUSE -1, // OP_ADD -1, // OP_SUB -1, // OP_MUL -1, // OP_DIV -1, // OP_NEG -1, // OP_INC -1, // OP_DEC -1, // OP_AND -1, // OP_OR -1, // OP_NOT -1, // OP_CONC -1, // OP_EQUAL -1, // OP_NOTEQUAL -1, // OP_GREATEREQUAL -1, // OP_LESSEQUAL -1, // OP_GREATER -1, // OP_LESS 2, // OP_FUNC | func_pointer 1, // OP_POPLOCAL 1, // OP_PUSHLOCAL 2, // OP_PUSHLOCALREF 1, //OP_TO_FLOAT 1, //OP_TO_INT }; /* The type equivalence table */ static TYPE_EQUIV *asInterpTypeEquiv = NULL; // whether the interpreter is running static BOOL bInterpRunning = false; /* Whether to output trace information */ static BOOL interpTrace = false; static SCRIPT_CODE *psCurProg = NULL; static BOOL bCurCallerIsEvent = false; /* Print out trace info if tracing is turned on */ #define TRCPRINTF(...) do { if (interpTrace) { fprintf( stderr, __VA_ARGS__ ); } } while (false) #define TRCPRINTVAL(x) \ if (interpTrace) \ cpPrintVal(x) #define TRCPRINTOPCODE(x) \ if (interpTrace) \ debug( LOG_NEVER, "%s", interpOpcodeToString(x) ) #define TRCPRINTSTACKTOP() \ if (interpTrace) \ stackPrintTop() #define TRCPRINTFUNC(x) \ if (interpTrace) \ debug( LOG_NEVER, "%s", interpFunctionToString(x) ) #define TRCPRINTVARFUNC(x, data) \ if (interpTrace) \ cpPrintVarFunc(x, data) // true if the interpreter is currently running BOOL interpProcessorActive(void) { return bInterpRunning; } /* Find the value store for a global variable */ static inline INTERP_VAL *interpGetVarData(VAL_CHUNK *psGlobals, UDWORD index) { VAL_CHUNK *psChunk; psChunk = psGlobals; while (index >= CONTEXT_VALS) { index -= CONTEXT_VALS; psChunk = psChunk->psNext; } return psChunk->asVals + index; } // get the array data for an array operation static BOOL interpGetArrayVarData(INTERP_VAL **pip, VAL_CHUNK *psGlobals, SCRIPT_CODE *psProg, INTERP_VAL **ppsVal) { SDWORD i, dimensions, vals[VAR_MAX_DIMENSIONS]; UBYTE *elements; //[VAR_MAX_DIMENSIONS] SDWORD size, val;//, elementDWords; // UBYTE *pElem; INTERP_VAL *InstrPointer = *pip; UDWORD base, index; // get the base index of the array base = InstrPointer->v.ival & ARRAY_BASE_MASK; // get the number of dimensions dimensions = (InstrPointer->v.ival & ARRAY_DIMENSION_MASK) >> ARRAY_DIMENSION_SHIFT; if (base >= psProg->numArrays) { debug( LOG_ERROR, "interpGetArrayVarData: array base index out of range" ); return false; } if (dimensions != psProg->psArrayInfo[base].dimensions) { debug( LOG_ERROR, "interpGetArrayVarData: dimensions do not match" ); return false; } // get the number of elements for each dimension elements = psProg->psArrayInfo[base].elements; // calculate the index of the array element size = 1; index = 0; for(i=dimensions-1; i>=0; i-=1) { if (!stackPopParams(1, VAL_INT, &val)) { return false; } if ( (val < 0) || (val >= elements[i]) ) { debug( LOG_ERROR, "interpGetArrayVarData: Array index for dimension %d out of range (passed index = %d, max index = %d)", i , val, elements[i]); return false; } index += val * size; size *= psProg->psArrayInfo[base].elements[i]; vals[i] = val; } // print out the debug trace if (interpTrace) { debug( LOG_NEVER, "%d->", base ); for(i=0; i psProg->arraySize) { debug( LOG_ERROR, "interpGetArrayVarData: Array indexes out of variable space" ); return false; } // get the variable data *ppsVal = interpGetVarData(psGlobals, psProg->psArrayInfo[base].base + index); // update the instruction pointer *pip += 1;// + elementDWords; return true; } // Initialise the interpreter BOOL interpInitialise(void) { asInterpTypeEquiv = NULL; return true; } /* Run a compiled script */ BOOL interpRunScript(SCRIPT_CONTEXT *psContext, INTERP_RUNTYPE runType, UDWORD index, UDWORD offset) { UDWORD data; OPCODE opcode; INTERP_VAL sVal, *psVar,*InstrPointer; VAL_CHUNK *psGlobals; UDWORD numGlobals = 0; INTERP_VAL *pCodeStart, *pCodeEnd, *pCodeBase; SCRIPT_FUNC scriptFunc = 0; SCRIPT_VARFUNC scriptVarFunc = 0; SCRIPT_CODE *psProg; SDWORD instructionCount = 0; UDWORD CurEvent = 0; BOOL bStop = false, bEvent = false; UDWORD callDepth = 0; BOOL bTraceOn=false; //enable to debug function/event calls ASSERT( psContext != NULL, "interpRunScript: invalid context pointer" ); psProg = psContext->psCode; psCurProg = psProg; //remember for future use ASSERT( psProg != NULL, "interpRunScript: invalid script code pointer" ); if (bInterpRunning) { debug(LOG_ERROR,"interpRunScript: interpreter already running" " - callback being called from within a script function?"); goto exit_with_error; } // note that the interpreter is running to stop recursive script calls bInterpRunning = true; // Reset the stack in case another script messed up stackReset(); //reset return stack retStackReset(); // Turn off tracing initially interpTrace = false; /* Get the global variables */ numGlobals = psProg->numGlobals; psGlobals = psContext->psGlobals; bEvent = false; // Find the code range switch (runType) { case IRT_TRIGGER: if (index > psProg->numTriggers) { debug(LOG_ERROR,"interpRunScript: trigger index out of range"); ASSERT( false, "interpRunScript: trigger index out of range" ); return false; } pCodeBase = psProg->pCode + psProg->pTriggerTab[index]; pCodeStart = pCodeBase; pCodeEnd = psProg->pCode + psProg->pTriggerTab[index+1]; bCurCallerIsEvent = false; // find the debug info for the trigger strcpy(last_called_script_event, eventGetTriggerID(psProg, index)); if(bTraceOn) debug(LOG_SCRIPT,"Trigger: %s", last_called_script_event); break; case IRT_EVENT: if (index > psProg->numEvents) { debug(LOG_ERROR,"interpRunScript: trigger index out of range"); ASSERT( false, "interpRunScript: trigger index out of range" ); return false; } pCodeBase = psProg->pCode + psProg->pEventTab[index]; pCodeStart = pCodeBase + offset; //offset only used for pause() script function pCodeEnd = psProg->pCode + psProg->pEventTab[index+1]; bEvent = true; //remember it's an event bCurCallerIsEvent = true; // remember last called event/function strcpy(last_called_script_event, eventGetEventID(psProg, index)); if(bTraceOn) debug(LOG_SCRIPT,"Original event name: %s", last_called_script_event); break; default: debug(LOG_ERROR,"interpRunScript: unknown run type"); ASSERT( false, "interpRunScript: unknown run type" ); return false; } // Get the first opcode InstrPointer = pCodeStart; /* Make sure we start with an opcode */ ASSERT(InstrPointer->type == VAL_PKOPCODE || InstrPointer->type == VAL_OPCODE, "Expected an opcode at the beginning of the interpreting process (type=%d)", InstrPointer->type); //opcode = InstrPointer->v.ival >> OPCODE_SHIFT; instructionCount = 0; CurEvent = index; bStop = false; // create new variable environment for this call if (bEvent) { createVarEnvironment(psContext, CurEvent); } while(!bStop) { // Run the code if (InstrPointer < pCodeEnd)// && opcode != OP_EXIT) { if (instructionCount > INTERP_MAXINSTRUCTIONS) { debug( LOG_ERROR, "interpRunScript: max instruction count exceeded - infinite loop ?" ); goto exit_with_error; } instructionCount++; TRCPRINTF( "%-6d ", (int)(InstrPointer - psProg->pCode) ); opcode = (OPCODE)(InstrPointer->v.ival >> OPCODE_SHIFT); //get opcode data = (SDWORD)(InstrPointer->v.ival & OPCODE_DATAMASK); //get data - only used with packed opcodes switch (opcode) { /* Custom function call */ case OP_FUNC: //debug( LOG_SCRIPT, "-OP_FUNC" ); //debug( LOG_SCRIPT, "OP_FUNC: remember event %d, ip=%d", CurEvent, (ip + 2) ); if(!retStackPush(CurEvent, (InstrPointer + aOpSize[opcode]))) //Remember where to jump back later { debug( LOG_ERROR, "interpRunScript() - retStackPush() failed."); return false; } ASSERT(((INTERP_VAL *)(InstrPointer+1))->type == VAL_EVENT, "wrong value type passed for OP_FUNC: %d", ((INTERP_VAL *)(InstrPointer+1))->type); // get index of the new event CurEvent = ((INTERP_VAL *)(InstrPointer+1))->v.ival; //Current event = event to jump to if (CurEvent > psProg->numEvents) { debug( LOG_ERROR, "interpRunScript: trigger index out of range"); goto exit_with_error; } // create new variable environment for this call createVarEnvironment(psContext, CurEvent); //Set new code execution boundaries //---------------------------------- pCodeBase = psProg->pCode + psProg->pEventTab[CurEvent]; pCodeStart = pCodeBase; pCodeEnd = psProg->pCode + psProg->pEventTab[CurEvent+1]; InstrPointer = pCodeStart; //Start at the beginning of the new event //remember last called event/index strcpy(last_called_script_event, eventGetEventID(psProg, CurEvent)); if(bTraceOn) debug(LOG_SCRIPT,"Called: '%s'", last_called_script_event); //debug( LOG_SCRIPT, "-OP_FUNC: jumped to event %d; ip=%d, numLocalVars: %d", CurEvent, ip, psContext->psCode->numLocalVars[CurEvent] ); //debug( LOG_SCRIPT, "-END OP_FUNC" ); break; //handle local variables case OP_PUSHLOCAL: //debug( LOG_SCRIPT, "OP_PUSHLOCAL"); //debug( LOG_SCRIPT, "OP_PUSHLOCAL, (CurEvent=%d, data =%d) num loc vars: %d; pushing: %d", CurEvent, data, psContext->psCode->numLocalVars[CurEvent], psContext->psCode->ppsLocalVarVal[CurEvent][data].v.ival); if (data >= psContext->psCode->numLocalVars[CurEvent]) { debug(LOG_ERROR, "interpRunScript: OP_PUSHLOCAL: variable index out of range"); goto exit_with_error; } //debug(LOG_SCRIPT, "OP_PUSHLOCAL type: %d", psContext->psCode->ppsLocalVarVal[CurEvent][data].type); if (!stackPush( &(varEnvironment[retStackCallDepth()][data]) )) { debug(LOG_ERROR, "interpRunScript: OP_PUSHLOCAL: push failed"); goto exit_with_error; } InstrPointer += aOpSize[opcode]; break; case OP_POPLOCAL: //debug( LOG_SCRIPT, "OP_POPLOCAL, event index: '%d', data: '%d'", CurEvent, data); //debug( LOG_SCRIPT, "OP_POPLOCAL, numLocalVars: '%d'", psContext->psCode->numLocalVars[CurEvent]); if (data >= psContext->psCode->numLocalVars[CurEvent]) { debug(LOG_ERROR, "interpRunScript: OP_POPLOCAL: variable index out of range"); goto exit_with_error; } //DbgMsg("OP_POPLOCAL type: %d, CurEvent=%d, data=%d", psContext->psCode->ppsLocalVarVal[CurEvent][data].type, CurEvent, data); if ( !stackPopType( &(varEnvironment[retStackCallDepth()][data]) ) ) { debug(LOG_ERROR, "interpRunScript: OP_POPLOCAL: pop failed"); goto exit_with_error; } //debug(LOG_SCRIPT, "OP_POPLOCAL: type=%d, val=%d", psContext->psCode->ppsLocalVarVal[CurEvent][data].type, psContext->psCode->ppsLocalVarVal[CurEvent][data].v.ival); InstrPointer += aOpSize[opcode]; break; case OP_PUSHLOCALREF: // The type of the variable is stored in with the opcode sVal.type = (INTERP_TYPE)(InstrPointer->v.ival & OPCODE_DATAMASK); ASSERT( ((INTERP_VAL *)(InstrPointer + 1))->type == VAL_INT, "wrong value type passed for OP_PUSHLOCALREF: %d", ((INTERP_VAL *)(InstrPointer + 1))->type); /* get local var index */ data = ((INTERP_VAL *)(InstrPointer + 1))->v.ival; if (data >= psContext->psCode->numLocalVars[CurEvent]) { debug(LOG_ERROR, "interpRunScript: OP_PUSHLOCALREF: variable index out of range"); goto exit_with_error; } /* get local variable */ sVal.v.oval = &(varEnvironment[retStackCallDepth()][data]); TRCPRINTOPCODE(opcode); TRCPRINTVAL(sVal); TRCPRINTF( "\n" ); if (!stackPush(&sVal)) { debug(LOG_ERROR, "interpRunScript: OP_PUSHLOCALREF: push failed"); goto exit_with_error; } InstrPointer += aOpSize[opcode]; break; case OP_PUSH: // The type of the value is stored in with the opcode sVal.type = (INTERP_TYPE)(InstrPointer->v.ival & OPCODE_DATAMASK); //ASSERT( ((INTERP_VAL *)(InstrPointer + 1))->type == sVal.type, // "wrong value type passed for OP_PUSH: %d, expected: %d", ((INTERP_VAL *)(InstrPointer + 1))->type, sVal.type ); ASSERT(interpCheckEquiv(((INTERP_VAL *)(InstrPointer + 1))->type, sVal.type), "wrong value type passed for OP_PUSH: %d, expected: %d", ((INTERP_VAL *)(InstrPointer + 1))->type, sVal.type); /* copy value */ memcpy(&sVal, (INTERP_VAL *)(InstrPointer + 1), sizeof(INTERP_VAL)); TRCPRINTOPCODE(opcode); TRCPRINTVAL(sVal); TRCPRINTF( "\n" ); if (!stackPush(&sVal)) { // Eeerk, out of memory debug( LOG_ERROR, "interpRunScript: out of memory!" ); goto exit_with_error; } InstrPointer += aOpSize[opcode]; break; case OP_PUSHREF: // The type of the variable is stored in with the opcode sVal.type = (INTERP_TYPE)(InstrPointer->v.ival & OPCODE_DATAMASK); // store pointer to INTERP_VAL sVal.v.oval = interpGetVarData(psGlobals, ((INTERP_VAL *)(InstrPointer + 1))->v.ival); TRCPRINTOPCODE(opcode); TRCPRINTVAL(sVal); TRCPRINTF( "\n" ); if (!stackPush(&sVal)) { // Eeerk, out of memory debug( LOG_ERROR, "interpRunScript: out of memory!" ); goto exit_with_error; } InstrPointer += aOpSize[opcode]; break; case OP_POP: ASSERT( InstrPointer->type == VAL_OPCODE, "wrong value type passed for OP_POP: %d", InstrPointer->type); TRCPRINTOPCODE(opcode); if (!stackPop(&sVal)) { debug( LOG_ERROR, "interpRunScript: could not do stack pop" ); goto exit_with_error; } InstrPointer += aOpSize[opcode]; break; case OP_BINARYOP: ASSERT( InstrPointer->type == VAL_PKOPCODE, "wrong value type passed for OP_BINARYOP: %d", InstrPointer->type); TRCPRINTOPCODE(data); if (!stackBinaryOp((OPCODE)data)) { debug( LOG_ERROR, "interpRunScript: could not do binary op" ); goto exit_with_error; } TRCPRINTSTACKTOP(); TRCPRINTF( "\n" ); InstrPointer += aOpSize[opcode]; break; case OP_UNARYOP: ASSERT( InstrPointer->type == VAL_PKOPCODE, "wrong value type passed for OP_UNARYOP: %d", InstrPointer->type); TRCPRINTOPCODE(data); if (!stackUnaryOp((OPCODE)data)) { debug( LOG_ERROR, "interpRunScript: could not do unary op" ); goto exit_with_error; } TRCPRINTSTACKTOP(); TRCPRINTF( "\n" ); InstrPointer += aOpSize[opcode]; break; case OP_PUSHGLOBAL: ASSERT( InstrPointer->type == VAL_PKOPCODE, "wrong value type passed for OP_PUSHGLOBAL: %d", InstrPointer->type); TRCPRINTF( "PUSHGLOBAL %d\n", data ); if (data >= numGlobals) { debug( LOG_ERROR, "interpRunScript: variable index out of range" ); goto exit_with_error; } if (!stackPush(interpGetVarData(psGlobals, data))) { debug( LOG_ERROR, "interpRunScript: could not do stack push" ); goto exit_with_error; } InstrPointer += aOpSize[opcode]; break; case OP_POPGLOBAL: ASSERT( InstrPointer->type == VAL_PKOPCODE, "wrong value type passed for OP_POPGLOBAL: %d", InstrPointer->type); TRCPRINTF( "POPGLOBAL %d ", data ); TRCPRINTSTACKTOP(); TRCPRINTF( "\n" ); if (data >= numGlobals) { debug( LOG_ERROR, "interpRunScript: variable index out of range" ); goto exit_with_error; } if (!stackPopType(interpGetVarData(psGlobals, data))) { debug( LOG_ERROR, "interpRunScript: could not do stack pop" ); goto exit_with_error; } InstrPointer += aOpSize[opcode]; break; case OP_PUSHARRAYGLOBAL: ASSERT( InstrPointer->type == VAL_PKOPCODE, "wrong value type passed for OP_PUSHARRAYGLOBAL: %d", InstrPointer->type); TRCPRINTOPCODE(opcode); if (!interpGetArrayVarData(&InstrPointer, psGlobals, psProg, &psVar)) { debug( LOG_ERROR, "interpRunScript: could not get array var data, CurEvent=%d", CurEvent ); goto exit_with_error; } TRCPRINTF( "\n" ); if (!stackPush(psVar)) { debug( LOG_ERROR, "interpRunScript: could not do stack push" ); goto exit_with_error; } break; case OP_POPARRAYGLOBAL: ASSERT( InstrPointer->type == VAL_PKOPCODE, "wrong value type passed for OP_POPARRAYGLOBAL: %d", InstrPointer->type); TRCPRINTOPCODE(opcode); if (!interpGetArrayVarData(&InstrPointer, psGlobals, psProg, &psVar)) { debug( LOG_ERROR, "interpRunScript: could not get array var data" ); goto exit_with_error; } TRCPRINTSTACKTOP(); TRCPRINTF( "\n" ); if (!stackPopType(psVar)) { debug( LOG_ERROR, "interpRunScript: could not do pop stack of type" ); goto exit_with_error; } break; case OP_JUMPFALSE: ASSERT( InstrPointer->type == VAL_PKOPCODE, "wrong value type passed for OP_JUMPFALSE: %d", InstrPointer->type); TRCPRINTF( "JUMPFALSE %d (%d)", (SWORD)data, (int)(InstrPointer - psProg->pCode + (SWORD)data) ); if (!stackPop(&sVal)) { debug( LOG_ERROR, "interpRunScript: could not do pop of stack" ); goto exit_with_error; } if (!sVal.v.bval) { // Do the jump TRCPRINTF( " - done -\n" ); InstrPointer += (SWORD)data; if (InstrPointer < pCodeStart || InstrPointer > pCodeEnd) { debug( LOG_ERROR, "interpRunScript: jump out of range" ); goto exit_with_error; } } else { TRCPRINTF( "\n" ); InstrPointer += aOpSize[opcode]; } break; case OP_JUMP: ASSERT( InstrPointer->type == VAL_PKOPCODE, "wrong value type passed for OP_JUMP: %d", InstrPointer->type); TRCPRINTF( "JUMP %d (%d)\n", (SWORD)data, (int)(InstrPointer - psProg->pCode + (SWORD)data) ); // Do the jump InstrPointer += (SWORD)data; if (InstrPointer < pCodeStart || InstrPointer > pCodeEnd) { debug( LOG_ERROR, "interpRunScript: jump out of range" ); goto exit_with_error; } break; case OP_CALL: //debug(LOG_SCRIPT, "OP_CALL"); ASSERT( InstrPointer->type == VAL_OPCODE, "wrong value type passed for OP_CALL: %d", InstrPointer->type); scriptFunc = ((INTERP_VAL *)(InstrPointer+1))->v.pFuncExtern; TRCPRINTFUNC( scriptFunc ); TRCPRINTF( "\n" ); //debug(LOG_SCRIPT, "OP_CALL 1"); if (!scriptFunc()) { debug( LOG_ERROR, "interpRunScript: could not do func" ); goto exit_with_error; } //debug(LOG_SCRIPT, "OP_CALL 2"); InstrPointer += aOpSize[opcode]; //debug(LOG_SCRIPT, "OP_CALL 3"); break; case OP_VARCALL: ASSERT( InstrPointer->type == VAL_PKOPCODE, "wrong value type passed for OP_VARCALL: %d", InstrPointer->type); TRCPRINTOPCODE(opcode); TRCPRINTVARFUNC( ((INTERP_VAL *)(InstrPointer+1))->v.pObjGetSet, data ); TRCPRINTF( "(%d)\n", data ); ASSERT( ((INTERP_VAL *)(InstrPointer+1))->type == VAL_OBJ_GETSET, "wrong set/get function pointer type passed for OP_VARCALL: %d", ((INTERP_VAL *)(InstrPointer+1))->type); scriptVarFunc =((INTERP_VAL *)(InstrPointer+1))->v.pObjGetSet; if (!scriptVarFunc(data)) { debug( LOG_ERROR, "interpRunScript: could not do var func" ); goto exit_with_error; } InstrPointer += aOpSize[opcode]; break; case OP_EXIT: /* end of function/event, "exit" or "return" statements */ ASSERT( InstrPointer->type == VAL_OPCODE, "wrong value type passed for OP_EXIT: %d", InstrPointer->type); // jump out of the code InstrPointer = pCodeEnd; break; case OP_PAUSE: ASSERT( InstrPointer->type == VAL_PKOPCODE, "wrong value type passed for OP_PAUSE: %d", InstrPointer->type); TRCPRINTF( "PAUSE %d\n", data ); ASSERT( stackEmpty(), "interpRunScript: OP_PAUSE without empty stack" ); InstrPointer += aOpSize[opcode]; // tell the event system to reschedule this event if (!eventAddPauseTrigger(psContext, index, (UDWORD)(InstrPointer - pCodeBase), data)) //only original caller can be paused since we pass index and not CurEvent (not sure if that's what we want) { debug( LOG_ERROR, "interpRunScript: could not add pause trigger" ); goto exit_with_error; } // now jump out of the event InstrPointer = pCodeEnd; break; case OP_TO_FLOAT: ASSERT( InstrPointer->type == VAL_OPCODE, "wrong value type passed for OP_TO_FLOAT: %d", InstrPointer->type); if(!stackCastTop(VAL_FLOAT)) { debug( LOG_ERROR, "interpRunScript: OP_TO_FLOAT failed" ); goto exit_with_error; } InstrPointer += aOpSize[opcode]; break; case OP_TO_INT: ASSERT( InstrPointer->type == VAL_OPCODE, "wrong value type passed for OP_TO_INT: %d", InstrPointer->type); if(!stackCastTop(VAL_INT)) { debug( LOG_ERROR, "interpRunScript: OP_TO_INT failed" ); goto exit_with_error; } InstrPointer += aOpSize[opcode]; break; default: debug(LOG_ERROR, "interpRunScript: unknown opcode: %d, type: %d", opcode, InstrPointer->type); goto exit_with_error; break; } } else //End of the event reached, see if we have to jump back to the caller function or just exit { //debug(LOG_SCRIPT, "End of event reached"); if(!retStackIsEmpty()) //There was a caller function before this one { // destroy current variable environment destroyVarEnvironment(psContext, retStackCallDepth(), CurEvent); //pop caller function index and return address if (!retStackPop(&CurEvent, &InstrPointer)) { debug( LOG_ERROR, "interpRunScript() - retStackPop() failed."); return false; } //remember last called event/index strcpy(last_called_script_event, eventGetEventID(psProg, CurEvent)); if(bTraceOn) debug(LOG_SCRIPT,"Returned to: '%s'", last_called_script_event); //debug( LOG_SCRIPT, "RETURNED TO CALLER EVENT %d", CurEvent ); //Set new boundaries //-------------------------- if(retStackIsEmpty()) //if we jumped back to the original caller { if(!bEvent) //original caller was a trigger (is it possible at all?) { pCodeBase = psProg->pCode + psProg->pTriggerTab[CurEvent]; pCodeStart = pCodeBase; pCodeEnd = psProg->pCode + psProg->pTriggerTab[CurEvent+1]; } else //original caller was an event { pCodeBase = psProg->pCode + psProg->pEventTab[CurEvent]; pCodeStart = pCodeBase + offset; //also use the offset passed, since it's an original caller event (offset is used for pause() ) pCodeEnd = psProg->pCode + psProg->pEventTab[CurEvent+1]; } } else //we are still jumping thru functions (this can't be a callback, since it can't/should not be called) { pCodeBase = psProg->pCode + psProg->pEventTab[CurEvent]; pCodeStart = pCodeBase; pCodeEnd = psProg->pCode + psProg->pEventTab[CurEvent+1]; } } else //we have returned to the original caller event/function { //debug( LOG_SCRIPT, " *** CALL STACK EMPTY ***" ); //reset local vars only if original caller was an event, not a trigger if(bEvent) { // destroy current variable environment destroyVarEnvironment(psContext, retStackCallDepth(), CurEvent); } bStop = true; //Stop execution of this event here, no more calling functions stored } } } psCurProg = NULL; TRCPRINTF( "%-6d EXIT\n", (int)(InstrPointer - psProg->pCode) ); bInterpRunning = false; return true; exit_with_error: // Deal with the script crashing or running out of memory debug(LOG_ERROR,"interpRunScript: *** ERROR EXIT *** (CurEvent=%d)", CurEvent); /* Free all memory allocated for variable environments */ cleanupVarEnvironments(); if(bEvent) { debug(LOG_ERROR,"Original event ID: %d (of %d)", index, psProg->numEvents); } else { debug(LOG_ERROR,"Original trigger ID: %d (of %d)", index, psProg->numTriggers); } debug(LOG_ERROR,"Current event ID: %d (of %d)", CurEvent, psProg->numEvents); callDepth = retStackCallDepth(); debug(LOG_ERROR,"Call depth : %d", callDepth); /* Output script call trace */ scrOutputCallTrace(); psCurProg = NULL; TRCPRINTF( "*** ERROR EXIT ***\n" ); ASSERT(!"error while executing a script", "interpRunScript: error while executing a script"); bInterpRunning = false; return false; } /* Set the type equivalence table */ void scriptSetTypeEquiv(TYPE_EQUIV *psTypeTab) { #ifdef DEBUG SDWORD i; for(i=0; psTypeTab[i].base != 0; i++) { ASSERT( psTypeTab[i].base >= VAL_USERTYPESTART, "scriptSetTypeEquiv: can only set type equivalence for user types (%d)", i ); } #endif asInterpTypeEquiv = psTypeTab; } static const struct { INTERP_TYPE type; const char *name; } typeToStringMap[] = { // Basic types { VAL_BOOL, "bool" }, { VAL_INT, "int" }, { VAL_FLOAT, "float" }, { VAL_STRING, "string" }, // events and triggers { VAL_TRIGGER, "trigger" }, { VAL_EVENT, "event" }, { VAL_VOID, "void" }, { VAL_OPCODE, "opcode" }, { VAL_PKOPCODE, "pkopcode" }, { VAL_OBJ_GETSET, "objgs" }, { VAL_FUNC_EXTERN, "func" }, { VAL_USERTYPESTART, "usertype" }, { VAL_REF, "ref" }, }; const char *interpTypeToString(INTERP_TYPE type) { int i; // Loop goes down -> signed // Look whether it is a defaul type: for (i = ARRAY_SIZE(typeToStringMap)-1; i >= 0 && type <= typeToStringMap[i].type; i--) { if (type >= typeToStringMap[i].type) return typeToStringMap[i].name; } // Look whether it is a user type: if (asScrTypeTab) { unsigned int i; for(i = 0; asScrTypeTab[i].typeID != 0; i++) { if (asScrTypeTab[i].typeID == type) { return asScrTypeTab[i].pIdent; } } } return "unknown"; } static const struct { OPCODE opcode; const char *name; } opcodeToStringMap[] = { { OP_PUSH, "push" }, { OP_PUSHREF, "push(ref)" }, { OP_POP, "pop" }, { OP_PUSHGLOBAL, "push(global)" }, { OP_POPGLOBAL, "pop(global)" }, { OP_PUSHARRAYGLOBAL, "push(global[])" }, { OP_POPARRAYGLOBAL, "push(global[])" }, { OP_CALL, "call" }, { OP_VARCALL, "vcall" }, { OP_JUMP, "jump" }, { OP_JUMPTRUE, "jump(true)" }, { OP_JUMPFALSE, "jump(false)" }, { OP_BINARYOP, "binary" }, { OP_UNARYOP, "unary" }, { OP_EXIT, "exit" }, { OP_PAUSE, "pause" }, // The following operations are secondary data to OP_BINARYOP and OP_UNARYOP // Maths operators { OP_ADD, "+" }, { OP_SUB, "-" }, { OP_MUL, "*" }, { OP_DIV, "/" }, { OP_NEG, "(-)" }, { OP_INC, "--" }, { OP_DEC, "++" }, // Boolean operators { OP_AND, "&&" }, { OP_OR, "||" }, { OP_NOT, "!" }, //String concatenation { OP_CONC, "&" }, // Comparison operators { OP_EQUAL, "=" }, { OP_NOTEQUAL, "!=" }, { OP_GREATEREQUAL, ">=" }, { OP_LESSEQUAL, "<=" }, { OP_GREATER, ">" }, { OP_LESS, "<" }, { OP_FUNC, "func" }, { OP_POPLOCAL, "pop(local)" }, { OP_PUSHLOCAL, "push(local)" }, { OP_PUSHLOCALREF, "push(localref)" }, { OP_TO_FLOAT, "(float)" }, { OP_TO_INT, "(int)" }, }; const char *interpOpcodeToString(OPCODE opcode) { int i; // Loop goes down -> signed // Look whether it is a defaul type: for (i = ARRAY_SIZE(opcodeToStringMap)-1; i >= 0 && opcode <= opcodeToStringMap[i].opcode; i--) { if (opcode >= opcodeToStringMap[i].opcode) return opcodeToStringMap[i].name; } return "unknown"; } const char *interpFunctionToString(SCRIPT_FUNC function) { // Search the instinct functions if (asScrInstinctTab) { unsigned int i; for(i = 0; asScrInstinctTab[i].pFunc != NULL; i++) { if (asScrInstinctTab[i].pFunc == function) { return asScrInstinctTab[i].pIdent; } } } // Search the callback functions if (asScrCallbackTab) { unsigned int i; for(i = 0; asScrCallbackTab[i].type != 0; i++) { if (asScrCallbackTab[i].pFunc == function) { return asScrCallbackTab[i].pIdent; } } } return "unknown"; } BOOL interpInitValue(INTERP_TYPE type, INTERP_VAL *value) { memset(value, 0, sizeof(*value)); value->type = type; switch (type) { case VAL_STRING: value->v.sval = malloc(MAXSTRLEN); if (value->v.sval == NULL) { debug(LOG_ERROR, "interpInitValue(string): Out of memory"); return false; } value->v.sval[0] = '\0'; break; default: break; } return true; } void interpCleanValue(INTERP_VAL *value) { switch (value->type) { case VAL_STRING: free(value->v.sval); value->v.sval = NULL; break; default: break; } } BOOL interpCopyValue(INTERP_VAL *to, INTERP_VAL *from) { /* Check whether we can do a direct copy */ if (interpCheckEquiv(to->type, from->type)) { switch (to->type) { case VAL_STRING: free(to->v.sval); to->v.sval = malloc(MAXSTRLEN); if (to->v.sval == NULL) return false; return (strlcpy(to->v.sval, from->v.sval, MAXSTRLEN) != 0); default: return (memcpy(&(to->v), &(from->v), sizeof(to->v)) != NULL); } } /* Or have to do an implicit conversion */ switch (to->type) { case VAL_STRING: switch (from->type) { case VAL_INT: return (snprintf(to->v.sval, MAXSTRLEN, "%d", from->v.ival) != 0); case VAL_BOOL: return (snprintf(to->v.sval, MAXSTRLEN, "%d", from->v.bval) != 0); case VAL_FLOAT: return (snprintf(to->v.sval, MAXSTRLEN, "%f", from->v.fval) != 0); default: break; } break; default: break; } ASSERT( false, "interpCopyValue: type mismatch (expected %s, got %s)", interpTypeToString(to->type), interpTypeToString(from->type) ); return false; } /* Check if two types are equivalent * Means: Their data can be copied without conversion. * I.e. strings are NOT equivalent to anything but strings, even though they can be converted */ BOOL interpCheckEquiv(INTERP_TYPE to, INTERP_TYPE from) { BOOL toRef = false, fromRef = false; // check for the VAL_REF flag if (to & VAL_REF) { toRef = true; to &= ~VAL_REF; } if (from & VAL_REF) { fromRef = true; from &= ~VAL_REF; } if (toRef != fromRef) { return false; } /* Void pointer is compatible with any other type */ if (toRef == true && fromRef == true && (to == VAL_VOID || from == VAL_VOID) ) { return true; } if (to == from) { return true; } else if (asInterpTypeEquiv) { unsigned int i; for (i = 0; asInterpTypeEquiv[i].base != 0; i++) { if (asInterpTypeEquiv[i].base == to) { unsigned int j; for (j = 0; j < asInterpTypeEquiv[i].numEquiv; j++) { if (asInterpTypeEquiv[i].aEquivTypes[j] == from) { return true; } } } } } return false; } /* Instinct function to turn on tracing */ BOOL interpTraceOn(void) { interpTrace = true; return true; } /* Instinct function to turn off tracing */ BOOL interpTraceOff(void) { interpTrace = false; return true; } /* Call stack stuff */ static ReturnAddressStack_t retStack[MAX_FUNC_CALLS]; // Primitive stack of return addresses static SDWORD retStackPos = -1; // Current Position, always points to the last valid element UDWORD retStackCallDepth(void) { ASSERT(retStackPos + 1 >= 0 && retStackPos + 1 < MAX_FUNC_CALLS, "retStackCallDepth: wrong call depth: %d", retStackPos + 1); return (retStackPos + 1); } static inline void retStackReset(void) { retStackPos = -1; // Beginning of the stack } static inline BOOL retStackIsEmpty(void) { if(retStackPos < 0) return true; return false; } static inline BOOL retStackIsFull(void) { if(retStackPos >= MAX_FUNC_CALLS) return true; return false; } static BOOL retStackPush(UDWORD CallerIndex, INTERP_VAL *ReturnAddress) { if (retStackIsFull()) { debug( LOG_ERROR, "retStackPush(): return address stack is full"); return false; // Stack full } retStackPos++; retStack[retStackPos].CallerIndex = CallerIndex; retStack[retStackPos].ReturnAddress = ReturnAddress; //debug( LOG_SCRIPT, "retStackPush: Event=%i Address=%p, ", CallerIndex, ReturnAddress); return true; } static BOOL retStackPop(UDWORD *CallerIndex, INTERP_VAL **ReturnAddress) { if (retStackIsEmpty()) { debug( LOG_ERROR, "retStackPop(): return address stack is empty"); return false; } *CallerIndex = retStack[retStackPos].CallerIndex; *ReturnAddress = retStack[retStackPos].ReturnAddress; retStackPos--; //debug( LOG_SCRIPT, "retStackPop: Event=%i Address=%p", *EventTrigIndex, *ReturnAddress); return true; } /* Output script call stack trace */ void scrOutputCallTrace(void) { SDWORD i; const char *pEvent; debug(LOG_SCRIPT, " *** Script call trace: ***"); if(!bInterpRunning){ debug(LOG_SCRIPT, ""); return; } if(psCurProg == NULL){ return; } debug(LOG_SCRIPT,"%d: %s (current event)", retStackPos + 1, &(last_called_script_event[0])); if(psCurProg->psDebug != NULL) { for(i=retStackPos; i>=0; i--) { if(i == 0 && !bCurCallerIsEvent){ //if original caller is a trigger pEvent = eventGetTriggerID(psCurProg, retStack[i].CallerIndex); }else{ pEvent = eventGetEventID(psCurProg, retStack[i].CallerIndex); } debug(LOG_SCRIPT,"%d: %s (return address: %p)", i, pEvent, retStack[i].ReturnAddress); } } else { debug(LOG_SCRIPT, ""); } } /* create a new local var environment for a new function call */ static inline void createVarEnvironment(SCRIPT_CONTEXT *psContext, UDWORD eventIndex) { UDWORD i, callDepth = retStackCallDepth(); UDWORD numEventVars = psContext->psCode->numLocalVars[eventIndex]; if (numEventVars > 0) { // alloc memory varEnvironment[callDepth] = (INTERP_VAL *)malloc(sizeof(INTERP_VAL) * numEventVars); // create environment memcpy(varEnvironment[callDepth], psContext->psCode->ppsLocalVars[eventIndex], sizeof(INTERP_VAL) * numEventVars); // allocate new space for strings to preserve original ones for (i = 0; i < numEventVars; i++) { if (!interpInitValue(varEnvironment[callDepth][i].type, &varEnvironment[callDepth][i])) { debug(LOG_ERROR, "createVarEnvironment: failed to init local var"); } } } else { varEnvironment[callDepth] = NULL; } } static inline void destroyVarEnvironment(SCRIPT_CONTEXT *psContext, UDWORD envIndex, UDWORD eventIndex) { UDWORD i; UDWORD numEventVars = 0; if(psContext != NULL) { numEventVars = psContext->psCode->numLocalVars[eventIndex]; } if (varEnvironment[envIndex] != NULL) { // deallocate string space for (i = 0; i < numEventVars; i++) { if (varEnvironment[envIndex][i].type == VAL_STRING) { free( varEnvironment[envIndex][i].v.sval ); varEnvironment[envIndex][i].v.sval = NULL; } } free( varEnvironment[envIndex] ); varEnvironment[envIndex] = NULL; } } /* Destroy all created variable environments */ static void cleanupVarEnvironments(void) { UDWORD i; for (i = 0; i < retStackCallDepth(); i++) { destroyVarEnvironment(NULL, i, 0); } }