Browse Source

Merge commit 'origin/andrei/switch'

* commit 'origin/andrei/switch':
  script parsing: while support
  script engine: while() support
  script engine: switch() and break execution
  script engine: switch() fixup and optimizations
  script parsing: C style switch() & case support
  expr engine: minor additions
Andrei Pelinescu-Onciul 16 years ago
parent
commit
bc7640b236
13 changed files with 858 additions and 17 deletions
  1. 4 0
      NEWS
  2. 129 4
      action.c
  3. 11 0
      cfg.lex
  4. 173 10
      cfg.y
  5. 3 0
      cfg_core.c
  6. 1 0
      cfg_core.h
  7. 3 0
      config.h
  8. 77 1
      route.c
  9. 7 2
      route_struct.h
  10. 11 0
      rvalue.c
  11. 4 0
      rvalue.h
  12. 374 0
      switch.c
  13. 61 0
      switch.h

+ 4 - 0
NEWS

@@ -17,6 +17,10 @@ config script changes:
       #!MAXCOMPAT
     where #!KAMAILIO is equivalent with #!OPENSER and #!ALL with #!MAXCOMPAT
   - support for kamailio style pvars
+  - C-like switch()/case (integer only)
+  - while()
+  - max_while_loops - maximum iterations allowed for a while, can be changed
+    at runtime. Default 100.
 
 
 

+ 129 - 4
action.c

@@ -77,6 +77,7 @@
 #ifdef USE_SCTP
 #include "sctp_server.h"
 #endif
+#include "switch.h"
 
 #include <sys/types.h>
 #include <sys/socket.h>
@@ -111,6 +112,11 @@ int do_action(struct run_act_ctx* h, struct action* a, struct sip_msg* msg)
 	struct sip_uri *u;
 	unsigned short port;
 	str* dst_host;
+	int i, flags;
+	struct switch_cond_table* sct;
+	struct switch_jmp_table*  sjt;
+	struct rval_expr* rve;
+
 
 	/* reset the value of error to E_UNSPEC so avoid unknowledgable
 	   functions to return with error (status<0) and not setting it
@@ -448,7 +454,7 @@ int do_action(struct run_act_ctx* h, struct action* a, struct sip_msg* msg)
 			/*ret=((ret=run_actions(rlist[a->val[0].u.number], msg))<0)?ret:1;*/
 			ret=run_actions(h, main_rt.rlist[a->val[0].u.number], msg);
 			h->last_retcode=ret;
-			h->run_flags&=~RETURN_R_F; /* absorb returns */
+			h->run_flags&=~(RETURN_R_F|BREAK_R_F); /* absorb return & break */
 			break;
 		case EXEC_T:
 			if (a->val[0].type!=STRING_ST){
@@ -704,7 +710,8 @@ int do_action(struct run_act_ctx* h, struct action* a, struct sip_msg* msg)
 						ret=0;
 						break;
 					}
-					h->run_flags &= ~RETURN_R_F; /* catch returns in expr */
+					h->run_flags &= ~(RETURN_R_F|BREAK_R_F); /* catch return &
+															    break in expr*/
 					ret=1;  /*default is continue */
 					if (v>0) {
 						if ((a->val[1].type==ACTIONS_ST)&&a->val[1].u.data){
@@ -808,6 +815,122 @@ int do_action(struct run_act_ctx* h, struct action* a, struct sip_msg* msg)
 				LOG(L_CRIT,"BUG: do_action: bad module call\n");
 			}
 			break;
+		case EVAL_T:
+			/* only eval the expression to account for possible
+			   side-effect */
+			rval_expr_eval_int(h, msg, &v,
+					(struct rval_expr*)a->val[0].u.data);
+			if (h->run_flags & EXIT_R_F){
+				ret=0;
+				break;
+			}
+			h->run_flags &= ~RETURN_R_F|BREAK_R_F; /* catch return & break in
+													  expr */
+			ret=1; /* default is continue */
+			break;
+		case SWITCH_COND_T:
+			sct=(struct switch_cond_table*)a->val[1].u.data;
+			if (unlikely( rval_expr_eval_int(h, msg, &v,
+									(struct rval_expr*)a->val[0].u.data) <0)){
+				/* handle error in expression => use default */
+				ret=-1;
+				goto sw_cond_def;
+			}
+			if (h->run_flags & EXIT_R_F){
+				ret=0;
+				break;
+			}
+			h->run_flags &= ~(RETURN_R_F|BREAK_R_F); /* catch return & break
+													    in expr */
+			ret=1; /* default is continue */
+			for(i=0; i<sct->n; i++)
+				if (sct->cond[i]==v){
+					if (likely(sct->jump[i])){
+						ret=run_actions(h, sct->jump[i], msg);
+						h->run_flags &= ~BREAK_R_F; /* catch breaks, but let
+													   returns passthrough */
+					}
+					goto skip;
+				}
+sw_cond_def:
+			if (sct->def){
+				ret=run_actions(h, sct->def, msg);
+				h->run_flags &= ~BREAK_R_F; /* catch breaks, but let
+											   returns passthrough */
+			}
+			break;
+		case SWITCH_JT_T:
+			sjt=(struct switch_jmp_table*)a->val[1].u.data;
+			if (unlikely( rval_expr_eval_int(h, msg, &v,
+									(struct rval_expr*)a->val[0].u.data) <0)){
+				/* handle error in expression => use default */
+				ret=-1;
+				goto sw_jt_def;
+			}
+			if (h->run_flags & EXIT_R_F){
+				ret=0;
+				break;
+			}
+			h->run_flags &= ~(RETURN_R_F|BREAK_R_F); /* catch return & break
+													    in expr */
+			ret=1; /* default is continue */
+			if (likely(v >= sjt->first && v <= sjt->last)){
+				if (likely(sjt->tbl[v - sjt->first])){
+					ret=run_actions(h, sjt->tbl[v - sjt->first], msg);
+					h->run_flags &= ~BREAK_R_F; /* catch breaks, but let
+												   returns passthrough */
+				}
+				break; 
+			}else{
+				for(i=0; i<sjt->rest.n; i++)
+					if (sjt->rest.cond[i]==v){
+						if (likely(sjt->rest.jump[i])){
+							ret=run_actions(h, sjt->rest.jump[i], msg);
+							h->run_flags &= ~BREAK_R_F; /* catch breaks, but 
+														   let returns pass */
+						}
+						goto skip;
+					}
+			}
+			/* not found => try default */
+sw_jt_def:
+			if (sjt->rest.def){
+				ret=run_actions(h, sjt->rest.def, msg);
+				h->run_flags &= ~BREAK_R_F; /* catch breaks, but let
+											   returns passthrough */
+			}
+			break;
+		case BLOCK_T:
+			if (likely(a->val[0].u.data)){
+				ret=run_actions(h, (struct action*)a->val[0].u.data, msg);
+				h->run_flags &= ~BREAK_R_F; /* catch breaks, but let
+											   returns passthrough */
+			}
+			break;
+		case WHILE_T:
+			i=0;
+			flags=0;
+			rve=(struct rval_expr*)a->val[0].u.data;
+			ret=1;
+			while(!(flags & BREAK_R_F) && 
+					(rval_expr_eval_int(h, msg, &v, rve) == 0) && v){
+				i++;
+				if (unlikely(i > cfg_get(core, core_cfg, max_while_loops))){
+					LOG(L_ERR, "ERROR: runaway while (%d, %d): more then"
+								" %d loops\n", 
+								rve->fpos.s_line, rve->fpos.s_col,
+								cfg_get(core, core_cfg, max_while_loops));
+					ret=-1;
+					break;
+				}
+				if (likely(a->val[1].u.data)){
+					ret=run_actions(h, (struct action*)a->val[1].u.data, msg);
+					flags|=h->run_flags;
+					h->run_flags &= ~BREAK_R_F; /* catch breaks, but let
+												   returns passthrough */
+				}
+			}
+			break;
 		case FORCE_RPORT_T:
 			msg->msg_flags|=FL_FORCE_RPORT;
 			ret=1; /* continue processing */
@@ -885,7 +1008,7 @@ int do_action(struct run_act_ctx* h, struct action* a, struct sip_msg* msg)
 		default:
 			LOG(L_CRIT, "BUG: do_action: unknown type %d\n", a->type);
 	}
-/*skip:*/
+skip:
 	return ret;
 
 error_uri:
@@ -935,7 +1058,9 @@ int run_actions(struct run_act_ctx* h, struct action* a, struct sip_msg* msg)
 
 	for (t=a; t!=0; t=t->next){
 		ret=do_action(h, t, msg);
-		if (h->run_flags & (RETURN_R_F|EXIT_R_F)){
+		/* break, return or drop/exit stop execution of the current
+		   block */
+		if (h->run_flags & (BREAK_R_F|RETURN_R_F|EXIT_R_F)){
 			if (h->run_flags & EXIT_R_F){
 #ifdef USE_LONGJMP
 				h->last_retcode=ret;

+ 11 - 0
cfg.lex

@@ -187,6 +187,10 @@ ELSE			"else"
 SET_ADV_ADDRESS	"set_advertised_address"
 SET_ADV_PORT	"set_advertised_port"
 FORCE_SEND_SOCKET	"force_send_socket"
+SWITCH			"switch"
+CASE			"case"
+DEFAULT			"default"
+WHILE			"while"
 
 /*ACTION LVALUES*/
 URIHOST			"uri:host"
@@ -372,6 +376,7 @@ MCAST_TTL		"mcast_ttl"
 TOS			"tos"
 PMTU_DISCOVERY	"pmtu_discovery"
 KILL_TIMEOUT	"exit_timeout"|"ser_kill_timeout"
+MAX_WLOOPS		"max_while_loops"
 
 /* stun config variables */
 STUN_REFRESH_INTERVAL "stun_refresh_interval"
@@ -495,6 +500,10 @@ EAT_ABLE	[\ \t\b\r]
 										return SET_ADV_PORT; }
 <INITIAL>{FORCE_SEND_SOCKET}	{	count(); yylval.strval=yytext;
 									return FORCE_SEND_SOCKET; }
+<INITIAL>{SWITCH}	{ count(); yylval.strval=yytext; return SWITCH; }
+<INITIAL>{CASE}	{ count(); yylval.strval=yytext; return CASE; }
+<INITIAL>{DEFAULT}	{ count(); yylval.strval=yytext; return DEFAULT; }
+<INITIAL>{WHILE}	{ count(); yylval.strval=yytext; return WHILE; }
 
 <INITIAL>{URIHOST}	{ count(); yylval.strval=yytext; return URIHOST; }
 <INITIAL>{URIPORT}	{ count(); yylval.strval=yytext; return URIPORT; }
@@ -710,6 +719,8 @@ EAT_ABLE	[\ \t\b\r]
 									return PMTU_DISCOVERY; }
 <INITIAL>{KILL_TIMEOUT}			{	count(); yylval.strval=yytext;
 									return KILL_TIMEOUT; }
+<INITIAL>{MAX_WLOOPS}			{	count(); yylval.strval=yytext;
+									return MAX_WLOOPS; }
 <INITIAL>{SERVER_ID}  { count(); yylval.strval=yytext; return SERVER_ID;}
 <INITIAL>{CFG_DESCRIPTION}	{ count(); yylval.strval=yytext; return CFG_DESCRIPTION; }
 <INITIAL>{LOADMODULE}	{ count(); yylval.strval=yytext; return LOADMODULE; }

+ 173 - 10
cfg.y

@@ -93,6 +93,7 @@
  *               lval=rval_expr, where lval=avp|pvar  (andrei)
  * 2007-12-06  expression are now evaluated in terms of rvalues;
  *             NUMBER is now always positive; cleanup (andrei)
+ * 2009-01-26  case/switch() support (andrei)
 */
 
 %{
@@ -109,6 +110,7 @@
 #include "route_struct.h"
 #include "globals.h"
 #include "route.h"
+#include "switch.h"
 #include "dprint.h"
 #include "sr_module.h"
 #include "modparam.h"
@@ -182,6 +184,7 @@
 
 extern int yylex();
 static void yyerror(char* s, ...);
+static void yyerror_at(struct cfg_pos* pos, char* s, ...);
 static char* tmp;
 static int i_tmp;
 static unsigned u_tmp;
@@ -199,7 +202,8 @@ static struct action *mod_func_action;
 static struct lvalue* lval_tmp;
 static struct rvalue* rval_tmp;
 
-static void warn(char* s);
+static void warn(char* s, ...);
+static void warn_at(struct cfg_pos* pos, char* s, ...);
 static void get_cpos(struct cfg_pos* pos);
 static struct rval_expr* mk_rve_rval(enum rval_type, void* v);
 static struct rval_expr* mk_rve1(enum rval_expr_op op, struct rval_expr* rve1);
@@ -211,6 +215,8 @@ static struct socket_id* mk_listen_id2(struct name_lst*, int, int);
 static void free_name_lst(struct name_lst* lst);
 static void free_socket_id_lst(struct socket_id* i);
 
+static struct case_stms* mk_case_stm(struct rval_expr* ct, struct action* a);
+
 %}
 
 %union {
@@ -219,6 +225,7 @@ static void free_socket_id_lst(struct socket_id* i);
 	char* strval;
 	struct expr* expr;
 	struct action* action;
+	struct case_stms* case_stms;
 	struct net* ipnet;
 	struct ip_addr* ipaddr;
 	struct socket_id* sockid;
@@ -272,6 +279,10 @@ static void free_socket_id_lst(struct socket_id* i);
 %token SET_ADV_ADDRESS
 %token SET_ADV_PORT
 %token FORCE_SEND_SOCKET
+%token SWITCH
+%token CASE
+%token DEFAULT
+%token WHILE
 %token URIHOST
 %token URIPORT
 %token MAX_LEN
@@ -428,6 +439,7 @@ static void free_socket_id_lst(struct socket_id* i);
 %token TOS
 %token PMTU_DISCOVERY
 %token KILL_TIMEOUT
+%token MAX_WLOOPS
 %token CFG_DESCRIPTION
 %token SERVER_ID
 
@@ -493,6 +505,8 @@ static void free_socket_id_lst(struct socket_id* i);
 %type <intval> intno eint_op eint_op_onsend
 %type <intval> eip_op eip_op_onsend
 %type <action> action actions cmd fcmd if_cmd stm /*exp_stm*/ assign_action
+%type <action> switch_cmd while_cmd
+%type <case_stms> single_case case_stms
 %type <ipaddr> ipv4 ipv6 ipv6addr ip
 %type <ipnet> ipnet
 %type <strval> host
@@ -514,7 +528,7 @@ static void free_socket_id_lst(struct socket_id* i);
 %type <attr> attr_id_any_str
 %type <pvar> pvar
 %type <lval> lval
-%type <rv_expr> rval rval_expr 
+%type <rv_expr> rval rval_expr ct_rval
 %type <lval> avp_pvar
 /* %type <intval> class_id */
 %type <intval> assign_op
@@ -1273,6 +1287,8 @@ assign_stm:
 	| PMTU_DISCOVERY error { yyerror("number expected"); }
 	| KILL_TIMEOUT EQUAL NUMBER { ser_kill_timeout=$3; }
 	| KILL_TIMEOUT EQUAL error { yyerror("number expected"); }
+	| MAX_WLOOPS EQUAL NUMBER { default_core_cfg.max_while_loops=$3; }
+	| MAX_WLOOPS EQUAL error { yyerror("number expected"); }
 	| STUN_REFRESH_INTERVAL EQUAL NUMBER { IF_STUN(stun_refresh_interval=$3); }
 	| STUN_REFRESH_INTERVAL EQUAL error{ yyerror("number expected"); }
 	| STUN_ALLOW_STUN EQUAL NUMBER { IF_STUN(stun_allow_stun=$3); }
@@ -1762,6 +1778,8 @@ actions:
 action:
 	fcmd SEMICOLON {$$=$1;}
 	| if_cmd {$$=$1;}
+	| switch_cmd {$$=$1;}
+	| while_cmd { $$=$1; }
 	| assign_action SEMICOLON {$$=$1;}
 	| SEMICOLON /* null action */ {$$=0;}
 	| fcmd error { $$=0; yyerror("bad command: missing ';'?"); }
@@ -1770,6 +1788,109 @@ if_cmd:
 	IF exp stm		{ $$=mk_action( IF_T, 3, EXPR_ST, $2, ACTIONS_ST, $3, NOSUBTYPE, 0); }
 	| IF exp stm ELSE stm	{ $$=mk_action( IF_T, 3, EXPR_ST, $2, ACTIONS_ST, $3, ACTIONS_ST, $5); }
 	;
+
+ct_rval: rval_expr {
+			$$=0;
+			if (!rve_is_constant($1)){
+				yyerror("constant expected");
+			}else if (!rve_check_type((enum rval_type*)&i_tmp, $1, 0, 0 ,0)){
+				yyerror("invalid expression (bad type)");
+			}else if (i_tmp!=RV_INT){
+				yyerror("invalid expression type, int expected\n");
+			}else
+				$$=$1;
+		}
+;
+single_case:
+	CASE ct_rval COLON actions {
+		$$=0;
+		if ($2==0) yyerror ("bad case label");
+		else if (($$=mk_case_stm($2, $4))==0){
+				yyerror("internal error: memory allocation failure");
+				YYABORT;
+		}
+	}
+	| CASE ct_rval COLON {
+		$$=0;
+		if ($2==0) yyerror ("bad case label");
+		else if (($$=mk_case_stm($2, 0))==0){
+				yyerror("internal error: memory allocation failure");
+				YYABORT;
+		}
+	}
+	| DEFAULT COLON actions {
+		if (($$=mk_case_stm(0, $3))==0){
+				yyerror("internal error: memory allocation failure");
+				YYABORT;
+		}
+	}
+	| DEFAULT COLON {
+		if (($$=mk_case_stm(0, 0))==0){
+				yyerror("internal error: memory allocation failure");
+				YYABORT;
+		}
+	}
+	| CASE error { $$=0; yyerror("bad case label"); }
+	| CASE ct_rval COLON error { $$=0; yyerror ("bad case body"); }
+;
+case_stms:
+	case_stms single_case {
+		$$=$1;
+		if ($2==0) yyerror ("bad case");
+		if ($$){
+			*($$->append)=$2;
+			if (*($$->append)!=0)
+				$$->append=&((*($$->append))->next);
+		}
+	}
+	| single_case {
+		$$=$1;
+		if ($1==0) yyerror ("bad case");
+		else $$->append=&($$->next);
+	}
+;
+switch_cmd:
+	  SWITCH rval_expr LBRACE case_stms RBRACE { 
+		$$=0;
+		if ($2==0) yyerror("bad expression in switch(...)");
+		else if ($4==0) yyerror ("bad switch body");
+		else{
+			$$=mk_action(SWITCH_T, 2, RVE_ST, $2, CASE_ST, $4);
+			if ($$==0) {
+				yyerror("internal error");
+				YYABORT;
+			}
+		}
+	}
+	| SWITCH rval_expr LBRACE RBRACE {
+		$$=0;
+		warn("empty switch()");
+		if ($2==0) yyerror("bad expression in switch(...)");
+		else{
+			/* it might have sideffects, so leave it for the optimizer */
+			$$=mk_action(SWITCH_T, 2, RVE_ST, $2, CASE_ST, 0);
+			if ($$==0) {
+				yyerror("internal error");
+				YYABORT;
+			}
+		}
+	}
+	| SWITCH error { $$=0; yyerror ("bad expression in switch(...)"); }
+	| SWITCH rval_expr LBRACE error RBRACE 
+		{$$=0; yyerror ("bad switch body"); }
+;
+
+while_cmd:
+	WHILE rval_expr stm {
+		if ($2){
+			if (rve_is_constant($2))
+				warn_at(&$2->fpos, "constant value in while(...)");
+		}else
+			yyerror_at(&$2->fpos, "bad while(...) expression");
+		$$=mk_action( WHILE_T, 2, RVE_ST, $2, ACTIONS_ST, $3);
+	}
+;
+
 /* class_id:
 	LBRACK ATTR_USER RBRACK { $$ = AVP_CLASS_USER; }
 	| LBRACK ATTR_DOMAIN RBRACK { $$ = AVP_CLASS_DOMAIN; }
@@ -2307,7 +2428,7 @@ cmd:
 	| RETURN			{$$=mk_action(DROP_T, 2, NUMBER_ST, (void*)1, NUMBER_ST, (void*)RETURN_R_F); }
 	| RETURN NUMBER			{$$=mk_action(DROP_T, 2, NUMBER_ST, (void*)$2, NUMBER_ST, (void*)RETURN_R_F);}
 	| RETURN RETCODE		{$$=mk_action(DROP_T, 2, RETCODE_ST, 0, NUMBER_ST, (void*)RETURN_R_F);}
-	| BREAK				{$$=mk_action(DROP_T, 2, NUMBER_ST, 0, NUMBER_ST, (void*)RETURN_R_F); }
+	| BREAK				{$$=mk_action(DROP_T, 2, NUMBER_ST, 0, NUMBER_ST, (void*)BREAK_R_F); }
 	| LOG_TOK LPAREN STRING RPAREN	{$$=mk_action(LOG_T, 2, NUMBER_ST, (void*)4, STRING_ST, $3); }
 	| LOG_TOK LPAREN NUMBER COMMA STRING RPAREN	{$$=mk_action(LOG_T, 2, NUMBER_ST, (void*)$3, STRING_ST, $5); }
 	| LOG_TOK error 		{ $$=0; yyerror("missing '(' or ')' ?"); }
@@ -2562,19 +2683,28 @@ static void get_cpos(struct cfg_pos* pos)
 }
 
 
-static void warn(char* s)
+static void warn_at(struct cfg_pos* p, char* format, ...)
 {
-	if (line!=startline)
+	va_list ap;
+	char s[256];
+	
+	va_start(ap, format);
+	vsnprintf(s, sizeof(s), format, ap);
+	va_end(ap);
+	if (p->e_line!=p->s_line)
 		LOG(L_WARN, "cfg. warning: (%d,%d-%d,%d): %s\n",
-					startline, startcolumn, line, column-1, s);
-	else if (startcolumn!=(column-1))
-		LOG(L_WARN, "cfg. warning: (%d,%d-%d): %s\n", startline, startcolumn,
-					column-1, s);
+					p->s_line, p->s_col, p->e_line, p->e_col, s);
+	else if (p->s_col!=p->e_col)
+		LOG(L_WARN, "cfg. warning: (%d,%d-%d): %s\n",
+					p->s_line, p->s_col, p->e_col, s);
 	else
-		LOG(L_WARN, "cfg. warning: (%d,%d): %s\n", startline, startcolumn, s);
+		LOG(L_WARN, "cfg. warning: (%d,%d): %s\n",
+				p->s_line, p->s_col, s);
 	cfg_warnings++;
 }
 
+
+
 static void yyerror_at(struct cfg_pos* p, char* format, ...)
 {
 	va_list ap;
@@ -2596,6 +2726,22 @@ static void yyerror_at(struct cfg_pos* p, char* format, ...)
 }
 
 
+
+static void warn(char* format, ...)
+{
+	va_list ap;
+	char s[256];
+	struct cfg_pos pos;
+	
+	get_cpos(&pos);
+	va_start(ap, format);
+	vsnprintf(s, sizeof(s), format, ap);
+	va_end(ap);
+	warn_at(&pos, s);
+}
+
+
+
 static void yyerror(char* format, ...)
 {
 	va_list ap;
@@ -2763,6 +2909,23 @@ static void free_socket_id_lst(struct socket_id* lst)
 	}
 }
 
+
+static struct case_stms* mk_case_stm(struct rval_expr* ct, struct action* a)
+{
+	struct case_stms* s;
+	s=pkg_malloc(sizeof(*s));
+	if (s==0) {
+		LOG(L_CRIT,"ERROR: cfg. parser: out of memory.\n");
+	} else {
+		memset(s, 0, sizeof(*s));
+		s->ct_rve=ct;
+		s->actions=a;
+		s->next=0;
+		s->append=0;
+	}
+	return s;
+}
+
 /*
 int main(int argc, char ** argv)
 {

+ 3 - 0
cfg_core.c

@@ -88,6 +88,7 @@ struct cfg_group_core default_core_cfg = {
 #ifdef SHM_MEM
 	0, /* mem_dump_shm */
 #endif
+	DEFAULT_MAX_WHILE_LOOPS, /* max_while_loops */
 };
 
 void	*core_cfg = &default_core_cfg;
@@ -177,5 +178,7 @@ cfg_def_t core_cfg_def[] = {
 	{"mem_dump_shm",	CFG_VAR_INT,	0, 0, mem_dump_shm_fixup, 0,
 		"dump shared memory status"},
 #endif
+	{"max_while_loops",	CFG_VAR_INT|CFG_ATOMIC,	0, 0, 0, 0,
+		"maximum iterations allowed for a while loop" },
 	{0, 0, 0, 0, 0, 0}
 };

+ 1 - 0
cfg_core.h

@@ -85,6 +85,7 @@ struct cfg_group_core {
 #ifdef SHM_MEM
 	int mem_dump_shm;
 #endif
+	int max_while_loops;
 };
 
 extern struct cfg_group_core default_core_cfg;

+ 3 - 0
config.h

@@ -208,4 +208,7 @@
 
 #define DEFAULT_DID "_default"
 
+/*  maximum allowed iterations for a while (to catch runaways) */
+#define DEFAULT_MAX_WHILE_LOOPS 100
+
 #endif

+ 77 - 1
route.c

@@ -82,6 +82,7 @@
 #include "str_hash.h"
 #include "ut.h"
 #include "rvalue.h"
+#include "switch.h"
 
 #define RT_HASH_SIZE	8 /* route names hash */
 
@@ -622,6 +623,10 @@ int fix_actions(struct action* a)
 	struct ip_addr ip;
 	struct socket_info* si;
 	struct lvalue* lval;
+	struct rval_expr* rve;
+	struct rval_expr* err_rve;
+	enum rval_type rve_type, err_type, expected_type;
+
 	
 	char buf[30]; /* tmp buffer needed for module param fixups */
 
@@ -697,7 +702,78 @@ int fix_actions(struct action* a)
 						return ret;
 				}
 				break;
-
+			case SWITCH_T:
+				if (t->val[0].type!=RVE_ST){
+					LOG(L_CRIT, "BUG: fix_actions: invalid subtype"
+								"%d for switch() (should be expr)\n",
+								t->val[0].type);
+					return E_BUG;
+				}else if (t->val[1].type!=CASE_ST){
+					LOG(L_CRIT, "BUG: fix_actions: invalid subtype"
+								"%d for switch(...){...}(should be case)\n",
+								t->val[1].type);
+					return E_BUG;
+				}
+				if (t->val[0].u.data){
+					if ((ret=fix_rval_expr(&t->val[0].u.data))<0)
+						return ret;
+				}else{
+					LOG(L_CRIT, "BUG: fix_actions: null switch()"
+							" expression\n");
+					return E_BUG;
+				}
+				if ((ret=fix_switch(t))<0)
+					return ret;
+				break;
+			case WHILE_T:
+				if (t->val[0].type!=RVE_ST){
+					LOG(L_CRIT, "BUG: fix_actions: invalid subtype"
+								"%d for while() (should be expr)\n",
+								t->val[0].type);
+					return E_BUG;
+				}else if (t->val[1].type!=ACTIONS_ST){
+					LOG(L_CRIT, "BUG: fix_actions: invalid subtype"
+								"%d for while(...){...}(should be action)\n",
+								t->val[1].type);
+					return E_BUG;
+				}
+				rve=(struct rval_expr*)t->val[0].u.data;
+				if (rve){
+					err_rve=0;
+					if (!rve_check_type(&rve_type, rve, &err_rve,
+											&err_type, &expected_type)){
+						if (err_rve)
+							LOG(L_ERR, "fix_actions: invalid expression "
+									"(%d,%d): subexpression (%d,%d) has type"
+									" %s,  but %s is expected\n",
+									rve->fpos.s_line, rve->fpos.s_col,
+									err_rve->fpos.s_line, err_rve->fpos.s_col,
+									rval_type_name(err_type),
+									rval_type_name(expected_type) );
+						else
+							LOG(L_ERR, "fix_actions: invalid expression "
+									"(%d,%d): type mismatch?",
+									rve->fpos.s_line, rve->fpos.s_col);
+						return E_UNSPEC;
+					}
+					if (rve_type!=RV_INT && rve_type!=RV_NONE){
+						LOG(L_ERR, "fix_actions: invalid expression (%d,%d):"
+								" bad type, integer expected\n",
+								rve->fpos.s_line, rve->fpos.s_col);
+						return E_UNSPEC;
+					}
+					if ((ret=fix_rval_expr((void**)&rve))<0)
+						return ret;
+				}else{
+					LOG(L_CRIT, "BUG: fix_actions: null while()"
+							" expression\n");
+					return E_BUG;
+				}
+				if ( t->val[1].u.data && 
+					((ret= fix_actions((struct action*)t->val[1].u.data))<0)){
+					return ret;
+				}
+				break;
 			case ASSIGN_T:
 			case ADD_T:
 				if (t->val[0].type !=LVAL_ST) {

+ 7 - 2
route_struct.h

@@ -70,7 +70,9 @@ enum { METHOD_O=1, URI_O, FROM_URI_O, TO_URI_O, SRCIP_O, SRCPORT_O,
 enum { FORWARD_T=1, SEND_T, DROP_T, LOG_T, ERROR_T, ROUTE_T, EXEC_T,
 		SET_HOST_T, SET_HOSTPORT_T, SET_USER_T, SET_USERPASS_T,
 		SET_PORT_T, SET_URI_T, SET_HOSTPORTTRANS_T,
-		IF_T, MODULE_T, MODULE3_T, MODULE4_T, MODULE5_T, MODULE6_T, MODULEX_T,
+		IF_T, SWITCH_T /* only until fixup*/,
+		BLOCK_T, EVAL_T, SWITCH_JT_T, SWITCH_COND_T, WHILE_T,
+		MODULE_T, MODULE3_T, MODULE4_T, MODULE5_T, MODULE6_T, MODULEX_T,
 		SETFLAG_T, RESETFLAG_T, ISFLAGSET_T ,
 		AVPFLAG_OPER_T,
 		LEN_GT_T, PREFIX_T, STRIP_T,STRIP_TAIL_T,
@@ -96,11 +98,14 @@ enum { NOSUBTYPE=0, STRING_ST, NET_ST, NUMBER_ST, IP_ST, RE_ST, PROXY_ST,
 		MYSELF_ST, STR_ST, SOCKID_ST, SOCKETINFO_ST, ACTION_ST, AVP_ST,
 		SELECT_ST, PVAR_ST,
 		LVAL_ST,  RVE_ST,
-		RETCODE_ST};
+		RETCODE_ST, CASE_ST,
+		BLOCK_ST, JUMPTABLE_ST, CONDTABLE_ST
+};
 
 /* run flags */
 #define EXIT_R_F   1
 #define RETURN_R_F 2
+#define BREAK_R_F  4
 
 
 struct cfg_pos{

+ 11 - 0
rvalue.c

@@ -452,6 +452,17 @@ int rve_is_constant(struct rval_expr* rve)
 
 
 
+/** returns true if the expression has side-effects.
+  * @return  1 for possible side-effects, 0 for no side-effects
+  * TODO: add better checks
+  */
+int rve_has_side_effects(struct rval_expr* rve)
+{
+	return !rve_is_constant(rve);
+}
+
+
+
 /** returns true if operator is unary (takes only 1 arg).
   * @return 0 or 1 on
   */

+ 4 - 0
rvalue.h

@@ -188,6 +188,8 @@ int rval_expr_eval_rvint( struct run_act_ctx* h, struct sip_msg* msg,
 enum rval_type rve_guess_type(struct rval_expr* rve);
 /** returns true if expression is constant. */
 int rve_is_constant(struct rval_expr* rve);
+/** returns true if the expression can have side-effect */
+int rve_has_side_effects(struct rval_expr* rve);
 /** returns 1 if expression is valid (type-wise).*/
 int rve_check_type(enum rval_type* type, struct rval_expr* rve,
 					struct rval_expr** bad_rve, enum rval_type* bad_type,
@@ -206,6 +208,8 @@ struct rval_expr* mk_rval_expr1(enum rval_expr_op op, struct rval_expr* rve1,
 struct rval_expr* mk_rval_expr2(enum rval_expr_op op, struct rval_expr* rve1,
 													  struct rval_expr* rve2,
 													  struct cfg_pos* pos);
+/** destroys a pkg_malloc'ed rve. */
+void rve_destroy(struct rval_expr* rve);
 
 /** fix a rval_expr. */
 int fix_rval_expr(void** p);

+ 374 - 0
switch.c

@@ -0,0 +1,374 @@
+/* 
+ * $Id$
+ * 
+ * Copyright (C) 2009 iptelorg GmbH
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+/*
+ * switch.c
+ */
+/*
+ * History:
+ * --------
+ *  2009-02-02  initial version (andrei)
+*/
+
+#include "switch.h"
+#include "rvalue.h"
+#include "route.h"
+#include "mem/mem.h"
+#include "error.h"
+
+#define MAX_JT_SIZE 100  /* maximum jump table size */
+
+
+/** create a cond table structure (pkg_malloc'ed).
+ * @return 0 on error, pointer on success
+ */
+static struct switch_cond_table* mk_switch_cond_table(int n)
+{
+	struct switch_cond_table* sct;
+	
+	/* allocate everything in a single block, better for cache locality */
+	sct=pkg_malloc(ROUND_INT(sizeof(*sct))+
+					ROUND_POINTER(n*sizeof(sct->cond[0]))+
+					n*sizeof(sct->jump[0]));
+	if (sct==0) return 0;
+	sct->n=n;
+	sct->cond=(int*)((char*)sct+ROUND_INT(sizeof(*sct)));
+	sct->jump=(struct action**)
+				((char*)sct->cond+ROUND_POINTER(n*sizeof(sct->cond[0])));
+	sct->def=0;
+	return sct;
+}
+
+
+
+/** create a jump table structure (pkg_malloc'ed).
+ * @param jmp_size - size of the jump table
+ * @param rest - size of the fallback condition table
+ * @return 0 on error, pointer on success
+ */
+static struct switch_jmp_table* mk_switch_jmp_table(int jmp_size, int rest)
+{
+	struct switch_jmp_table* jt;
+	int size;
+	
+	/* alloc everything in a block */
+	size = 	ROUND_POINTER(sizeof(*jt))+
+			ROUND_INT(jmp_size*sizeof(jt->tbl[0]))+
+			ROUND_POINTER(rest*sizeof(jt->rest.cond[0]))+
+			rest*sizeof(jt->rest.jump[0]);
+	jt=pkg_malloc(size);
+	if (jt == 0) return 0;
+	memset(jt, 0, size);
+	jt->tbl = (struct action**)((char*) jt + ROUND_POINTER(sizeof(*jt)));
+	jt->rest.cond = (int*)
+					((char*) jt->tbl + ROUND_INT(jmp_size*sizeof(jt->tbl[0])));
+	jt->rest.jump = (struct action**) ((char*) jt->rest.cond + 
+								ROUND_POINTER(rest*sizeof(jt->rest.cond[0])));
+	jt->rest.n=rest;
+	return jt;
+}
+
+
+
+void destroy_case_stms(struct case_stms *lst)
+{
+	struct case_stms* l;
+	struct case_stms* n;
+	
+	for (l=lst; l; l=n){
+		n=l->next;
+		rve_destroy(l->ct_rve);
+		/* TODO: the action list is not freed (missing destroy_action() and
+		   there are some case when we need at least part of the action list
+		*/
+		pkg_free(l);
+	}
+}
+
+
+
+/** fixup function for SWITCH_T actions.
+ * can produce 4 different action types:
+ *  - BLOCK_T (actions) - actions grouped in a block, break ends the block
+ *    execution.
+ *  - EVAL_T (cond)  - null switch block, but the condition has to be
+ *                       evaluated due to possible side-effects.
+ *  - SWITCH_COND_T(cond, jumps) - condition table
+ *  - SWITCH_JT_T(cond, jumptable) - jumptable + condition table
+ * TODO: external optimizers that would "flatten" BLOCK_T w/no breaks or
+ * breaks at the end.
+ * 
+ */
+int fix_switch(struct action* t)
+{
+	struct case_stms* c;
+	int n, i, j, ret, val;
+	struct action* a;
+	struct action* block;
+	struct action* def_a;
+	struct action* action_lst;
+	struct action** tail;
+	struct switch_jmp_table* jmp;
+	struct switch_cond_table* sct;
+	int labels_no;
+	struct action** def_jmp_bm;
+	int* cond;
+	struct action*** jmp_bm;
+	int default_found;
+	int first, last, start, end, hits, best_hits;
+	struct rval_expr* sw_rve;
+	
+	ret=E_BUG;
+	cond=0;
+	jmp_bm=0;
+	def_jmp_bm=0;
+	labels_no=0;
+	default_found=0;
+	sw_rve=(struct rval_expr*)t->val[0].u.data;
+	/*  handle null actions: optimize away if no
+	   sideffects */
+	if (t->val[1].u.data==0){
+		if (!rve_has_side_effects(sw_rve)){
+			t->type=BLOCK_T;
+			rve_destroy(sw_rve);
+			t->val[0].type=BLOCK_ST;
+			t->val[0].u.data=0;
+			DBG("SWITCH: null switch optimized away\n");
+		}else{
+			t->type=EVAL_T;
+			t->val[0].type=RVE_ST;
+			DBG("SWITCH: null switch turned to EVAL_T\n");
+		}
+		return 0;
+	}
+	def_a=0;
+	n=0;
+	for (c=(struct case_stms*)t->val[1].u.data; c; c=c->next){
+		if (c->ct_rve){
+			if (!rve_is_constant(c->ct_rve)){
+				LOG(L_ERR, "ERROR: fix_switch: non constant "
+						"expression in case\n");
+				return E_BUG;
+			}
+			if (rval_expr_eval_int(0, 0,  &c->int_label, c->ct_rve)
+					<0){
+				LOG(L_ERR, "ERROR: fix_switch: case expression"
+						" (%d,%d) has non-interger type\n",
+						c->ct_rve->fpos.s_line,
+						c->ct_rve->fpos.s_col);
+				return E_BUG;
+			}
+			c->is_default=0;
+			n++; /* count only non-default cases */
+		}else{
+			if (default_found){
+				LOG(L_ERR, "ERROR: fix_switch: more then one \"default\""
+						" label found (%d, %d)\n",
+						c->ct_rve->fpos.s_line,
+						c->ct_rve->fpos.s_col);
+				return E_UNSPEC;
+			}
+			default_found=1;
+			c->int_label=-1;
+			c->is_default=1;
+			def_a=c->actions;
+		}
+		if ( c->actions && ((ret=fix_actions(c->actions))<0))
+			goto error;
+	}
+	DBG("SWITCH: %d cases, %d default\n", n, default_found);
+	/*: handle n==0 (no case only a default:) */
+	if (n==0){
+		if (default_found){
+			if (!rve_has_side_effects(sw_rve)){
+				t->type=BLOCK_T;
+				rve_destroy(sw_rve);
+				destroy_case_stms(t->val[1].u.data);
+				t->val[0].type=BLOCK_ST;
+				t->val[0].u.data=def_a;
+				t->val[1].type=0;
+				t->val[1].u.data=0;
+				DBG("SWITCH: default only switch optimized away (BLOCK_T)\n");
+				return 0;
+			}
+			DBG("SWITCH: default only switch with side-effect...\n");
+		}else{
+			LOG(L_CRIT, "BUG: fix_switch: empty switch not expected at this"
+					" point\n");
+			ret=E_BUG;
+			goto error;
+		}
+	}
+	labels_no=n;
+	cond=pkg_malloc(sizeof(cond[0])*n);
+	jmp_bm=pkg_malloc(sizeof(jmp_bm[0])*n);
+	if (cond==0 || jmp_bm==0){
+		LOG(L_ERR, "ERROR: fix_switch: memory allocation failure\n");
+		ret=E_OUT_OF_MEM;
+		goto error;
+	}
+	
+	/* fill condition table and jump point bookmarks and "flatten" the action 
+	   lists (transform them into a single list for the entire switch, rather
+	    then one block per case ) */
+	n=0;
+	action_lst=0;
+	tail=&action_lst;
+	for (c=(struct case_stms*)t->val[1].u.data; c; c=c->next){
+		a=c->actions;
+		if (a){
+			for (; a->next; a=a->next);
+			if (action_lst==0)
+				action_lst=c->actions;
+			else
+				*tail=c->actions;
+		}
+		if (c->is_default){
+			def_jmp_bm=tail;
+		} else {
+			for (j=0; j<n; j++){
+				if (cond[j]==c->int_label){
+					LOG(L_ERR, "ERROR: fix_switch: duplicate case (%d,%d)\n",
+							c->ct_rve->fpos.s_line, c->ct_rve->fpos.s_col);
+					ret=E_UNSPEC;
+					goto error;
+				}
+			}
+			cond[n]=c->int_label;
+			jmp_bm[n]=tail;
+			n++;
+		}
+		if (c->actions)
+			tail=&a->next;
+	}
+	/* handle constant rve w/ no side-effects: replace the whole case 
+	   with the case rve block */
+	if ( (scr_opt_lev>=2) &&
+			!rve_has_side_effects(sw_rve) && rve_is_constant(sw_rve)){
+		if (rval_expr_eval_int(0, 0,  &val, sw_rve) <0){
+			LOG(L_ERR, "ERROR: fix_switch: wrong type for switch(...) "
+					"expression (%d,%d)\n", 
+					sw_rve->fpos.s_line, sw_rve->fpos.s_col);
+			ret=E_UNSPEC;
+			goto error;
+		}
+		/* start with the "default:" value in case nothing is found */
+		block=def_jmp_bm?*def_jmp_bm:0;
+		for (i=0; i<n; i++){
+			if (cond[i]==val){
+				block=*jmp_bm[i];
+				break;
+			}
+		}
+		t->type=BLOCK_T;
+		rve_destroy(sw_rve);
+		t->val[0].type=BLOCK_ST;
+		t->val[0].u.data=block;
+		destroy_case_stms(t->val[1].u.data);
+		t->val[1].type=0;
+		t->val[1].u.data=0;
+		ret=0;
+		DBG("SWITCH: constant switch(%d) with %d cases optimized away to case "
+				" %d \n", val, n, i);
+		goto end;
+	}
+	/* try to create a jumptable */
+	/* cost: 2 cmp & table lookup
+	   => makes sense for more then 3 cases
+	   & if size< MAX_JT_SIZE
+	*/
+	best_hits=3; /* more then 3 hits needed */
+	start=end=0;
+	for (i=0; i<n; i++){
+		last=first=cond[i];
+		hits=1;
+		for (j=0; j<n; j++){
+			if ((i==j) || (cond[j]<=first)) continue;
+			if (cond[j]<last)
+				hits++;
+			else if ((cond[j]-first)<MAX_JT_SIZE){
+				last=cond[j];
+				hits++;
+			}
+		}
+		if (hits>best_hits){
+			best_hits=hits;
+			start=first;
+			end=last;
+			if (hits==n) break;
+		}
+	}
+	if (start!=end){
+		/* build jumptable: end-start entries and
+		 with a n-best_hits normal switch table */
+		jmp=mk_switch_jmp_table(end-start+1, n-best_hits);
+		if (jmp==0){
+			LOG(L_ERR, "ERROR: fix_switch: memory allocation error\n");
+			ret=E_OUT_OF_MEM;
+			goto error;
+		}
+		jmp->first=start;
+		jmp->last=end;
+		jmp->rest.n=n-best_hits;
+		jmp->rest.def=def_jmp_bm?*def_jmp_bm:0;
+		/* fill it with default values */
+		for (i=0; i<=(end-start); i++)
+			jmp->tbl[i]=jmp->rest.def;
+		for (i=0, j=0; i<n; i++){
+			if (cond[i]>=start && cond[i]<=end){
+				jmp->tbl[cond[i]-start]=*jmp_bm[i];
+			}else{
+				jmp->rest.cond[j]=cond[i];
+				jmp->rest.jump[j]=*jmp_bm[i];
+				j++;
+			}
+		}
+		t->type=SWITCH_JT_T;
+		t->val[1].type=JUMPTABLE_ST;
+		t->val[1].u.data=jmp;
+		ret=0;
+		DBG("SWITCH: optimized to jumptable [%d, %d] and %d condtable,"
+				"default: %s\n ",
+				jmp->first, jmp->last, jmp->rest.n, jmp->rest.def?"yes":"no");
+	}else{
+		sct=mk_switch_cond_table(n);
+		if (sct==0){
+			LOG(L_ERR, "ERROR: fix_switch: memory allocation error\n");
+			ret=E_OUT_OF_MEM;
+			goto error;
+		}
+		sct->n=n;
+		for (i=0; i<n; i++){
+			sct->cond[i]=cond[i];
+			sct->jump[i]=*jmp_bm[i];
+		}
+		sct->def=def_jmp_bm?*def_jmp_bm:0;
+		t->type=SWITCH_COND_T;
+		t->val[1].type=CONDTABLE_ST;
+		t->val[1].u.data=sct;
+		DBG("SWITCH: optimized to condtable (%d) default: %s\n ",
+				sct->n, sct->def?"yes":"no");
+		ret=0;
+	}
+end:
+error:
+	if (cond) pkg_free(cond);
+	if (jmp_bm) pkg_free(jmp_bm);
+	return ret;
+}
+
+/* vi: set ts=4 sw=4 tw=79:ai:cindent: */

+ 61 - 0
switch.h

@@ -0,0 +1,61 @@
+/* 
+ * $Id$
+ * 
+ * Copyright (C) 2009 iptelorg GmbH
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+/*
+ * /home/andrei/sr.git/switch.h
+ */
+/*
+ * History:
+ * --------
+ *  2009-02-02  initial version (andrei)
+*/
+
+#ifndef __switch_h
+#define __switch_h
+
+#include "route_struct.h"
+
+struct case_stms{
+	struct rval_expr* ct_rve;
+	struct action* actions;
+	struct case_stms* next;
+	struct case_stms** append;
+	int int_label;
+	int is_default;
+};
+
+
+struct switch_cond_table{
+	int n;                  /**< size */
+	int* cond;              /**< int labels array */
+	struct action** jump;   /**< jump points array */
+	struct action* def; /**< default jump  */
+};
+
+
+struct switch_jmp_table{
+	int first;              /**< first int label in the jump table */
+	int last;               /**< last int label in the jump table */
+	struct action** tbl;    /**< jmp table [v-first] iff first<=v<=last */
+	struct switch_cond_table rest; /**< normal cond. table for the rest */
+};
+
+int fix_switch(struct action* t);
+
+#endif /*__switch_h*/
+
+/* vi: set ts=4 sw=4 tw=79:ai:cindent: */