/* * AUTHOR * N. Nielsen * * LICENSE * This software is in the public domain. * * The software is provided "as is", without warranty of any kind, * express or implied, including but not limited to the warranties * of merchantability, fitness for a particular purpose, and * noninfringement. In no event shall the author(s) be liable for any * claim, damages, or other liability, whether in an action of * contract, tort, or otherwise, arising from, out of, or in connection * with the software or the use or other dealings in the software. * * SUPPORT * Send bug reports to: */ /* ---------------------------------------------------------------------- * Rep Execution Unit * 2000-2001 Copyright, Nate Nielsen */ #include "common/usuals.h" #include "common/compat.h" #include "common/xstring.h" #include "lib/rlib.h" #include "priv.h" #include "execute.h" #include "ops.h" /* 100 million loops max! */ #define MAX_PASSES 100000000 /* NOTE: all *end* counts/pointers point to the last char of the actual data */ /* Quick access macro for accessing VM registers */ #define REGISTER(reg) \ (state->vmregs[ARG_GET_REGISTER(reg)]) #define REGISTER2(reg, off) \ (state->vmregs[ARG_GET_REGISTER(reg) + (off)]) /* ========================================================================= * MEMORY: Memory blocks are used by the code and are simply a set * of numeric values indexed by key. */ /* _memoryAllocate: --------------------------------------------------- * Helper which double the size of a 'memory' block if needed */ bool _memoryAllocate(memory* mem) { ASSERT_PTR(mem); if(!mem->thememory) { /* Allocate new set */ mem->thememory = (struct mem*)malloc(sizeof(struct mem) * BLOCK_SIZE); if(!mem->thememory) return false; mem->alloc = BLOCK_SIZE; memoryClearAll(mem); } /* If no more space ... */ if(mem->alloc <= mem->cur) { /* ...double allocation */ size_t sz = sizeof(struct mem) * mem->alloc; mem->thememory = (struct mem*)reallocf(mem->thememory, sz * 2); if(!mem->thememory) return false; mem->alloc *= 2; } return true; } /* _memoryFind: ---------------------------------------------------- * Find memory location for given key */ struct mem* _memoryFind(memory* mem, uint key) { size_t i; ASSERT_PTR(mem); for(i = 0; i < mem->cur; i++) { if(key == mem->thememory[i].key) return &(mem->thememory[i]); } return 0; } /* memoryInit: ----------------------------------------------------- * Initialize and or allocate a memory block */ bool memoryInit(memory* mem) { zero(*mem); return true;; } /* memoryFree: ---------------------------------------------------- * Free a memory block */ void memoryFree(memory* mem) { if(mem) { ASSERT_PTR(mem); if(mem->thememory) free(mem->thememory); mem->thememory = NULL; } } /* memoryValue: -------------------------------------------------- * Get a value for key from memory */ uint* memoryValue(memory* mem, uint key) { struct mem* m; ASSERT_PTR(mem); if(!_memoryAllocate(mem)) return NULL; m = _memoryFind(mem, key); if(!m) { /* Add new watermark */ mem->thememory[mem->cur].key = key; mem->thememory[mem->cur].value = 0; m = &(mem->thememory[mem->cur]); mem->cur++; } return &(m->value); } /* memoryClearAll: --------------------------------------------------- * Clear all values from block */ void memoryClearAll(memory* mem) { ASSERT_PTR(mem); mem->cur = 0; } /* ========================================================================= * DATA: */ /* _dataAllocate: --------------------------------------------------- * Helper which double the size of a 'memory' block if needed */ bool _dataAllocate(data* dat) { ASSERT_PTR(dat); if(!dat->thedata) { /* Allocate new set */ dat->thedata = (struct dat*)malloc(sizeof(struct dat) * BLOCK_SIZE); if(!dat->thedata) return false; dat->alloc = BLOCK_SIZE; dataClearAll(dat); } /* If no more space ... */ if(dat->alloc <= dat->cur) { /* ...double allocation */ size_t sz = sizeof(struct dat) * dat->alloc; dat->thedata = (struct dat*)reallocf(dat->thedata, sz * 2); if(!dat->thedata) return false; dat->alloc *= 2; } return true; } /* _dataFind: ---------------------------------------------------- * Find memory location for given key */ struct dat* _dataFind(data* dat, void* key) { size_t i; ASSERT_PTR(dat); for(i = 0; i < dat->cur; i++) { if(key == dat->thedata[i].key) return &(dat->thedata[i]); } return 0; } /* dataInit: ----------------------------------------------------- * Initialize and or allocate a data block */ bool dataInit(data* dat) { zero(*dat); return true; } /* dataFree: ---------------------------------------------------- * Free a data block */ void dataFree(data* dat) { if(dat) { ASSERT_PTR(dat); if(dat->thedata) { dataClearAll(dat); free(dat->thedata); } dat->thedata = NULL; } } /* dataGetValue: -------------------------------------------------- * Get a value for key from data */ void* dataGetValue(data* dat, void* key) { struct dat* d; ASSERT_PTR(dat); if((d = _dataFind(dat, key))) return d->value; return NULL; } /* dataSetValue: -------------------------------------------------- * Set a value for key */ bool dataSetValue(data* dat, void* key, void* value) { struct dat* d; ASSERT_PTR(dat); if(!_dataAllocate(dat)) return false; if((d = _dataFind(dat, key))) { d->value = value; } else { /* Add new one */ dat->thedata[dat->cur].key = key; dat->thedata[dat->cur].value = value; dat->cur++; } return true; } /* dataClearAll: --------------------------------------------------- * Clear all values from block */ void dataClearAll(data* dat) { size_t i; ASSERT_PTR(dat); if(dat->thedata) { for(i = 0; i < dat->cur; i++) { if(dat->thedata[i].value) free(dat->thedata[i].value); } } dat->cur = 0; } /* ======================================================================== * VARIABLES: Variables are strings indexed by a name. */ /* _variablesAllocate: ---------------------------------------------- * Expand a block of variables if necessary */ bool _variablesAllocate(variables* vars) { ASSERT_PTR(vars); if(!vars->thevars) { /* Allocate new set */ vars->thevars = (struct vari*)malloc(sizeof(struct vari) * BLOCK_SIZE); if(!vars->thevars) return false; vars->alloc = BLOCK_SIZE; vars->cur = 0; } /* If no more space ... */ if(vars->alloc <= vars->cur) { /* ...double allocation */ size_t sz = sizeof(struct vari) * vars->alloc; vars->thevars = (struct vari*)reallocf(vars->thevars, sz * 2); if(!vars->thevars) return false; vars->alloc *= 2; } return true; } /* _variablesNew: -------------------------------------------------------- * Make a new variable in a given block */ struct vari* _variablesNew(variables* vars, const char* name, const char* val, size_t cnt) { struct vari* v; ASSERT_PTR(vars); if(!_variablesAllocate(vars)) return NULL; v = &(vars->thevars[vars->cur]); /* Add new variable */ v->name = strdup(name); v->value = (char*)malloc(sizeof(char) * (cnt + 2)); starclr(v->value); starnadd(&(v->value), val, cnt); /* Oops! */ if(!v->name || !v->value) { if(v->name) free(v->name); if(v->value) free(v->value); return NULL; } vars->cur++; return v; } /* _variablesFind: ------------------------------------------------------ * Find a variable in the block by name */ struct vari* _variablesFind(variables* vars, const char* name) { size_t i; char* val; ASSERT_PTR(vars); i = vars->cur; while(i--) { if(!strcasecmp(name, vars->thevars[i].name)) return &(vars->thevars[i]); } /* If not found then look up in the Environment */ if((val = getenv(name))) return _variablesNew(vars, name, val, strlen(val)); return NULL; } /* variablesInit: ------------------------------------------------------ * Initialize and possibly allocate a variable set */ bool variablesInit(variables* vars) { zero(*vars); return true; } /* variablesFree: ------------------------------------------------------ * Free a set of variables */ void variablesFree(variables* vars) { if(vars) { ASSERT_PTR(vars); if(vars->thevars) free(vars->thevars); vars->thevars = NULL; } } /* variablesAddBytes: -------------------------------------------------- * Add a counted string to a variable. */ bool variablesAddBytes(variables* vars, const char* name, const char* val, size_t cnt) { struct vari* v; ASSERT_PTR(vars); v = _variablesFind(vars, name); if(!v) { return _variablesNew(vars, name, val, cnt) != NULL; } else { /* Already have variable. Just add value */ ASSERT(v->value); starnadd(&(v->value), val, cnt); return v->value != NULL; } } /* variablesAdd: ------------------------------------------------------- * Add a string to a variable. */ bool variablesAdd(variables* vars, const char* name, const char* val) { ASSERT_PTR(vars); return variablesAddBytes(vars, name, val, strlen(val)); } /* variablesClear: ---------------------------------------------------- * Clear a variable's value. */ bool variablesClear(variables* vars, const char* name) { struct vari* v; ASSERT_PTR(vars); if((v = _variablesFind(vars, name))) starclr(v->value); return true; } /* variablesClearAll: ------------------------------------------------- * Delete all variables from set */ bool variablesClearAll(variables* vars) { ASSERT_PTR(vars); if(vars->thevars) { while(vars->cur--) { ASSERT(vars->thevars[vars->cur].name); free(vars->thevars[vars->cur].name); ASSERT(vars->thevars[vars->cur].value); free(vars->thevars[vars->cur].value); } vars->cur = 0; } return true; } /* _escapeString: ----------------------------------------------------- * Helper function to make a string pass through the regex compiler */ static char* _escapeString(const char* string) { const char kSpecialChars[] = "$^*(){}[]\\?+."; size_t pos = 0; size_t cnt = 0; char* buff; size_t len = strlen(string); while((pos += strcspn(string + pos, kSpecialChars)) < len) cnt++, pos++; if((buff = (char*)malloc(sizeof(char) * (len + cnt + 1)))) { pos = 0; strcpy(buff, string); while((pos += strcspn(buff + pos, kSpecialChars)) < len + cnt) strins(buff + pos, "\\"), pos += 2; } return buff; } /* variablesSubstitute: ----------------------------------------------- * Perform variable and register substitution on a string */ int variablesSubstitute(variables* vars, r_stream* stream, r_script* script, char** pstr, bool mode) { char* next = *pstr; struct internal_state* state = stream->state; ASSERT_PTR(vars); ASSERT_PTR(state); #define SYNTAX_ERROR(s) \ do{ \ scriptSetError(script, s); \ return R_SYNTAX; \ } while(0) /* If mode == true then we're substituting inside a regular expression otherwise we're substituting in replaced text */ /* Find next backslash or percent */ while(next += strcspn(next, "\\%"), next && next[0] != 0) { switch(*next) { /* It's a variable or a register */ case '%': /* Is it a register? */ if(isdigit(next[1])) { /* Registers only in replace mode replace mode */ if(!mode) { uint reg = next[1] - 0x30; /* Get register number */ if(reg < REGISTER(r_cg)) { /* Save offset for reallocations */ size_t off = next - *pstr; size_t reglen = REGISTER2(r_e0, reg) - REGISTER2(r_b0, reg); /* Reallocate to fit register text */ strrsrv(*pstr, strlen(*pstr) + reglen); if(!(*pstr)) return R_NOMEM; /* offset next properly */ next = *pstr + off; /* Replace \N with register text */ next = strnrep(next, 2, (stream->nextIn + ABS_TO_REL(REGISTER2(r_b0, reg), stream->state)), reglen); } else /* If invalid number just blow away */ next = strnrep(next, 2, "", 0); } } /* Otherwise it's a variable */ else { bool multi = false; size_t len, off; char temp; struct vari* v; const char* value; /* Get the name */ len = strspn(next + 1, kValidIdentifier); if(len == 0) SYNTAX_ERROR("Invalid variable name."); /* Increment length for the % prefix */ len++; /* Null terminate the variable name */ temp = next[len]; next[len] = 0; /* Do we have this variable? */ if((v = _variablesFind(vars, next + 1))) value = v->value; else value = "\0\0"; /* Check if the variable is an array */ if(mode) multi = starnext(value) ? true : false; /* Unnull-terminate it */ next[len] = temp; /* Get offset for reallocations */ off = next - *pstr; /* Reallocate the string to accomodate new replacement */ strrsrv(*pstr, strlen(*pstr) + (starend(value) - value) + 4); if(!*pstr) return R_NOMEM; /* Offset next back properly */ next = *pstr + off; /* Eat variable name and add open parentheses if needed The '?:' after the opening paren denotes a non capturing group */ next = strrep(next, len, multi ? "(?:" : ""); do { /* If in regular expression mode ... */ char* escval; if(mode) { /* ... escape the value */ escval = _escapeString(value); if(!escval) return R_NOMEM; } /* Insert the value */ next = strrep(next, 0, mode ? escval : value); /* If in regular expression mode free escaped. */ if(mode) free(escval); /* If not multi then only put the first one in */ if(!multi) break; /* Add a pipe (alternation operator) if we have more values */ else next = strrep(next, 0, starnext(value) ? "|" : ""); } while((value = starnext(value))); /* Add closing parentheses if needed */ next = strrep(next, 0, multi ? ")" : ""); } break; /* It's a variable name or escaped character */ case '\\': /* Only unescape in replace mode Regular expressions will unescape everything else */ if(!mode) next = strnrep(next, 1, "", 0); else next++; next++; break; } } return R_OK; } /* variablesValidName: ------------------------------------------------- * Is the given name a valid variable name */ bool variablesValidName(const char* name) { return strspn(name, kValidIdentifier) >= strlen(name); } /* variablesHasVars: ---------------------------------------------------- * Does the given string have variables or registers that need * substitution? */ bool variablesHasVars(const char* string) { const char* cur = string; if((cur = strchr(cur, '%'))) { if(!isEscaped(string, cur)) return true; cur++; } return false; } /* ========================================================================= * LOCKS: Locks are used by the engine to implement the replacement locks */ /* _locksAllocate: ------------------------------------------------------- * Expand a block of locks if needed */ bool _locksAllocate(locks* lcks) { if(!lcks->thelocks) { /* Allocate the first set */ lcks->thelocks = (struct lock*)malloc(sizeof(struct lock) * BLOCK_SIZE); if(!lcks->thelocks) return false; lcks->alloc = BLOCK_SIZE; locksClearAll(lcks); } /* If not enough space ... */ if(lcks->alloc <= lcks->cur) { /* ... reallocate double */ size_t sz = sizeof(struct lock) * lcks->alloc; lcks->thelocks = (struct lock*)reallocf(lcks->thelocks, sz * 2); if(!lcks->thelocks) return false; lcks->alloc *= 2; } return true; } /* locksInit: ------------------------------------------------------------- * Initialize and or allocate a block of locks */ bool locksInit(locks* lcks) { zero(*lcks); return true; } /* locksFree: ------------------------------------------------------------- * Free a set of locks */ void locksFree(locks* lcks) { if(lcks) { ASSERT_PTR(lcks); if(lcks->thelocks) free(lcks->thelocks); lcks->thelocks = NULL; } } #define RANGE_ADD(bd, ed, b1, e1) \ (((bd) = (bd) < (b1) ? (bd) : (b1)), ((ed) = (ed) > (e1) ? (ed) : (e1))) #define RANGE_INTERSECTS(b1, e1, b2, e2) \ ((b1) < (e2) && (e1) > (b2)) #define RANGE_BEFORE(b1, e1, b2, e2) \ ((b1) <= (b2) && (e1) <= (e2)) /* locksAdd: --------------------------------------------------------------- * Add a lock to the set */ bool locksAdd(locks* lcks, size_t beg, size_t end) { size_t i = 0; for(; i < lcks->cur; i++) { if(RANGE_INTERSECTS(beg, end, lcks->thelocks[i].beg, lcks->thelocks[i].end)) { RANGE_ADD(lcks->thelocks[i].beg, lcks->thelocks[i].end, beg, end); /* Clean up any doubles */ for(i = 0; i < lcks->cur - 1; i++) { if(RANGE_INTERSECTS(lcks->thelocks[i].beg, lcks->thelocks[i].end, lcks->thelocks[i + 1].beg, lcks->thelocks[i + 1].end)) { RANGE_ADD(lcks->thelocks[i].beg, lcks->thelocks[i].end, lcks->thelocks[i + 1].beg, lcks->thelocks[i + 1].end); lcks->cur--; memmove(lcks->thelocks + i + 1, lcks->thelocks + i + 2, sizeof(lcks->thelocks[0]) * (lcks->cur - (i + 1))); i--; } } return true; } if(RANGE_BEFORE(beg, end, lcks->thelocks[i].beg, lcks->thelocks[i].end)) break; } if(!_locksAllocate(lcks)) return false; /* Move the locks one down */ memmove(lcks->thelocks + i + 1, lcks->thelocks + i, sizeof(lcks->thelocks[0]) * (lcks->cur - i)); lcks->thelocks[i].beg = beg; lcks->thelocks[i].end = end; lcks->cur++; return true; } #ifdef _DEBUG static void _locksTestIntersects() { ASSERT(!RANGE_INTERSECTS(0x0F, 0x15, 0x15, 0x15)); ASSERT(RANGE_INTERSECTS(0x0F, 0x15, 0x0F, 0x15)); ASSERT(RANGE_INTERSECTS(0x10, 0x15, 0x0F, 0x15)); ASSERT(RANGE_INTERSECTS(0x0F, 0x12, 0x0F, 0x15)); ASSERT(RANGE_INTERSECTS(0x10, 0x12, 0x0F, 0x15)); ASSERT(RANGE_INTERSECTS(0x0F, 0x15, 0x10, 0x12)); ASSERT(RANGE_INTERSECTS(0x10, 0x1A, 0x0F, 0x15)); ASSERT(RANGE_INTERSECTS(0x02, 0x12, 0x0F, 0x15)); ASSERT(!RANGE_INTERSECTS(0x15, 0x15, 0x15, 0x15)); ASSERT(!RANGE_INTERSECTS(0x15, 0x15, 0x15, 0x20)); } #endif /* locksCheck: ----------------------------------------------------------- * Check a range against the locks */ bool locksCheck(locks* lcks, r_stream* stream, size_t beg, size_t end) { size_t cnt; #ifdef _DEBUG _locksTestIntersects(); #endif for(cnt = 0; cnt < lcks->cur; cnt++) if(RANGE_INTERSECTS(beg, end, lcks->thelocks[cnt].beg, lcks->thelocks[cnt].end)) return true; return false; } /* ============================================================================ * REPLACEMENT LIST: Contains the text of any replacements. They're copied * into the output later. */ /* replacementAlloc: --------------------------------------------------------- * Prepare and allocate a replacement */ int replacementAlloc(r_stream* stream, const char* text, replacement** pprep) { int ret; size_t len = strlen(text); ret = R_OK; /* Allocate the replacement. Note that text hangs off the end */ *pprep = (replacement*)malloc(sizeof(replacement) + (sizeof(char) * len)); if(!*pprep) ret = R_NOMEM; else { /* Set it up properly */ memset(*pprep, 0, sizeof(replacement) + len); (*pprep)->next = NULL; /* Copy string on end of buffer */ strcpy((*pprep)->text, text); } return ret; } /* replacementAdd: ----------------------------------------------------------- * Insert replacement into queue in the right order (IMPORTANT!) */ void replacementAdd(replacement* repl, r_stream* stream) { replacement top; replacement* first = ⊤ top.beg = top.end = 0; top.next = stream->state->replaces; /* Find appropriate pos */ while(first && first->next && (first->next->end < repl->end)) first = first->next; while(first && first->next && (first->next->beg < repl->beg)) first = first->next; /* Hook in */ repl->next = first->next; first->next = repl; stream->state->replaces = top.next; } /* replacementPop: ------------------------------------------------------- * Remove and return a replacement from the queue */ replacement* replacementPop(replacement* repl) { replacement* ret = repl->next; free(repl); return ret; } /* replacementDump: ------------------------------------------------------ * Dump all replacements in the stream to stderr */ void replacementDump(r_stream* stream) { replacement* repl = stream->state->replaces; while(repl) { fprintf(stderr, " beg: %x end: %x text: \'%s\'\n", repl->beg, repl->end, repl->text); repl = repl->next; } } /* ========================================================================= * REGISTER HELPER FUNCTIONS */ /* regsSet: ---------------------------------------------------------- * Offset and copy an entire set of regs int the main registers */ static void regsSet(struct internal_state* state, int pcreregs[], size_t offset) { size_t i; for(i = 0; i < REGISTER(r_cg); i++) { REGISTER2(r_b0, i) = pcreregs[i * 2] + offset; REGISTER2(r_e0, i) = pcreregs[(i * 2) + 1] + offset; } /* Set rest of registers as invalid */ for( ; i < MAX_REGS; i++) { REGISTER2(r_b0, i) = ~0; REGISTER2(r_b0, i) = ~0; } } /* =========================================================================== * MAIN EXECUTION FUNCTIONS */ /* vmInit: -------------------------------------------------------------- * Initialize state for execution */ bool vmInit(r_stream* stream) { struct internal_state* state = stream->state; ASSERT(stream->state); if(!variablesInit(&(state->vars)) || !memoryInit(&(state->mem)) || !locksInit(&(state->lcks)) || !dataInit(&(state->working))) return R_NOMEM; /* init replaces */ state->replaces = NULL; return true; } /* vmFree: -------------------------------------------------------------- * Undo initialization */ void vmFree(r_stream* stream) { struct internal_state* state = stream->state; ASSERT(stream->state); while(state->replaces) state->replaces = replacementPop(state->replaces); variablesFree(&(state->vars)); memoryFree(&(state->mem)); locksFree(&(state->lcks)); dataFree(&(state->working)); } /* vmClean: ------------------------------------------------------------- * Prepare a VM for a totally new replacement operation */ void vmClean(r_stream* stream) { struct internal_state* state = stream->state; ASSERT(stream->state); while(state->replaces) state->replaces = replacementPop(state->replaces); variablesClearAll(&(state->vars)); memoryClearAll(&(state->mem)); locksClearAll(&(state->lcks)); dataClearAll(&(state->working)); stream->state->offset = 0; } /* rvalue: -------------------------------------------------------------- * Return a value to be used on the right (value) side of an expression */ #ifdef USE_STACK_VARS uint rvalue(vmop_t* ops, struct internal_state* state, memory* mem, memory* stackMem) #else uint rvalue(vmop_t* ops, struct internal_state* state, memory* mem) #endif { switch(ARG_TYPE(*ops)) { case ARG_VAL_TYPE: return ARG_GET_VALUE(*((uint*)ops)); case ARG_REG_TYPE: return REGISTER(*ops); case ARG_MEM_TYPE: return *(memoryValue(mem, ARG_GET_MEMORY(*((uint*)ops)))); case ARG_STACK_TYPE: #ifdef USE_STACK_VARS return *(memoryValue(stackMem, ARG_GET_STACK(*((uint*)ops)))); #else return *(memoryValue(mem, ARG_GET_STACK(*((uint*)ops)))); #endif default: ASSERT(false); return 0; } } /* lvalue: ----------------------------------------------------------------- * Return a value to be used on the left side (assigned) of an expression */ #ifdef USE_STACK_VARS static uint* lvalue(vmop_t* ops, struct internal_state* state, memory* mem, memory* stackMem) #else static uint* lvalue(vmop_t* ops, struct internal_state* state, memory* mem) #endif { switch(ARG_TYPE(*ops)) { case ARG_VAL_TYPE: ASSERT(false && "Can't put a value on left side."); return 0; case ARG_REG_TYPE: return &(REGISTER(*ops)); case ARG_MEM_TYPE: return memoryValue(mem, ARG_GET_MEMORY(*((uint*)ops))); case ARG_STACK_TYPE: #ifdef USE_STACK_VARS return memoryValue(stackMem, ARG_GET_STACK(*((uint*)ops))); #else return memoryValue(mem, ARG_GET_MEMORY(*((uint*)ops))); #endif default: ASSERT(false); return 0; } } /* vmExecute: ------------------------------------------------------------- * The main VM run loop */ int vmExecute(r_stream* stream, r_script* script) { #ifdef USE_STACK_VARS #define RVALUE(ops) rvalue(ops, state, &(state->mem), stackVars) #define LVALUE(ops) lvalue(ops, state, &(state->mem), stackVars) #else #define RVALUE(ops) rvalue(ops, state, &(state->mem)) #define LVALUE(ops) lvalue(ops, state, &(state->mem)) #endif #define PUSH_STACK(top, v) ((top)++[0] = (v)) #define POP_STACK(top) ((--top)[0]) /* Jump to cleanup label instead of return */ #define RETURN(r) \ do { \ retval = r; \ goto cleanup; \ } while (0) struct internal_state* state; int retval = R_OK; vmop_t* ops; uint passes = 0; /* These are the registers passed to PCRE */ int pcreregs[MAX_REGS * 3]; /* And over here we have the stack */ uint* vmStack = NULL; size_t allocStack = 0; uint** stack; #define STACK *stack #ifdef USE_STACK_VARS memory* stackVars; #endif /* The text buffer */ char* text = NULL; /* We just setup some vars for easy access to structs */ state = stream->state; ops = script->ops; zero(state->vmregs); ASSERT(script->ops); stack = (uint**)&(state->vmregs[r_sp >> 2]); /* Set the initial limits in the x1 and y1 registers */ REGISTER(r_x1) = REL_TO_ABS(0, state); REGISTER(r_y1) = REL_TO_ABS(stream->availIn, state); /* Stack variables */ #ifdef USE_STACK_VARS stackVars = (memory*)malloc(sizeof(memory)); if(!stackVars || !memoryInit(stackVars)) RETURN(R_NOMEM); #endif /* Preallocate some memory for the text buffer */ text = malloc(sizeof(char) * 256); if(!text) RETURN(R_NOMEM); text[0] = 0; while(1) { vmop_t op = *ops; ops++; /* The text buffer should always be pointing to a valid block of memory */ ASSERT(text != NULL); /* Check and see if we have enough stack and allocate if not */ if((STACK + 0x010) > (vmStack + allocStack)) { size_t off = STACK - vmStack; vmStack = (uint*)reallocf(vmStack, (allocStack + 0x080) * sizeof(uint)); if(!vmStack) RETURN(R_NOMEM); allocStack += 0x080; STACK = vmStack + off; } /* Main switch which dispatches the ops */ switch(op) { /* end: Finished executing script (but can come back again for more) */ case o_end: RETURN(R_OK); /* nop: Do nothing */ case o_nop: break; /* push: Push a value on the stack */ case o_push: PUSH_STACK(STACK, RVALUE(ops)); INC_ARGUMENT(ops); break; /* pop: Pop a value from the stack */ case o_pop: *(LVALUE(ops)) = POP_STACK(STACK); INC_ARGUMENT(ops); break; /* lock: Lock the area between the selected area */ case o_lock: { uint beg = RVALUE(ops); INC_ARGUMENT(ops); if(!locksAdd(&(state->lcks), beg, RVALUE(ops))) RETURN(R_NOMEM); INC_ARGUMENT(ops); /* Locking is an action too */ REGISTER(r_ac) = 1; } break; /* check: Check the selected area against any locks */ case o_check: { uint beg = RVALUE(ops); INC_ARGUMENT(ops); /* Check the lowest registers against the locks it against the locks */ REGISTER(r_fe) = locksCheck(&(state->lcks), stream, beg, RVALUE(ops)) ? 0 : 1; INC_ARGUMENT(ops); } break; /* match: Match a pattern buffer */ case o_match: { uint beg, end, begLimit, endLimit, begMatch, endMatch; match_op* header; pcre* re = NULL; pcre_extra* extra = NULL; bool cache = false; size_t i = 0; int rt; beg = ABS_TO_REL(RVALUE(ops), state); INC_ARGUMENT(ops); end = ABS_TO_REL(RVALUE(ops), state); INC_ARGUMENT(ops); begLimit = ABS_TO_REL(REGISTER(r_x1), state); endLimit = ABS_TO_REL(REGISTER(r_y1), state); /* Determine what kind of a match we're talking about here */ header = (match_op*)ops; /* This is the only type of regular expression we support at the moment */ ASSERT(header->type & kMatchPcre); /* * Check if we've already cached the compiled * regular expression. * - We use the op header pointer as key to the pcre struct * - And the pcre struct pointer as the key to the pcre_extra struct */ if((re = (pcre*)dataGetValue(&(state->working), header))) { /* This prevents the freeing of stuff below */ cache = true; /* Get out the pcre_extra if present */ extra = (pcre_extra*)dataGetValue(&(state->working), re); } /* Otherwise we compile it */ else { match_op_pcre* pcreop = (match_op_pcre*)header; char* pattern = strdup(pcreop->pattern); int erroroffset; const char* error = NULL; /* If there's variables then don't cache and ... */ if(!(cache = !variablesHasVars(pattern))) { /* ... do variable substitution */ rt = variablesSubstitute(&(state->vars), stream, script, &pattern, true); if(rt < 0) RETURN(rt); } /* Compile the pattern */ re = pcre_compile(pattern, pcreop->options, &error, &erroroffset, NULL); if(!re) { if(error) scriptSetError(script, error); RETURN(R_REGEXP); } /* * If there's no variables in the regular expression * then we can cache the compiled pcre and study it */ if(cache) { if(!dataSetValue(&(state->working), header, re)) RETURN(R_NOMEM); extra = pcre_study(re, 0, &error); if(error) { scriptSetError(script, error); RETURN(R_REGEXP); } if(extra && !dataSetValue(&(state->working), re, extra)) RETURN(R_NOMEM); } free(pattern); } begMatch = beg; endMatch = end; /* Set failed flag */ REGISTER(r_fe) = 0; while(i < locksSize(&(state->lcks)) && locksEnd(&(state->lcks), i) <= REL_TO_ABS(beg, state)) i++; do { if(i < locksSize(&(state->lcks))) endMatch = ABS_TO_REL(locksBeg(&(state->lcks), i), state); else if(i >= locksSize(&(state->lcks))) endMatch = end; /* If that put it too high then bring back */ if(endMatch > end) endMatch = end; /* If we haven't overstepped the bounds, search */ if(begMatch <= endMatch) { int opts = 0; /* * We have to do a little trickery here * First we fool pcre_exec into thinking that our range start * is the beginning of the string. We fix up the registers later * * Then we fool'm into thinking that the our search end is the * end of the string, but if it's really not the end of the * range then set not_eol so that '$' doesn't match. */ if(endMatch != endLimit) opts |= PCRE_NOTEOL; /* Do actual search */ /* TODO: Get this ready for binary replacements */ rt = pcre_exec(re, extra, ((char*)stream->nextIn + begLimit), /* Data */ endMatch - begLimit, /* size */ begMatch - begLimit, /* position */ opts, /* options */ pcreregs, /* group registers */ MAX_REGS * 3); /* number of registers */ /* These are programmer errors */ ASSERT(rt != PCRE_ERROR_NULL); ASSERT(rt != PCRE_ERROR_BADOPTION); ASSERT(rt != PCRE_ERROR_BADMAGIC); ASSERT(rt != PCRE_ERROR_UNKNOWN_NODE); ASSERT(rt != PCRE_ERROR_NOSUBSTRING); if(rt == PCRE_ERROR_NOMEMORY) RETURN(R_NOMEM); if(rt >= 0) { /* Found a match! */ /* Do group register maintainance */ REGISTER(r_cg) = rt; regsSet(state, pcreregs, REL_TO_ABS(begLimit, state)); /* Set succeeded flag */ REGISTER(r_fe) = 1; /* Get out of here */ break; } /* Make sure we've got all the errors */ else if(rt != -1) ASSERT(0); } /* The beginning of the next search block should be the end of the current lock */ if(i < locksSize(&(state->lcks))) begMatch = ABS_TO_REL(locksEnd(&(state->lcks), i), state); i++; } while(endMatch < end); if(!cache) { if(re) free(re); if(extra) free(extra); } ops += match_op_size(*header); } break; /* setvar: Add selected text to a variable */ case o_setvar: { var_op* pOp = (var_op*)ops; /* TODO: Get this ready for binary replacements */ if(!variablesAddBytes(&(state->vars), (char*)pOp->name, text, strlen(text))) RETURN(R_NOMEM); ops += var_op_size(*pOp); } break; /* setvar: clear a variable */ case o_clrvar: { var_op* pOp = (var_op*)ops; /* TODO: Get this ready for binary variables */ variablesClear(&(state->vars), (char*)pOp->name); ops += var_op_size(*pOp); } break; case o_je: case o_jne: case o_jmp: /* Get the conditional jumps out of the way */ if((op == o_je && REGISTER(r_fe) == 0) || (op == o_jne && REGISTER(r_fe) > 0)) { INC_ARGUMENT(ops); break; } else { /* Now do the jump */ vmop_t* opOld = ops; /* Change the op to the indicated pos */ ops = script->ops + RVALUE(ops); /* * If a backwards jump then count as a "pass" thingy * we use this count to catch endless loop errors */ /* TODO: I don't think we need this anymore */ if(opOld > ops) { passes++; if(passes > MAX_PASSES) return R_LOOP; } } break; /* repl: Replace last match with given text */ case o_repl: { uint beg, end; replacement* rep = NULL; int rt; beg = RVALUE(ops); INC_ARGUMENT(ops); end = RVALUE(ops); INC_ARGUMENT(ops); /* Get the formatted replacement */ rt = replacementAlloc(stream, text, &rep); if(rt < 0) RETURN(rt); rep->beg = beg; rep->end = end; /* If we have a confirm function then ask about replacement */ if(stream->fMatch) { r_replace repl; repl.from = ABS_TO_REL(beg, state); repl.flen = end - beg; repl.to = (byte*)rep->text; repl.tlen = strlen(rep->text); repl.offset = beg; if(!(stream->fMatch)(stream, &repl)) { free(rep); rep = NULL; } } if(rep) { /* Add it! */ REGISTER(r_ac) = 1; stream->total++; replacementAdd(rep, stream); } } break; /* stop: Stop execution of script */ case o_stop: { uint error = RVALUE(ops); INC_ARGUMENT(ops); scriptSetError(script, text); if(error == 0) RETURN(R_DONE); else RETURN(R_USER); } break; /* cmp: compare two values and set registers accordingly */ case o_cmp: { uint arg1, arg2; arg1 = RVALUE(ops); INC_ARGUMENT(ops); arg2 = RVALUE(ops); INC_ARGUMENT(ops); REGISTER(r_fe) = (arg1 == arg2) ? 1 : 0; REGISTER(r_fg) = (arg1 > arg2) ? 1 : 0; REGISTER(r_fl) = (arg1 < arg2) ? 1 : 0; } break; /* test: Test whether value is 0 or not */ case o_test: REGISTER(r_fe) = RVALUE(ops) != 0; INC_ARGUMENT(ops); break; /* mov: Set one value to another */ case o_mov: { uint* dest = LVALUE(ops); INC_ARGUMENT(ops); *dest = RVALUE(ops); INC_ARGUMENT(ops); } break; /* call: Call a function */ case o_call: { #ifdef USE_STACK_VARS memory* newStack; #endif uint pos = RVALUE(ops); INC_ARGUMENT(ops); /* Push current instruction pointer on the stack */ PUSH_STACK(STACK, ops - script->ops); /* Change instruction pointer to function start */ ops = script->ops + pos; #ifdef USE_STACK_VARS /* Push new set of stack variables */ newStack = (memory*)malloc(sizeof(memory)); if(!newStack || !memoryInit(newStack)) RETURN(R_NOMEM); newStack->prev = stackVars; stackVars = newStack; #endif } break; /* ret: Return from a function */ case o_ret: { #ifdef USE_STACK_VARS memory* prev; #endif /* Get location to return to */ uint pos = POP_STACK(STACK); /* Change instruction pointer to returned location */ ops = script->ops + pos; #ifdef USE_STACK_VARS /* Get previous set of stack vars */ ASSERT(stackVars->prev); prev = stackVars->prev; memoryFree(stackVars); free(stackVars); stackVars = prev; #endif } break; /* add: Add 2 values */ case o_add: { /* Get first value */ uint* val = LVALUE(ops); INC_ARGUMENT(ops); *val += RVALUE(ops); INC_ARGUMENT(ops); } break; /* sub: Subtract values */ case o_sub: { /* Get first value */ uint* val = LVALUE(ops); INC_ARGUMENT(ops); *val -= RVALUE(ops); INC_ARGUMENT(ops); } break; /* text: set the text buffer */ case o_text: { text_op* top = (text_op*)ops; int rt; text = (char*)reallocf(text, top->len + 1); if(!text) RETURN(R_NOMEM); memcpy(text, top->string, top->len * sizeof(char)); text[top->len] = 0; rt = variablesSubstitute(&(state->vars), stream, script, &text, false); if(rt < 0) RETURN(rt); ops += text_op_size(*top); } break; /* msg: output text in buffer */ case o_msg: { if(stream->fMessage) (stream->fMessage)(stream, text); } break; default: /* Invalid Instruction! */ ASSERT(false); } } cleanup: if(vmStack) free(vmStack); if(text) free(text); while(stackVars) { memory* prev = stackVars->prev; memoryFree(stackVars); free(stackVars); stackVars = prev; } return retval; }