/* vm.c - the arithmetic stack of a virtual machine. * * Module begun 2008-02-22 by Rainer Gerhards * * Copyright 2008 Rainer Gerhards and Adiscon GmbH. * * This file is part of the rsyslog runtime library. * * The rsyslog runtime library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The rsyslog runtime library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the rsyslog runtime library. If not, see . * * A copy of the GPL can be found in the file "COPYING" in this distribution. * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. */ #include "config.h" #include #include #include #include #include "rsyslog.h" #include "obj.h" #include "vm.h" #include "sysvar.h" #include "stringbuf.h" /* static data */ DEFobjStaticHelpers DEFobjCurrIf(vmstk) DEFobjCurrIf(var) DEFobjCurrIf(sysvar) /* ------------------------------ function registry code and structures ------------------------------ */ /* we maintain a registry of known functions */ /* currently, this is a singly-linked list, this shall become a binary * tree when we add the real call interface. So far, entries are added * at the root, only. */ typedef struct s_rsf_entry { cstr_t *pName; /* function name */ prsf_t rsf; /* pointer to function code */ struct s_rsf_entry *pNext; /* Pointer to next element or NULL */ } rsf_entry_t; rsf_entry_t *funcRegRoot = NULL; /* add a function to the function registry. * The handed-over cstr_t* object must no longer be used by the caller. * A duplicate function name is an error. * rgerhards, 2009-04-06 */ static rsRetVal rsfrAddFunction(uchar *szName, prsf_t rsf) { rsf_entry_t *pEntry; size_t lenName; DEFiRet; assert(szName != NULL); assert(rsf != NULL); /* first check if we have a duplicate name, with the current approach this means * we need to go through the whole list. */ lenName = strlen((char*)szName); for(pEntry = funcRegRoot ; pEntry != NULL ; pEntry = pEntry->pNext) if(!rsCStrSzStrCmp(pEntry->pName, szName, lenName)) ABORT_FINALIZE(RS_RET_DUP_FUNC_NAME); /* unique name, so add to head of list */ CHKmalloc(pEntry = calloc(1, sizeof(rsf_entry_t))); CHKiRet(rsCStrConstructFromszStr(&pEntry->pName, szName)); CHKiRet(cstrFinalize(pEntry->pName)); pEntry->rsf = rsf; pEntry->pNext = funcRegRoot; funcRegRoot = pEntry; finalize_it: RETiRet; } /* find a function inside the function registry * The caller provides a cstr_t with the function name and receives * a function pointer back. If no function is found, an RS_RET_UNKNW_FUNC * error is returned. So if the function returns with RS_RET_OK, the caller * can savely assume the function pointer is valid. * rgerhards, 2009-04-06 */ static rsRetVal findRSFunction(cstr_t *pcsName, prsf_t *prsf) { rsf_entry_t *pEntry; rsf_entry_t *pFound; DEFiRet; assert(prsf != NULL); /* find function by list walkthrough. */ pFound = NULL; for(pEntry = funcRegRoot ; pEntry != NULL && pFound == NULL ; pEntry = pEntry->pNext) if(!rsCStrCStrCmp(pEntry->pName, pcsName)) pFound = pEntry; if(pFound == NULL) ABORT_FINALIZE(RS_RET_UNKNW_FUNC); *prsf = pFound->rsf; finalize_it: RETiRet; } /* find the name of a RainerScript function whom's function pointer * is known. This function returns the cstr_t object, which MUST NOT * be modified by the caller. * rgerhards, 2009-04-06 */ static rsRetVal findRSFunctionName(prsf_t rsf, cstr_t **ppcsName) { rsf_entry_t *pEntry; rsf_entry_t *pFound; DEFiRet; assert(rsf != NULL); assert(ppcsName != NULL); /* find function by list walkthrough. */ pFound = NULL; for(pEntry = funcRegRoot ; pEntry != NULL && pFound == NULL ; pEntry = pEntry->pNext) if(pEntry->rsf == rsf) pFound = pEntry; if(pFound == NULL) ABORT_FINALIZE(RS_RET_UNKNW_FUNC); *ppcsName = pFound->pName; finalize_it: RETiRet; } /* free the whole function registry */ static void rsfrRemoveAll(void) { rsf_entry_t *pEntry; rsf_entry_t *pEntryDel; BEGINfunc pEntry = funcRegRoot; while(pEntry != NULL) { pEntryDel = pEntry; pEntry = pEntry->pNext; cstrDestruct(&pEntryDel->pName); free(pEntryDel); } funcRegRoot = NULL; ENDfunc } /* ------------------------------ end function registry code and structures ------------------------------ */ /* ------------------------------ instruction set implementation ------------------------------ * * The following functions implement the VM's instruction set. */ #define BEGINop(instruction) \ static rsRetVal op##instruction(vm_t *pThis, __attribute__((unused)) vmop_t *pOp) \ { \ DEFiRet; #define CODESTARTop(instruction) \ ISOBJ_TYPE_assert(pThis, vm); #define PUSHRESULTop(operand, res) \ /* we have a result, so let's push it */ \ var.SetNumber(operand, res); \ vmstk.Push(pThis->pStk, operand); /* result */ #define ENDop(instruction) \ RETiRet; \ } /* code generator for boolean operations */ #define BOOLOP(name, OPERATION) \ BEGINop(name) /* remember to set the instruction also in the ENDop macro! */ \ var_t *operand1; \ var_t *operand2; \ CODESTARTop(name) \ vmstk.PopBool(pThis->pStk, &operand1); \ vmstk.PopBool(pThis->pStk, &operand2); \ if(operand1->val.num OPERATION operand2->val.num) { \ CHKiRet(var.SetNumber(operand1, 1)); \ } else { \ CHKiRet(var.SetNumber(operand1, 0)); \ } \ vmstk.Push(pThis->pStk, operand1); /* result */ \ var.Destruct(&operand2); /* no longer needed */ \ finalize_it: \ ENDop(name) BOOLOP(OR, ||) BOOLOP(AND, &&) #undef BOOLOP /* code generator for numerical operations */ #define NUMOP(name, OPERATION) \ BEGINop(name) /* remember to set the instruction also in the ENDop macro! */ \ var_t *operand1; \ var_t *operand2; \ CODESTARTop(name) \ vmstk.PopNumber(pThis->pStk, &operand1); \ vmstk.PopNumber(pThis->pStk, &operand2); \ operand1->val.num = operand1->val.num OPERATION operand2->val.num; \ vmstk.Push(pThis->pStk, operand1); /* result */ \ var.Destruct(&operand2); /* no longer needed */ \ ENDop(name) NUMOP(PLUS, +) NUMOP(MINUS, -) NUMOP(TIMES, *) NUMOP(DIV, /) NUMOP(MOD, %) #undef BOOLOP /* code generator for compare operations */ #define BEGINCMPOP(name) \ BEGINop(name) \ var_t *operand1; \ var_t *operand2; \ number_t bRes; \ CODESTARTop(name) \ CHKiRet(vmstk.Pop2CommOp(pThis->pStk, &operand1, &operand2)); \ /* data types are equal (so we look only at operand1), but we must \ * check which type we have to deal with... \ */ \ switch(operand1->varType) { #define ENDCMPOP(name) \ default: \ bRes = 0; /* we do not abort just so that we have a value. TODO: reconsider */ \ break; \ } \ \ /* we have a result, so let's push it */ \ var.SetNumber(operand1, bRes); \ vmstk.Push(pThis->pStk, operand1); /* result */ \ var.Destruct(&operand2); /* no longer needed */ \ finalize_it: \ ENDop(name) BEGINCMPOP(CMP_EQ) /* remember to change the name also in the END macro! */ case VARTYPE_NUMBER: bRes = operand1->val.num == operand2->val.num; break; case VARTYPE_STR: bRes = !rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr); break; ENDCMPOP(CMP_EQ) BEGINCMPOP(CMP_NEQ) /* remember to change the name also in the END macro! */ case VARTYPE_NUMBER: bRes = operand1->val.num != operand2->val.num; break; case VARTYPE_STR: bRes = rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr); break; ENDCMPOP(CMP_EQ) BEGINCMPOP(CMP_LT) /* remember to change the name also in the END macro! */ case VARTYPE_NUMBER: bRes = operand1->val.num < operand2->val.num; break; case VARTYPE_STR: bRes = rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr) < 0; break; ENDCMPOP(CMP_LT) BEGINCMPOP(CMP_GT) /* remember to change the name also in the END macro! */ case VARTYPE_NUMBER: bRes = operand1->val.num > operand2->val.num; break; case VARTYPE_STR: bRes = rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr) > 0; break; ENDCMPOP(CMP_GT) BEGINCMPOP(CMP_LTEQ) /* remember to change the name also in the END macro! */ case VARTYPE_NUMBER: bRes = operand1->val.num <= operand2->val.num; break; case VARTYPE_STR: bRes = rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr) <= 0; break; ENDCMPOP(CMP_LTEQ) BEGINCMPOP(CMP_GTEQ) /* remember to change the name also in the END macro! */ case VARTYPE_NUMBER: bRes = operand1->val.num >= operand2->val.num; break; case VARTYPE_STR: bRes = rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr) >= 0; break; ENDCMPOP(CMP_GTEQ) #undef BEGINCMPOP #undef ENDCMPOP /* end regular compare operations */ /* comare operations that work on strings, only */ BEGINop(CMP_CONTAINS) /* remember to set the instruction also in the ENDop macro! */ var_t *operand1; var_t *operand2; number_t bRes; CODESTARTop(CMP_CONTAINS) /* operand2 is on top of stack, so needs to be popped first */ vmstk.PopString(pThis->pStk, &operand2); vmstk.PopString(pThis->pStk, &operand1); /* TODO: extend cstr class so that it supports location of cstr inside cstr */ bRes = (rsCStrLocateInSzStr(operand2->val.pStr, rsCStrGetSzStr(operand1->val.pStr)) == -1) ? 0 : 1; /* we have a result, so let's push it */ PUSHRESULTop(operand1, bRes); var.Destruct(&operand2); /* no longer needed */ ENDop(CMP_CONTAINS) BEGINop(CMP_CONTAINSI) /* remember to set the instruction also in the ENDop macro! */ var_t *operand1; var_t *operand2; number_t bRes; CODESTARTop(CMP_CONTAINSI) /* operand2 is on top of stack, so needs to be popped first */ vmstk.PopString(pThis->pStk, &operand2); vmstk.PopString(pThis->pStk, &operand1); var.DebugPrint(operand1); \ var.DebugPrint(operand2); \ /* TODO: extend cstr class so that it supports location of cstr inside cstr */ bRes = (rsCStrCaseInsensitiveLocateInSzStr(operand2->val.pStr, rsCStrGetSzStr(operand1->val.pStr)) == -1) ? 0 : 1; /* we have a result, so let's push it */ PUSHRESULTop(operand1, bRes); var.Destruct(&operand2); /* no longer needed */ ENDop(CMP_CONTAINSI) BEGINop(CMP_STARTSWITH) /* remember to set the instruction also in the ENDop macro! */ var_t *operand1; var_t *operand2; number_t bRes; CODESTARTop(CMP_STARTSWITH) /* operand2 is on top of stack, so needs to be popped first */ vmstk.PopString(pThis->pStk, &operand2); vmstk.PopString(pThis->pStk, &operand1); /* TODO: extend cstr class so that it supports location of cstr inside cstr */ bRes = (rsCStrStartsWithSzStr(operand1->val.pStr, rsCStrGetSzStr(operand2->val.pStr), rsCStrLen(operand2->val.pStr)) == 0) ? 1 : 0; /* we have a result, so let's push it */ PUSHRESULTop(operand1, bRes); var.Destruct(&operand2); /* no longer needed */ ENDop(CMP_STARTSWITH) BEGINop(CMP_STARTSWITHI) /* remember to set the instruction also in the ENDop macro! */ var_t *operand1; var_t *operand2; number_t bRes; CODESTARTop(CMP_STARTSWITHI) /* operand2 is on top of stack, so needs to be popped first */ vmstk.PopString(pThis->pStk, &operand2); vmstk.PopString(pThis->pStk, &operand1); /* TODO: extend cstr class so that it supports location of cstr inside cstr */ bRes = (rsCStrCaseInsensitveStartsWithSzStr(operand1->val.pStr, rsCStrGetSzStr(operand2->val.pStr), rsCStrLen(operand2->val.pStr)) == 0) ? 1 : 0; /* we have a result, so let's push it */ PUSHRESULTop(operand1, bRes); var.Destruct(&operand2); /* no longer needed */ ENDop(CMP_STARTSWITHI) /* end comare operations that work on strings, only */ BEGINop(STRADD) /* remember to set the instruction also in the ENDop macro! */ var_t *operand1; var_t *operand2; CODESTARTop(STRADD) vmstk.PopString(pThis->pStk, &operand2); vmstk.PopString(pThis->pStk, &operand1); CHKiRet(rsCStrAppendCStr(operand1->val.pStr, operand2->val.pStr)); CHKiRet(cstrFinalize(operand1->val.pStr)); /* we have a result, so let's push it */ vmstk.Push(pThis->pStk, operand1); var.Destruct(&operand2); /* no longer needed */ finalize_it: ENDop(STRADD) BEGINop(NOT) /* remember to set the instruction also in the ENDop macro! */ var_t *operand; CODESTARTop(NOT) vmstk.PopBool(pThis->pStk, &operand); PUSHRESULTop(operand, !operand->val.num); ENDop(NOT) BEGINop(UNARY_MINUS) /* remember to set the instruction also in the ENDop macro! */ var_t *operand; CODESTARTop(UNARY_MINUS) vmstk.PopNumber(pThis->pStk, &operand); PUSHRESULTop(operand, -operand->val.num); ENDop(UNARY_MINUS) BEGINop(PUSHCONSTANT) /* remember to set the instruction also in the ENDop macro! */ var_t *pVarDup; /* we need to duplicate the var, as we need to hand it over */ CODESTARTop(PUSHCONSTANT) CHKiRet(var.Duplicate(pOp->operand.pVar, &pVarDup)); vmstk.Push(pThis->pStk, pVarDup); finalize_it: ENDop(PUSHCONSTANT) BEGINop(PUSHMSGVAR) /* remember to set the instruction also in the ENDop macro! */ var_t *pVal; /* the value to push */ cstr_t *pstrVal; CODESTARTop(PUSHMSGVAR) if(pThis->pMsg == NULL) { /* TODO: flag an error message! As a work-around, we permit * execution to continue here with an empty string */ /* TODO: create a method in var to create a string var? */ CHKiRet(var.Construct(&pVal)); CHKiRet(var.ConstructFinalize(pVal)); CHKiRet(rsCStrConstructFromszStr(&pstrVal, (uchar*)"")); CHKiRet(var.SetString(pVal, pstrVal)); } else { /* we have a message, so pull value from there */ CHKiRet(msgGetMsgVar(pThis->pMsg, pOp->operand.pVar->val.pStr, &pVal)); } /* if we reach this point, we have a valid pVal and can push it */ vmstk.Push(pThis->pStk, pVal); finalize_it: ENDop(PUSHMSGVAR) BEGINop(PUSHSYSVAR) /* remember to set the instruction also in the ENDop macro! */ var_t *pVal; /* the value to push */ CODESTARTop(PUSHSYSVAR) CHKiRet(sysvar.GetVar(pOp->operand.pVar->val.pStr, &pVal)); vmstk.Push(pThis->pStk, pVal); finalize_it: ENDop(PUSHSYSVAR) /* The function call operation is only very roughly implemented. While the plumbing * to reach this instruction is fine, the instruction itself currently supports only * functions with a single argument AND with a name that we know. * TODO: later, we can add here the real logic, that involves looking up function * names, loading them dynamically ... and all that... * implementation begun 2009-03-10 by rgerhards */ BEGINop(FUNC_CALL) /* remember to set the instruction also in the ENDop macro! */ var_t *numOperands; CODESTARTop(FUNC_CALL) vmstk.PopNumber(pThis->pStk, &numOperands); CHKiRet((*pOp->operand.rsf)(pThis->pStk, numOperands->val.num)); var.Destruct(&numOperands); /* no longer needed */ finalize_it: ENDop(FUNC_CALL) /* ------------------------------ end instruction set implementation ------------------------------ */ /* ------------------------------ begin built-in function implementation ------------------------------ */ /* note: this shall probably be moved to a separate module, but for the time being we do it directly * in here. This is on our way to get from a dirty to a clean solution via baby steps that are * a bit less dirty each time... * * The advantage of doing it here is that we do not yet need to think about how to handle the * exit case, where we must not unload function modules which functions are still referenced. * * CALLING INTERFACE: * The function must pop its parameters off the stack and pop its result onto * the stack when it is finished. The number of parameters the function was * called with is provided to it. If the argument count is less then what the function * expected, it may handle the situation with defaults (or return an error). If the * argument count is greater than expected, returnung an error is highly * recommended (use RS_RET_INVLD_NBR_ARGUMENTS for these cases). * * All function names are prefixed with "rsf_" (RainerScript Function) to have * a separate "name space". * * rgerhards, 2009-04-06 */ /* The strlen function, also probably a prototype of how all functions should be * implemented. * rgerhards, 2009-04-06 */ static rsRetVal rsf_strlen(vmstk_t *pStk, int numOperands) { DEFiRet; var_t *operand1; int iStrlen; if(numOperands != 1) ABORT_FINALIZE(RS_RET_INVLD_NBR_ARGUMENTS); /* pop args and do operaton (trivial case here...) */ vmstk.PopString(pStk, &operand1); iStrlen = strlen((char*) rsCStrGetSzStr(operand1->val.pStr)); /* Store result and cleanup */ var.SetNumber(operand1, iStrlen); vmstk.Push(pStk, operand1); finalize_it: RETiRet; } /* The "tolower" function, which converts its sole argument to lower case. * Quite honestly, currently this is primarily a test driver for me... * rgerhards, 2009-04-06 */ static rsRetVal rsf_tolower(vmstk_t *pStk, int numOperands) { DEFiRet; var_t *operand1; uchar *pSrc; cstr_t *pcstr; int iStrlen; if(numOperands != 1) ABORT_FINALIZE(RS_RET_INVLD_NBR_ARGUMENTS); /* pop args and do operaton */ CHKiRet(cstrConstruct(&pcstr)); vmstk.PopString(pStk, &operand1); pSrc = cstrGetSzStr(operand1->val.pStr); iStrlen = strlen((char*)pSrc); // TODO: use count from string! while(iStrlen--) { CHKiRet(cstrAppendChar(pcstr, tolower(*pSrc++))); } /* Store result and cleanup */ CHKiRet(cstrFinalize(pcstr)); var.SetString(operand1, pcstr); vmstk.Push(pStk, operand1); finalize_it: RETiRet; } /* Standard-Constructor */ BEGINobjConstruct(vm) /* be sure to specify the object type also in END macro! */ ENDobjConstruct(vm) /* ConstructionFinalizer * rgerhards, 2008-01-09 */ static rsRetVal vmConstructFinalize(vm_t __attribute__((unused)) *pThis) { DEFiRet; ISOBJ_TYPE_assert(pThis, vm); CHKiRet(vmstk.Construct(&pThis->pStk)); CHKiRet(vmstk.ConstructFinalize(pThis->pStk)); finalize_it: RETiRet; } /* destructor for the vm object */ BEGINobjDestruct(vm) /* be sure to specify the object type also in END and CODESTART macros! */ CODESTARTobjDestruct(vm) if(pThis->pStk != NULL) vmstk.Destruct(&pThis->pStk); if(pThis->pMsg != NULL) msgDestruct(&pThis->pMsg); ENDobjDestruct(vm) /* debugprint for the vm object */ BEGINobjDebugPrint(vm) /* be sure to specify the object type also in END and CODESTART macros! */ CODESTARTobjDebugPrint(vm) dbgoprint((obj_t*) pThis, "rsyslog virtual machine, currently no state info available\n"); ENDobjDebugPrint(vm) /* execute a program */ static rsRetVal execProg(vm_t *pThis, vmprg_t *pProg) { DEFiRet; vmop_t *pCurrOp; /* virtual instruction pointer */ ISOBJ_TYPE_assert(pThis, vm); ISOBJ_TYPE_assert(pProg, vmprg); #define doOP(OP) case opcode_##OP: CHKiRet(op##OP(pThis, pCurrOp)); break pCurrOp = pProg->vmopRoot; /* TODO: do this via a method! */ while(pCurrOp != NULL && pCurrOp->opcode != opcode_END_PROG) { switch(pCurrOp->opcode) { doOP(OR); doOP(AND); doOP(CMP_EQ); doOP(CMP_NEQ); doOP(CMP_LT); doOP(CMP_GT); doOP(CMP_LTEQ); doOP(CMP_GTEQ); doOP(CMP_CONTAINS); doOP(CMP_CONTAINSI); doOP(CMP_STARTSWITH); doOP(CMP_STARTSWITHI); doOP(NOT); doOP(PUSHCONSTANT); doOP(PUSHMSGVAR); doOP(PUSHSYSVAR); doOP(STRADD); doOP(PLUS); doOP(MINUS); doOP(TIMES); doOP(DIV); doOP(MOD); doOP(UNARY_MINUS); doOP(FUNC_CALL); default: ABORT_FINALIZE(RS_RET_INVALID_VMOP); dbgoprint((obj_t*) pThis, "invalid instruction %d in vmprg\n", pCurrOp->opcode); break; } /* so far, we have plain sequential execution, so on to next... */ pCurrOp = pCurrOp->pNext; } #undef doOP /* if we reach this point, our program has intintionally terminated * (no error state). */ finalize_it: RETiRet; } /* Set the current message object for the VM. It *is* valid to set a * NULL message object, what simply means there is none. Message * objects are properly reference counted. */ static rsRetVal SetMsg(vm_t *pThis, msg_t *pMsg) { DEFiRet; if(pThis->pMsg != NULL) { msgDestruct(&pThis->pMsg); } if(pMsg != NULL) { pThis->pMsg = MsgAddRef(pMsg); } RETiRet; } /* Pop a var from the stack and return it to caller. The variable type is not * changed, it is taken from the stack as is. This functionality is * partly needed. We may (or may not ;)) be able to remove it once we have * full RainerScript support. -- rgerhards, 2008-02-25 */ static rsRetVal PopVarFromStack(vm_t *pThis, var_t **ppVar) { DEFiRet; CHKiRet(vmstk.Pop(pThis->pStk, ppVar)); finalize_it: RETiRet; } /* Pop a boolean from the stack and return it to caller. This functionality is * partly needed. We may (or may not ;)) be able to remove it once we have * full RainerScript support. -- rgerhards, 2008-02-25 */ static rsRetVal PopBoolFromStack(vm_t *pThis, var_t **ppVar) { DEFiRet; CHKiRet(vmstk.PopBool(pThis->pStk, ppVar)); finalize_it: RETiRet; } /* queryInterface function * rgerhards, 2008-02-21 */ BEGINobjQueryInterface(vm) CODESTARTobjQueryInterface(vm) if(pIf->ifVersion != vmCURR_IF_VERSION) { /* check for current version, increment on each change */ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); } /* ok, we have the right interface, so let's fill it * Please note that we may also do some backwards-compatibility * work here (if we can support an older interface version - that, * of course, also affects the "if" above). */ pIf->Construct = vmConstruct; pIf->ConstructFinalize = vmConstructFinalize; pIf->Destruct = vmDestruct; pIf->DebugPrint = vmDebugPrint; pIf->ExecProg = execProg; pIf->PopBoolFromStack = PopBoolFromStack; pIf->PopVarFromStack = PopVarFromStack; pIf->SetMsg = SetMsg; pIf->FindRSFunction = findRSFunction; pIf->FindRSFunctionName = findRSFunctionName; finalize_it: ENDobjQueryInterface(vm) /* Exit the vm class. * rgerhards, 2009-04-06 */ BEGINObjClassExit(vm, OBJ_IS_CORE_MODULE) /* class, version */ rsfrRemoveAll(); objRelease(sysvar, CORE_COMPONENT); objRelease(var, CORE_COMPONENT); objRelease(vmstk, CORE_COMPONENT); ENDObjClassExit(vm) /* Initialize the vm class. Must be called as the very first method * before anything else is called inside this class. * rgerhards, 2008-02-19 */ BEGINObjClassInit(vm, 1, OBJ_IS_CORE_MODULE) /* class, version */ /* request objects we use */ CHKiRet(objUse(vmstk, CORE_COMPONENT)); CHKiRet(objUse(var, CORE_COMPONENT)); CHKiRet(objUse(sysvar, CORE_COMPONENT)); /* set our own handlers */ OBJSetMethodHandler(objMethod_DEBUGPRINT, vmDebugPrint); OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, vmConstructFinalize); /* register built-in functions // TODO: move to its own module */ CHKiRet(rsfrAddFunction((uchar*)"strlen", rsf_strlen)); CHKiRet(rsfrAddFunction((uchar*)"tolower", rsf_tolower)); ENDObjClassInit(vm) /* vi:set ai: */