%{ /* This file is part of Mailfromd. Copyright (C) 2005-2020 Sergey Poznyakoff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include "mailfromd.h" #include #include "prog.h" #include "optab.h" static void free_node(NODE *node); static void set_poll_arg(struct poll_data *poll, int kw, NODE *expr); static int codegen(prog_counter_t *pc, NODE *node, struct exmask *exmask, int finalize, size_t nautos); static void mark(NODE *node); static void dataseg_layout(void); static void regex_layout(void); static int optimize_tree(NODE *node); static void compile_tree(NODE *node); static NODE *reverse(NODE *in); static size_t nodelistlength(NODE *list); static NODE *function_call(struct function *function, size_t count, NODE *subtree); static NODE *declare_function(struct function *func, struct mu_locus_range const *loc, size_t nautos); static data_type_t node_type(NODE *node); static NODE *cast_arg_list(NODE *args, size_t parmc, data_type_t *parmtype, int disable_prom); void add_xref(struct variable *var, struct mu_locus_range const *locus); static struct variable *vardecl(const char *name, data_type_t type, storage_class_t sc, struct mu_locus_range const *loc); static struct variable *externdecl(const char *name, struct value *value, struct mu_locus_range const *loc); static int initialize_variable(struct variable *var, struct value *val, struct mu_locus_range const *locus); static void apply_deferred_init(void); static NODE *create_asgn_node(struct variable *var, NODE *expr, struct mu_locus_range const *loc); NODE *defined_parm(struct variable *var, struct mu_locus_range const *locus); static void register_auto(struct variable *var); static void unregister_auto(struct variable *var); static size_t forget_autos(size_t nparam, size_t prev, size_t hidden_arg); static void optimize(NODE *node); static NODE *root_node[smtp_state_count]; prog_counter_t entry_point[smtp_state_count]; #define PS_BEGIN 0 #define PS_END 1 int regex_flags; /* Should default to REG_NOSUB ? */ unsigned error_count; /* Number of detected errors */ size_t variable_count = 0; /* Number of variables in the data segment. */ size_t precious_count = 0; static enum smtp_state state_tag; /* State tag of the currently processed PROG */ static struct function *func; /* The currently compiled function */ static prog_counter_t jump_pc; /* Pointer to the chain of jmp instructions */ /* Outermost context decides how to code exits from catches. Innermost context is used to access parameters. */ enum lexical_context outer_context, inner_context; size_t catch_nesting; /* Nesting level for catch statements */ static struct stmtlist genstmt; /* List of generated statements */ /* State handlers and their positional parameters */ struct state_parms { int cnt; /* Number or positional parameters */ data_type_t types[4]; /* Their data types */ } state_parms[] = { { 0, }, /* smtp_state_none */ { 0, }, /* smtp_state_begin */ { 4, { dtype_string, dtype_number, dtype_number, dtype_string } }, /* smtp_state_connect */ { 1, { dtype_string } }, /* smtp_state_helo */ { 2, { dtype_string, dtype_string } }, /* smtp_state_envfrom */ { 2, { dtype_string, dtype_string } }, /* smtp_state_envrcpt */ { 0, }, /* smtp_state_data */ { 2, { dtype_string, dtype_string } }, /* smtp_state_header */ { 0, }, /* smtp_state_eoh */ { 2, { dtype_pointer, dtype_number } }, /* smtp_state_body */ { 0, }, /* smtp_state_eom */ { 0, } /* smtp_state_end */ }; struct parmtype { struct parmtype *next; data_type_t type; }; static int parmcount_none() { return 0; } static data_type_t parmtype_none(int n) { return dtype_unspecified; } static int parmcount_handler() { return state_parms[state_tag].cnt; } static data_type_t parmtype_handler(int n) { return state_parms[state_tag].types[n-1]; } static int parmcount_catch() { return 2; } static data_type_t parmtype_catch(int n) { switch (n) { case 1: return dtype_number; case 2: return dtype_string; } abort(); } static int parmcount_function() { return func->parmcount; } static data_type_t parmtype_function(int n) { if (func->varargs && n > func->parmcount) return dtype_string; return func->parmtype[n-1]; } struct parminfo { int (*parmcount)(void); data_type_t (*parmtype)(int n); } parminfo[] = { { parmcount_none, parmtype_none }, { parmcount_handler, parmtype_handler }, { parmcount_catch, parmtype_catch }, { parmcount_function, parmtype_function }, }; #define PARMCOUNT() parminfo[inner_context].parmcount() #define PARMTYPE(n) parminfo[inner_context].parmtype(n) #define FUNC_HIDDEN_ARGS(f) (((f)->optcount || (f)->varargs) ? 1 : 0) data_type_t string_to_type(const char *s) { if (strcmp(s, "n") == 0) return dtype_number; else if (strcmp(s, "s") == 0) return dtype_string; else return dtype_unspecified; } const char * type_to_string(data_type_t t) { switch (t) { case dtype_number: return "number"; case dtype_string: return "string"; case dtype_unspecified: return "unspecified"; case dtype_pointer: return "pointer"; default: abort(); } } static int check_func_usage(struct function *fp, struct mu_locus_range const *locus) { switch (outer_context) { case context_handler: if (fp->statemask && !(STATMASK(state_tag) & fp->statemask)) { parse_error_locus(locus, _("function `%s' cannot be used in " "prog `%s'"), fp->sym.name, state_to_string(state_tag)); return 1; } break; case context_function: func->statemask |= fp->statemask; break; default: break; } return 0; } static int check_builtin_usage(const struct builtin *bp, struct mu_locus_range const *locus) { switch (outer_context) { case context_handler: if (bp->statemask && !(STATMASK(state_tag) & bp->statemask)) { parse_error_locus(locus, _("built-in function `%s' cannot be used in " "prog `%s'"), bp->name, state_to_string(state_tag)); return 1; } break; case context_function: func->statemask |= bp->statemask; break; default: break; } if (bp->flags & MFD_BUILTIN_CAPTURE) capture_on(); return 0; } static void jump_fixup(prog_counter_t pos, prog_counter_t endpos); #define LITERAL_TEXT(lit) ((lit) ? (lit)->text : NULL) #define LITERAL_OFF(lit) ((lit) ? (lit)->off : 0) static int _create_alias(void *item, void *data) { struct literal *lit = item; struct function *fun = data; /* FIXME: Ideally we should pass a locus of `lit' instead of fun->locus. However, its only purpose is for use in redefinition diagnostic messages and these are never produced, because the grammar catches these errors earlier. This might change in the future. 2008-09-14 */ install_alias(lit->text, fun, &fun->sym.locus); return 0; } %} %error-verbose %locations /*%expect 1*/ %union { struct literal *literal; struct stmtlist stmtlist; NODE *node; struct return_node ret; struct poll_data poll; struct pollarg { int kw; NODE *expr; } pollarg; struct arglist { NODE *head; NODE *tail; size_t count; } arglist; long number; const struct builtin *builtin; struct variable *var; enum smtp_state state; struct { int qualifier; } matchtype; data_type_t type; struct parmtype *parm; struct parmlist { struct parmtype *head, *tail; size_t count; size_t optcount; int varargs; } parmlist; enum lexical_context tie_in; struct function *function; struct { struct valist *head, *tail; } valist_list; struct { int all; struct valist *valist; } catchlist; struct valist *valist; struct value value; struct { struct case_stmt *head, *tail; } case_list ; struct case_stmt *case_stmt; struct loop_node loop; struct { int code; } progspecial; struct enumlist { struct constant *cv; struct enumlist *prev; } enumlist; mu_list_t list; struct import_rule_list import_rule_list; char *string; }; %token T_ACCEPT "accept" T_REJECT "reject" T_TEMPFAIL "tempfail" T_CONTINUE "continue" T_DISCARD "discard" T_ADD "add" T_REPLACE "replace" T_DELETE "delete" T_PROG "prog" T_BEGIN "begin" T_END "end" T_IF "if" T_FI "fi" T_ELSE "else" T_ELIF "elif" T_ON "on" T_HOST "host" T_FROM "from" T_AS "as" T_DO "do" T_DONE "done" T_POLL "poll" T_MATCHES "matches" T_FNMATCHES "fnmatches" T_MXMATCHES "mx matches" T_MXFNMATCHES "mx fnmatches" T_WHEN "when" T_PASS "pass" T_SET "set" T_CATCH "catch" T_TRY "try" T_THROW "throw" T_ECHO "echo" T_RETURNS "returns" T_RETURN "return" T_FUNC "func" T_SWITCH "switch" T_CASE "case" T_DEFAULT "default" T_CONST "const" T_FOR "for" T_LOOP "loop" T_WHILE "while" T_BREAK "break" T_NEXT "next" T_ARGCOUNT "$#" T_ALIAS "alias" T_DOTS "..." T_ARGX "$(n)" T_VAPTR "vaptr" T_PRECIOUS "precious" T_OR "or" T_AND "and" T_EQ "==" T_NE "!=" T_LT "<" T_LE "<=" T_GT ">" T_GE ">=" T_NOT "!" T_LOGAND "&" T_LOGOR "|" T_LOGXOR "^" T_LOGNOT "~" T_REQUIRE "require" T_IMPORT "import" T_STATIC "static" T_PUBLIC "public" T_MODULE "module" T_BYE "bye" T_DCLEX "dclex" T_SHL "<<" T_SHR ">>" %token T_COMPOSE "composed string" %token T_MODBEG T_MODEND %token T_STRING "string" %token T_SYMBOL "MTA macro" T_IDENTIFIER "identifier" %token T_ARG "$n" T_NUMBER "number" T_BACKREF "back reference" %token T_BUILTIN "builtin function" %token T_FUNCTION "function" %token T_TYPE "data type" T_TYPECAST "typecast" %token T_VARIABLE "variable" %token T_BOGUS %left '.' %left T_OR %left T_AND %left T_NOT %left T_LOGOR %left T_LOGXOR %left T_LOGAND %nonassoc T_EQ T_NE T_MATCHES T_FNMATCHES T_MXMATCHES T_MXFNMATCHES %nonassoc T_LT T_LE T_GT T_GE %left T_SHL T_SHR %left '+' '-' %left '*' '/' '%' %left T_UMINUS %type decl stmt condition action sendmail_action header_action if_cond else_cond on_cond atom argref paren_argref funcall expr maybe_expr maybe_xcode_expr simp_expr atom_expr asgn catch simple_catch try_block throw return case_cond autodcl constdecl loopstmt opt_while jumpstmt strval strcat %type program stmtlist decllist modcntl %type triplet maybe_triplet %type pollstmt pollarglist %type pollarg loop_parm %type opt_loop_parms loop_parm_list %type arglist %type variable %type literal string opt_ident loop_ident alias code xcode varname %type state_ident %type matches fnmatches %type retdecl %type params opt_parmlist parmlist fparmlist parmdecl %type parm %type fundecl %type value %type valist %type catchlist %type cond_branches branches %type cond_branch branch %type on %type progspecial %type aliases aliasdecl %type imports %type qualifiers qualifier qualconst %type enumlist constdefn %% input : program { if (error_count == 0) { if (genstmt.head) { genstmt.tail->next = $1.head; genstmt.tail = $1.tail; } else genstmt = $1; optimize_tree(genstmt.head); if (error_count) YYERROR; mark(genstmt.head); if (error_count) YYERROR; apply_deferred_init(); fixup_exceptions(); dataseg_layout(); if (optimization_level) regex_layout(); compile_tree(genstmt.head); if (!optimization_level) regex_layout(); } } ; program : decllist | moddecl decllist bye { $$ = $2; } ; decllist : decl { $$.head = $$.tail = $1; } | modcntl | decllist decl { if ($2) { if ($1.tail) $1.tail->next = $2; else $1.head = $2; $1.tail = $2; } $$ = $1; } | decllist modcntl { if ($2.tail) { if ($1.tail) { $$.head = $1.head; $1.tail->next = $2.head; $$.tail = $2.tail; } else $$ = $2; } } ; modcntl : require { $$.head = $$.tail = NULL; } | require T_MODBEG opt_moddecl decllist bye T_MODEND { $$ = $4; pop_top_module(); } ; opt_moddecl: /* empty */ { parse_warning(_("missing module declaration")); } | moddecl ; moddecl : T_MODULE literal '.' { if (top_module->dclname) { struct mu_locus_range lr; lr.beg = @1.beg; lr.end = @3.end; parse_error_locus(&lr, _("duplicate module declaration")); } else top_module->dclname = $2->text; } | T_MODULE literal qualifier '.' { if (top_module->dclname) { struct mu_locus_range lr; lr.beg = @1.beg; lr.end = @4.end; parse_error_locus(&lr, _("duplicate module declaration")); } else { if (($3 & (SYM_STATIC|SYM_PUBLIC)) == 0) parse_error_locus(&@3, _("invalid module declaration")); top_module->dclname = $2->text; top_module->flags = $3; } } ; require : T_REQUIRE literal { require_module($2->text, NULL); } | T_FROM literal T_IMPORT imports '.' { require_module($2->text, $4.head); } ; bye : /* empty */ | T_BYE { lex_bye(); } ; imports : literal { struct import_rule *rule = import_rule_create($1); $$.head = $$.tail = rule; } | imports ',' literal { struct import_rule *rule = import_rule_create($3); $$.tail->next = rule; $$.tail = rule; } ; literal : T_STRING | T_IDENTIFIER ; decl : T_PROG state_ident T_DO stmtlist T_DONE { $$ = alloc_node(node_type_progdecl, &@1); $$->v.progdecl.tag = $2; $$->v.progdecl.auto_count = forget_autos(PARMCOUNT(), 0, 0); $$->v.progdecl.tree = $4.head; outer_context = inner_context = context_none; state_tag = smtp_state_none; } | progspecial T_DO stmtlist T_DONE { static NODE *progspecial[2]; NODE *np = progspecial[$1.code]; if (!np) { np = alloc_node(node_type_progdecl, &@1); $$ = progspecial[$1.code] = np; np->v.progdecl.tag = state_tag; np->v.progdecl.tree = $3.head; np->v.progdecl.auto_count = 0; } else { NODE *cur; for (cur = np->v.progdecl.tree; cur->next; cur = cur->next) ; cur->next = $3.head; $$ = NULL; } np->v.progdecl.auto_count = forget_autos(PARMCOUNT(), np->v.progdecl.auto_count, 0); outer_context = inner_context = context_none; state_tag = smtp_state_none; } | qualifiers T_FUNC fundecl T_DO stmtlist T_DONE { if ($1 & SYM_PRECIOUS) parse_error_locus(&@2, _("`precious' used with func")); if (($1 & (SYM_STATIC|SYM_PUBLIC)) == (SYM_STATIC|SYM_PUBLIC)) parse_error_locus(&@2, _("`static' and `public' " "used together")); func->sym.flags = $1; func->node = $5.head; $$ = declare_function(func, &@2, forget_autos(PARMCOUNT() + FUNC_HIDDEN_ARGS(func), 0, FUNC_HIDDEN_ARGS(func))); outer_context = inner_context = context_none; func = NULL; } | vardecl { $$ = NULL; } | constdecl { $$ = NULL; } | exdecl { $$ = NULL; } ; progspecial: T_BEGIN { state_tag = smtp_state_begin; outer_context = inner_context = context_handler; $$.code = PS_BEGIN; } | T_END { state_tag = smtp_state_end; outer_context = inner_context = context_handler; $$.code = PS_END; } ; varname : T_IDENTIFIER | T_VARIABLE { $$ = literal_lookup($1->sym.name); } ; vardecl : qualifiers T_TYPE varname { struct variable *var; if (($1 & (SYM_STATIC|SYM_PUBLIC)) == (SYM_STATIC|SYM_PUBLIC)) parse_error_locus(&@1, _("`static' and `public' " "used together")); var = vardecl($3->text, $2, storage_extern, &@3); if (!var) YYERROR; var->sym.flags |= $1; } | qualifiers T_TYPE varname expr { struct value value; struct variable *var; if (($1 & (SYM_STATIC|SYM_PUBLIC)) == (SYM_STATIC|SYM_PUBLIC)) parse_error_locus(&@1, _("`static' and `public' " "used together")); var = vardecl($3->text, $2, storage_extern, &@2); if (!var) YYERROR; var->sym.flags |= $1; if (optimization_level) optimize($4); value.type = node_type($4); switch ($4->type) { case node_type_string: value.v.literal = $4->v.literal; break; case node_type_number: value.v.number = $4->v.number; break; default: yyerror("initializer element is not constant"); YYERROR; } if (initialize_variable(var, &value, &@3)) YYERROR; } | T_SET varname expr /* FIXME: Optimize if varname: T_VARIABLE */ { struct value value; if (optimization_level) optimize($3); value.type = node_type($3); switch ($3->type) { case node_type_string: value.v.literal = $3->v.literal; break; case node_type_number: value.v.number = $3->v.number; break; default: yyerror("initializer element is not constant"); YYERROR; } if (!externdecl($2->text, &value, &@2)) YYERROR; } ; qualifiers : /* empty */ { $$ = 0; } | qualifiers qualifier { if ($1 & $2) parse_warning(_("duplicate `%s'"), symbit_to_qualifier($2)); $$ = $1 | $2; } ; qualifier : T_PRECIOUS { $$ = SYM_PRECIOUS; } | T_STATIC { $$ = SYM_STATIC; } | T_PUBLIC { $$ = SYM_PUBLIC; } ; constdecl : qualconst constdefn { $2.cv->sym.flags = $1; $$ = NULL; } | qualconst T_DO enumlist T_DONE { struct enumlist *elist; while ((elist = $3.prev)) { elist->cv->sym.flags = $1; elist = elist->prev; free($3.prev); $3.prev = elist; } $$ = NULL; } ; qualconst : qualifiers T_CONST { if ($1 & SYM_PRECIOUS) parse_error_locus(&@1, _("`precious' used with const")); if (($1 & (SYM_STATIC|SYM_PUBLIC)) == (SYM_STATIC|SYM_PUBLIC)) parse_error_locus(&@1, _("`static' and `public' " "used together")); $$ = $1; } ; constdefn : varname expr /* FIXME: Optimize if varname: T_IDENTIFIER */ { struct value value; struct variable *pvar; /* FIXME: This is necessary because constants can be referred to the same way as variables. */ if (pvar = variable_lookup($1->text)) { parse_warning(_("constant name `%s' clashes with a variable name"), $1->text); parse_warning_locus(&pvar->sym.locus, _("this is the location of the " "previous definition")); } if (optimization_level) optimize($2); switch ($2->type) { case node_type_string: value.type = dtype_string; value.v.literal = $2->v.literal; break; case node_type_number: value.type = dtype_number; value.v.number = $2->v.number; break; default: yyerror(_("initializer element is not constant")); YYERROR; } $$.cv = define_constant($1->text, &value, 0, &@1); $$.prev = NULL; } ; enumlist : varname { struct enumlist *elist; struct value value; struct variable *pvar; if (pvar = variable_lookup($1->text)) { parse_warning(_("constant name `%s' clashes with a variable name"), $1->text); parse_warning_locus(&pvar->sym.locus, _("this is the location of the " "previous definition")); } value.type = dtype_number; value.v.number = 0; elist = mu_alloc(sizeof(*elist)); elist->cv = define_constant($1->text, &value, 0, &@1); elist->prev = NULL; $$.cv = NULL; $$.prev = elist; } | constdefn { struct enumlist *elist = mu_alloc(sizeof(*elist)); elist->cv = $1.cv; elist->prev = NULL; $$.cv = NULL; $$.prev = elist; } | enumlist varname { if ($1.prev->cv->value.type == dtype_number) { struct enumlist *elist = mu_alloc(sizeof(*elist)); struct value value; value.type = dtype_number; value.v.number = $1.prev->cv->value.v.number + 1; elist->cv = define_constant($2->text, &value, 0, &@2); elist->prev = $1.prev; $$.prev = elist; } else { yyerror(_("initializer element is not numeric")); YYERROR; } } | enumlist constdefn { struct enumlist *elist = mu_alloc(sizeof(*elist)); elist->cv = $2.cv; elist->prev = $1.prev; $1.prev = elist; $$ = $1; } ; exdecl : T_DCLEX T_IDENTIFIER { define_exception($2, &@1); } ; fundecl : varname '(' parmdecl ')' aliasdecl retdecl { data_type_t *ptypes = NULL; if ($3.count) { int i; struct parmtype *p; ptypes = mu_alloc($3.count * sizeof *ptypes); for (i = 0, p = $3.head; p; i++) { struct parmtype *next = p->next; ptypes[i] = p->type; free(p); p = next; } } $$ = func = function_install($1->text, $3.count, $3.optcount, $3.varargs, ptypes, $6, &@1); if ($5) { mu_list_foreach($5, _create_alias, $$); mu_list_destroy(&$5); } outer_context = inner_context = context_function; } ; parmdecl : /* empty */ { $$.count = $$.optcount = 0; $$.varargs = 0; } | params ; params : fparmlist | T_DOTS { $$.count = $$.optcount = 0; $$.varargs = 1; } | opt_parmlist ';' fparmlist { $1.count += $3.count; $1.optcount = $3.count; $1.varargs = $3.varargs; if ($1.tail) $1.tail->next = $3.head; else $1.head = $3.head; $1.tail = $3.tail; $$ = $1; } ; opt_parmlist: /* empty */ { $$.count = 0; $$.varargs = 0; $$.head = $$.tail = NULL; } | parmlist ; parmlist : parm { $$.count = 1; $$.varargs = 0; $$.optcount = 0; $$.head = $$.tail = $1; } | parmlist ',' parm { $1.count++; $1.tail->next = $3; $1.tail = $3; $$ = $1; } ; fparmlist : parmlist | parmlist ',' T_DOTS { $1.varargs = 1; $$ = $1; } ; parm : T_TYPE varname { if (!vardecl($2->text, $1, storage_param, &@2)) YYERROR; $$ = mu_alloc(sizeof *$$); $$->next = NULL; $$->type = $1; } /* FIXME: Is this still needed? */ | T_TYPE { parse_warning_locus(&@1, _("unnamed formal parameters are deprecated")); $$ = mu_alloc(sizeof *$$); $$->next = NULL; $$->type = $1; } ; aliasdecl : /* empty */ { $$ = NULL; } | aliases ; aliases : alias { mu_list_create(&$$); mu_list_append($$, $1); } | aliases alias { mu_list_append($1, $2); $$ = $1; } ; alias : T_ALIAS varname { $$ = $2; } ; retdecl : /* empty */ { $$ = dtype_unspecified; } | T_RETURNS T_TYPE { $$ = $2; } ; state_ident: T_IDENTIFIER { $$ = string_to_state($1->text); if ($$ == smtp_state_none) parse_error_locus(&@1, _("unknown smtp state tag: %s"), $1->text); state_tag = $$; outer_context = inner_context = context_handler; } ; stmtlist : stmt { if ($1) $1->next = NULL; $$.head = $$.tail = $1; } | stmtlist stmt { if ($2) { if ($1.tail) $1.tail->next = $2; else $1.head = $2; $1.tail = $2; } $$ = $1; } ; stmt : condition | action | asgn | autodcl | catch | throw | return | funcall { if (node_type($1) != dtype_unspecified) parse_warning(_("return from %s is ignored"), $1->type == node_type_builtin ? $1->v.builtin.builtin->name : $1->v.call.func->sym.name); } | constdecl | loopstmt | jumpstmt ; asgn : T_SET varname expr { struct variable *var; data_type_t t = node_type($3); if (t == dtype_unspecified) { parse_error_locus(&@3, _("unspecified value not ignored as " "it should be")); YYERROR; } var = variable_lookup($2->text); if (!var) { var = vardecl($2->text, t, storage_auto, &@2); if (!var) YYERROR; } $$ = create_asgn_node(var, $3, &@1); if (!$$) YYERROR; } ; autodcl : T_TYPE varname { if (!vardecl($2->text, $1, storage_auto, &@2)) YYERROR; $$ = NULL; } | T_TYPE varname expr { struct variable *var = vardecl($2->text, $1, storage_auto, &@2); if (!var) YYERROR; $$ = create_asgn_node(var, $3, &@1); if (!$$) YYERROR; } ; action : sendmail_action { if (inner_context == context_handler) { if (state_tag == smtp_state_begin) parse_error_locus(&@1, _("Sendmail action is not " "allowed in begin block")); else if (state_tag == smtp_state_end) parse_error_locus(&@1, _("Sendmail action is not " "allowed in end block")); } } | header_action { if (inner_context == context_handler && state_tag == smtp_state_end) parse_error_locus(&@1, _("header action is not allowed " "in end block")); } | T_PASS { $$ = alloc_node(node_type_noop, &@1); } | T_ECHO expr { $$ = alloc_node(node_type_echo, &@1); $$->v.node = cast_to(dtype_string, $2); } ; sendmail_action: T_ACCEPT maybe_triplet { if ($2.code || $2.xcode || $2.message) parse_warning(_("arguments are ignored for accept")); $$ = alloc_node(node_type_result, &@1); $$->v.ret = $2; $$->v.ret.stat = SMFIS_ACCEPT; } | T_REJECT maybe_triplet { $$ = alloc_node(node_type_result, &@1); $$->v.ret = $2; $$->v.ret.stat = SMFIS_REJECT; } | T_REJECT '(' maybe_expr ',' maybe_xcode_expr ',' maybe_expr ')' { $$ = alloc_node(node_type_result, &@1); $$->v.ret.stat = SMFIS_REJECT; $$->v.ret.code = $3 ? cast_to(dtype_string, $3) : NULL; $$->v.ret.xcode = $5 ? cast_to(dtype_string, $5) : NULL; $$->v.ret.message = $7 ? cast_to(dtype_string, $7) : NULL; } | T_TEMPFAIL maybe_triplet { $$ = alloc_node(node_type_result, &@1); $$->v.ret = $2; $$->v.ret.stat = SMFIS_TEMPFAIL; } | T_TEMPFAIL '(' maybe_expr ',' maybe_xcode_expr ',' maybe_expr ')' { $$ = alloc_node(node_type_result, &@1); $$->v.ret.stat = SMFIS_TEMPFAIL; $$->v.ret.code = $3 ? cast_to(dtype_string, $3) : NULL; $$->v.ret.xcode = $5 ? cast_to(dtype_string, $5) : NULL; $$->v.ret.message = $7 ? cast_to(dtype_string, $7) : NULL; } | T_CONTINUE { $$ = alloc_node(node_type_result, &@1); memset(&$$->v.ret, 0, sizeof $$->v.ret); $$->v.ret.stat = SMFIS_CONTINUE; } | T_DISCARD { $$ = alloc_node(node_type_result, &@1); memset(&$$->v.ret, 0, sizeof $$->v.ret); $$->v.ret.stat = SMFIS_DISCARD; } ; maybe_xcode_expr: maybe_expr | xcode { $$ = alloc_node(node_type_string, &@1); $$->v.literal = $1; } ; header_action: T_ADD string expr { $$ = alloc_node(node_type_header, &@1); $$->v.hdr.opcode = header_add; $$->v.hdr.name = $2; $$->v.hdr.value = cast_to(dtype_string, $3); } | T_REPLACE string expr { $$ = alloc_node(node_type_header, &@1); $$->v.hdr.opcode = header_replace; $$->v.hdr.name = $2; $$->v.hdr.value = cast_to(dtype_string, $3); } | T_DELETE string { $$ = alloc_node(node_type_header, &@1); $$->v.hdr.opcode = header_delete; $$->v.hdr.name = $2; $$->v.hdr.value = NULL; } ; maybe_triplet: /* empty */ { memset(&$$, 0, sizeof $$); } | triplet ; triplet : code { $$.code = alloc_node(node_type_string, &@1); $$.code->v.literal = $1; $$.xcode = NULL; $$.message = NULL; } | code xcode { $$.code = alloc_node(node_type_string, &@1); $$.code->v.literal = $1; $$.xcode = alloc_node(node_type_string, &@2); $$.xcode->v.literal = $2; $$.message = NULL; } | code xcode expr { $$.code = alloc_node(node_type_string, &@1); $$.code->v.literal = $1; $$.xcode = alloc_node(node_type_string, &@2); $$.xcode->v.literal = $2; $$.message = cast_to(dtype_string, $3); } | code expr { $$.code = alloc_node(node_type_string, &@1); $$.code->v.literal = $1; $$.xcode = NULL; $$.message = cast_to(dtype_string, $2); } ; code : T_NUMBER { char buf[4]; if ($1 < 200 || $1 > 599) { yyerror(_("invalid SMTP reply code")); buf[0] = 0; } else snprintf(buf, sizeof(buf), "%lu", $1); $$ = string_alloc(buf, strlen(buf)); } ; xcode : T_NUMBER '.' T_NUMBER '.' T_NUMBER { char buf[sizeof("5.999.999")]; /* RFC 1893: The syntax of the new status codes is defined as: status-code = class "." subject "." detail class = "2"/"4"/"5" subject = 1*3digit detail = 1*3digit */ if (($1 != 2 && $1 != 4 && $1 !=5) || $3 > 999 || $5 > 999) { yyerror(_("invalid extended reply code")); buf[0] = 0; } else snprintf(buf, sizeof(buf), "%lu.%lu.%lu", $1, $3, $5); $$ = string_alloc(buf, strlen(buf)); } ; condition : if_cond | case_cond | on_cond ; if_cond : T_IF expr stmtlist else_cond T_FI { $$ = alloc_node(node_type_if, &@1); $$->v.cond.cond = $2; $$->v.cond.if_true = $3.head; $$->v.cond.if_false = $4; } ; else_cond : /* empty */ { $$ = NULL; } | T_ELIF expr stmtlist else_cond { $$ = alloc_node(node_type_if, &@1); $$->v.cond.cond = $2; $$->v.cond.if_true = $3.head; $$->v.cond.if_false = $4; } | T_ELSE stmtlist { $$ = $2.head; } ; case_cond : T_SWITCH expr T_DO cond_branches T_DONE { struct case_stmt *defcase = NULL, *pcase, *prev; $$ = alloc_node(node_type_switch, &@1); $$->v.switch_stmt.node = $2; /* Make sure there is only one default case and place it at the beginning of the list */ pcase = $4.head; if (!pcase->valist) { defcase = pcase; $4.head = $4.head->next; } prev = pcase; pcase = pcase->next; while (pcase) { if (!pcase->valist) { if (defcase) { parse_error_locus(&pcase->locus, _("duplicate default statement")); parse_error_locus(&defcase->locus, _("previously defined here")); YYERROR; } defcase = pcase; prev->next = pcase->next; } else prev = pcase; pcase = pcase->next; } if (!defcase) { defcase = mu_alloc(sizeof *defcase); mu_locus_range_init(&defcase->locus); mu_locus_range_copy(&defcase->locus, &@5); defcase->valist = NULL; defcase->node = alloc_node(node_type_noop, &defcase->locus); } defcase->next = $4.head; $$->v.switch_stmt.cases = defcase; } ; cond_branches: cond_branch { $$.head = $$.tail = $1; } | cond_branches cond_branch { $$.tail->next = $2; $$.tail = $2; } ; cond_branch: T_CASE valist ':' stmtlist { $$ = mu_alloc(sizeof *$$); $$->next = NULL; mu_locus_range_init (&$$->locus); mu_locus_range_copy (&$$->locus, &@1); $$->valist = $2.head; $$->node = $4.head; } | T_DEFAULT ':' stmtlist { $$ = mu_alloc(sizeof *$$); $$->next = NULL; mu_locus_range_init (&$$->locus); mu_locus_range_copy (&$$->locus, &@1); $$->valist = NULL; $$->node = $3.head; } ; valist : value { $$.head = $$.tail = mu_alloc(sizeof($$.head[0])); $$.head->next = NULL; $$.head->value = $1; } | valist T_OR value { struct valist *p = mu_alloc(sizeof(*p)); p->value = $3; p->next = NULL; $1.tail->next = p; $1.tail = p; $$ = $1; } ; value : T_STRING { $$.type = dtype_string; $$.v.literal = $1; } | T_NUMBER { $$.type = dtype_number; $$.v.number = $1; } ; string : value { if ($1.type != dtype_string) { parse_error_locus(&@1, _("expected string, but found %s"), type_to_string($1.type)); /* Make sure we return something usable: */ $$ = string_alloc("ERROR", 5); } else $$ = $1.v.literal; } ; matches : T_MATCHES { $$.qualifier = 0; } | T_MXMATCHES { $$.qualifier = QUALIFIER_MX; } ; fnmatches : T_FNMATCHES { $$.qualifier = 0; } | T_MXFNMATCHES { $$.qualifier = QUALIFIER_MX; } ; /* Loop statements */ loopstmt : T_LOOP loop_ident opt_loop_parms T_DO stmtlist T_DONE opt_while { leave_loop(); $3.end_while = $7; $$ = alloc_node(node_type_loop, &@1); $3.body = $5.head; $3.ident = $2; $$->v.loop = $3; } ; loop_ident : opt_ident { enter_loop($1, NULL, NULL); } ; opt_ident : /* empty */ { $$ = NULL; } | varname ; opt_loop_parms: /* empty */ { memset(&$$, 0, sizeof $$); } | loop_parm_list ; loop_parm_list: loop_parm { memset(&$$, 0, sizeof $$); switch ($1.kw) { case 0: $$.stmt = $1.expr; break; case T_FOR: $$.for_stmt = $1.expr; break; case T_WHILE: $$.beg_while = $1.expr; break; default: abort(); } } | loop_parm_list ',' loop_parm { switch ($3.kw) { case 0: if ($$.stmt) parse_error_locus(&@3, _("duplicate loop increment")); $$.stmt = $3.expr; break; case T_FOR: if ($$.for_stmt) parse_error_locus(&@3, _("duplicate for statement")); $$.for_stmt = $3.expr; break; case T_WHILE: if ($$.beg_while) parse_error_locus(&@3, _("duplicate while statement")); $$.beg_while = $3.expr; break; default: abort(); } } ; loop_parm : stmtlist { $$.kw = 0; $$.expr = $1.head; } | T_FOR stmtlist { $$.kw = T_FOR; $$.expr = $2.head; } | T_WHILE expr { $$.kw = T_WHILE; $$.expr = $2; } ; opt_while : /* empty */ { $$ = NULL; } | T_WHILE expr { $$ = $2; } ; jumpstmt : T_BREAK opt_ident { if (!within_loop($2)) { if ($2) parse_error_locus(&@2, _("no such loop: %s"), $2->text); parse_error_locus(&@1, _("`break' used outside of `loop'")); YYERROR; } $$ = alloc_node(node_type_break, &@1); $$->v.literal = $2; } | T_NEXT opt_ident { if (!within_loop($2)) { if ($2) { parse_error_locus(&@2, _("no such loop: %s"), $2->text); parse_error_locus(&@1, _("`next' used outside `loop'")); YYERROR; } else { parse_error_locus(&@1, _("`next' is used outside `loop'; " "did you mean `pass'?")); YYERROR; } } else { $$ = alloc_node(node_type_next, &@1); $$->v.literal = $2; } } ; /* Expressions */ expr : T_NOT expr { $$ = alloc_node(node_type_un, &@1); $$->v.un.opcode = unary_not; $$->v.un.arg = cast_to(dtype_number, $2); } | expr T_EQ expr { $$ = alloc_node(node_type_bin, &@2); $$->v.bin.opcode = bin_eq; $$->v.bin.arg[0] = $1; $$->v.bin.arg[1] = cast_to(node_type($1), $3); } | expr T_NE expr { $$ = alloc_node(node_type_bin, &@2); $$->v.bin.opcode = bin_ne; $$->v.bin.arg[0] = $1; $$->v.bin.arg[1] = cast_to(node_type($1), $3); } | expr T_LT expr { $$ = alloc_node(node_type_bin, &@2); $$->v.bin.opcode = bin_lt; $$->v.bin.arg[0] = $1; $$->v.bin.arg[1] = cast_to(node_type($1), $3); } | expr T_LE expr { $$ = alloc_node(node_type_bin, &@2); $$->v.bin.opcode = bin_le; $$->v.bin.arg[0] = $1; $$->v.bin.arg[1] = cast_to(node_type($1), $3); } | expr T_GT expr { $$ = alloc_node(node_type_bin, &@2); $$->v.bin.opcode = bin_gt; $$->v.bin.arg[0] = $1; $$->v.bin.arg[1] = cast_to(node_type($1), $3); } | expr T_GE expr { $$ = alloc_node(node_type_bin, &@2); $$->v.bin.opcode = bin_ge; $$->v.bin.arg[0] = $1; $$->v.bin.arg[1] = cast_to(node_type($1), $3); } | expr matches expr %prec T_MATCHES { NODE *p; $$ = alloc_node(node_type_bin, &@2); $$->v.bin.opcode = bin_match; $$->v.bin.qualifier = $2.qualifier; $$->v.bin.arg[0] = cast_to(dtype_string, $1); $$->v.bin.arg[1] = p = alloc_node(node_type_regcomp, &@2); p->v.regcomp_data.expr = cast_to(dtype_string, $3); p->v.regcomp_data.flags = regex_flags; p->v.regcomp_data.regind = -1; } | expr fnmatches expr %prec T_MATCHES { $$ = alloc_node(node_type_bin, &@2); $$->v.bin.opcode = bin_fnmatch; $$->v.bin.qualifier = $2.qualifier; $$->v.bin.arg[0] = cast_to(dtype_string, $1); $$->v.bin.arg[1] = cast_to(dtype_string, $3); } | expr T_OR expr { $$ = alloc_node(node_type_bin, &@2); $$->v.bin.opcode = bin_or; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } | expr T_AND expr { $$ = alloc_node(node_type_bin, &@2); $$->v.bin.opcode = bin_and; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } | simp_expr ; maybe_expr : /* empty */ { $$ = NULL; } | expr ; simp_expr : atom_expr | simp_expr '+' simp_expr { $$ = alloc_node(node_type_bin, &@1); $$->v.bin.opcode = bin_add; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } | simp_expr '.' simp_expr { $$ = alloc_node(node_type_concat, &@2); $$->v.concat.arg[0] = cast_to(dtype_string, $1); $$->v.concat.arg[1] = cast_to(dtype_string, $3); } | simp_expr '-' simp_expr { $$ = alloc_node(node_type_bin, &@1); $$->v.bin.opcode = bin_sub; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } | simp_expr '*' simp_expr { $$ = alloc_node(node_type_bin, &@1); $$->v.bin.opcode = bin_mul; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } | simp_expr '/' simp_expr { $$ = alloc_node(node_type_bin, &@1); $$->v.bin.opcode = bin_div; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } | simp_expr '%' simp_expr { $$ = alloc_node(node_type_bin, &@1); $$->v.bin.opcode = bin_mod; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } | simp_expr T_LOGAND simp_expr { $$ = alloc_node(node_type_bin, &@1); $$->v.bin.opcode = bin_logand; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } | simp_expr T_LOGOR simp_expr { $$ = alloc_node(node_type_bin, &@1); $$->v.bin.opcode = bin_logor; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } | simp_expr T_LOGXOR simp_expr { $$ = alloc_node(node_type_bin, &@1); $$->v.bin.opcode = bin_logxor; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } | simp_expr T_SHL simp_expr { $$ = alloc_node(node_type_bin, &@1); $$->v.bin.opcode = bin_shl; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } | simp_expr T_SHR simp_expr { $$ = alloc_node(node_type_bin, &@1); $$->v.bin.opcode = bin_shr; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } ; atom_expr : funcall { if (node_type($1) == dtype_unspecified) parse_error(_("unspecified value not ignored as it should be")); } | '(' expr ')' { $$ = $2; } | T_TYPECAST '(' expr ')' { $$ = cast_to($1, $3); } | atom | '-' simp_expr %prec T_UMINUS { $$ = alloc_node(node_type_un, &@1); $$->v.un.opcode = unary_minus; $$->v.un.arg = cast_to(dtype_number, $2); } | '+' simp_expr %prec T_UMINUS { $$ = $2; } | T_LOGNOT simp_expr %prec T_UMINUS { $$ = alloc_node(node_type_un, &@1); $$->v.un.opcode = unary_lognot; $$->v.un.arg = cast_to(dtype_number, $2); } ; atom : T_SYMBOL { $$ = create_node_symbol($1, &@1); } | T_NUMBER { $$ = alloc_node(node_type_number, &@1); $$->v.number = $1; } | T_BACKREF { $$ = create_node_backref($1, &@1); } | argref | T_ARGCOUNT { $$ = create_node_argcount(&@1); } | '@' T_VARIABLE { $$ = alloc_node(node_type_offset, &@1); $$->v.var_ref.variable = $2; $$->v.var_ref.nframes = catch_nesting; } | T_VAPTR paren_argref { $$ = alloc_node(node_type_vaptr, &@1); $$->v.node = $2; } | strcat ; strcat : strval | strcat strval { $$ = alloc_node(node_type_concat, &@2); $$->v.concat.arg[0] = $1; $$->v.concat.arg[1] = $2; } ; strval : T_STRING { $$ = alloc_node(node_type_string, &@1); $$->v.literal = $1; } | T_COMPOSE ; argref : variable { $$ = create_node_variable($1, &@1); } | T_ARG { $$ = create_node_arg($1, &@1); } | T_ARGX '(' expr ')' { if (outer_context == context_function) { if (func->varargs) { $$ = alloc_node(node_type_argx, &@1); $$->v.argx.nargs = PARMCOUNT() + FUNC_HIDDEN_ARGS(func); $$->v.argx.node = $3; } else { $$ = alloc_node(node_type_noop, &@1); parse_error_locus(&@1, _("$(expr) is allowed only " "in a function with " "variable number of " "arguments")); } } else { $$ = alloc_node(node_type_noop, &@1); parse_error_locus(&@1, _("$(expr) is allowed only " "in a function with " "variable number of " "arguments")); } } ; paren_argref: argref | '(' argref ')' { $$ = $2; } ; funcall : T_BUILTIN '(' arglist ')' { if (check_builtin_usage($1, &@1)) YYERROR; if ($3.count < $1->parmcount - $1->optcount) { parse_error_locus(&@1, _("too few arguments in call to `%s'"), $1->name); YYERROR; } else if ($3.count > $1->parmcount && !($1->flags & MFD_BUILTIN_VARIADIC)) { parse_error_locus(&@1, _("too many arguments in call to `%s'"), $1->name); YYERROR; } else { $$ = alloc_node(node_type_builtin, &@1); $$->v.builtin.builtin = $1; $$->v.builtin.args = reverse(cast_arg_list($3.head, $1->parmcount, $1->parmtype, $$->v.builtin.builtin->flags & MFD_BUILTIN_NO_PROMOTE)); } } | T_BUILTIN '(' ')' { if (check_builtin_usage($1, &@1)) YYERROR; if ($1->parmcount - $1->optcount) { parse_error_locus(&@1, _("too few arguments in call to `%s'"), $1->name); YYERROR; } else { $$ = alloc_node(node_type_builtin, &@1); $$->v.builtin.builtin = $1; $$->v.builtin.args = NULL; } } | T_FUNCTION '(' arglist ')' { if (check_func_usage($1, &@1)) YYERROR; $$ = function_call($1, $3.count, $3.head); if (!$$) YYERROR; } | T_FUNCTION '(' ')' { if (check_func_usage($1, &@1)) YYERROR; $$ = function_call($1, 0, NULL); if (!$$) YYERROR; } ; arglist : expr { $1->next = NULL; $$.head = $$.tail = $1; $$.count = 1; } | arglist ',' expr { $1.tail->next = $3; $1.tail = $3; $1.count++; $$ = $1; } ; variable : T_VARIABLE { add_xref($1, &@1); } | T_BOGUS { YYERROR; } ; catch : simple_catch { if (outer_context == context_function) { func->exmask->all |= $$->v.catch.exmask->all; bitmask_merge(&func->exmask->bm, &$$->v.catch.exmask->bm); } } | try_block simple_catch { $$ = alloc_node(node_type_try, &@1); $$->v.try.node = $1; $$->v.try.catch = $2; } ; try_block : T_TRY T_DO stmtlist T_DONE { $$ = $3.head; } ; simple_catch : T_CATCH catchlist T_DO { $$ = inner_context; inner_context = context_catch; catch_nesting++; } stmtlist T_DONE { int i; struct valist *p; inner_context = $4; catch_nesting--; $$ = alloc_node(node_type_catch, &@1); $$->v.catch.exmask = exmask_create(); $$->v.catch.context = outer_context;/*??*/ $$->v.catch.exmask->all = $2.all; if (!$2.all) { for (i = 0, p = $2.valist; p; p = p->next, i++) { if (p->value.type != dtype_number) { parse_error_locus(&@1, _("expected numeric value, but found `%s'"), p->value.v.literal->text); continue; } bitmask_set(&$$->v.catch.exmask->bm, p->value.v.number); } } $$->v.catch.node = $5.head; } ; catchlist : '*' { $$.all = 1; } | valist { $$.all = 0; $$.valist = $1.head; } ; throw : T_THROW value expr { $$ = alloc_node(node_type_throw, &@1); if ($2.type != dtype_number) parse_error_locus(&@2, _("exception code not a number")); else if ($2.v.number > exception_count) parse_error_locus(&@2, _("invalid exception number: %lu"), $2.v.number); $$->v.throw.code = $2.v.number; $$->v.throw.expr = cast_to(dtype_string, $3); } ; return : T_RETURN { if (!func) parse_error_locus(&@1, _("`return' outside of a function")); else if (func->rettype != dtype_unspecified) parse_error_locus(&@1, _("`return' with no value, in function " "returning non-void")); $$ = alloc_node(node_type_return, &@1); $$->v.node = NULL; } | T_RETURN expr { if (!func) parse_error_locus(&@1, _("`return' outside of a function")); else { $$ = alloc_node(node_type_return, &@1); if (func->rettype == dtype_unspecified) { parse_error_locus(&@1, _("`return' with a value, in function " "returning void")); $$->v.node = NULL; } else $$->v.node = cast_to(func->rettype, $2); } } ; /* *************************** */ /* ON statement */ /* *************************** */ on_cond : on pollstmt do branches T_DONE { NODE *sel, *np; NODE *head = NULL, *tail; struct function *fp; fp = function_lookup($2.client_addr ? "strictpoll" : "stdpoll"); if (!fp) { parse_error_locus(&@1, _("`on poll' used without prior `require poll'")); YYERROR; } /* Build argument list */ if ($2.client_addr) { head = tail = $2.client_addr; tail = $2.email; if (!tail) { parse_error_locus(&@2, _("recipient address not specified " "in `on poll' construct")); YYERROR; } tail->next = NULL; head->next = tail; } else head = tail = $2.email; if ($2.ehlo) np = $2.ehlo; else { /* FIXME: Pass NULL? */ np = alloc_node(node_type_variable, &@2); np->v.var_ref.variable = variable_lookup("ehlo_domain"); np->v.var_ref.nframes = 0; } tail->next = np; tail = np; if ($2.mailfrom) np = $2.mailfrom; else { /* FIXME: Pass NULL? */ np = alloc_node(node_type_variable, &@2); np->v.var_ref.variable = variable_lookup("mailfrom_address"); np->v.var_ref.nframes = 0; } tail->next = np; tail = np; sel = function_call(fp, nodelistlength(head), head); $$ = alloc_node(node_type_switch, &@1); $$->v.switch_stmt.node = sel; $$->v.switch_stmt.cases = $4.head; } | on funcall do branches T_DONE { $$ = alloc_node(node_type_switch, &@1); $$->v.switch_stmt.node = $2; $$->v.switch_stmt.cases = $4.head; } ; on : T_ON { tie_in_onblock(1); } ; do : T_DO { tie_in_onblock(0); } ; pollstmt : T_POLL expr { struct pollarg arg; arg.kw = T_FOR; arg.expr = $2; memset(&$$, 0, sizeof $$); set_poll_arg(&$$, arg.kw, arg.expr); } | T_POLL expr pollarglist { struct pollarg arg; arg.kw = T_FOR; arg.expr = $2; set_poll_arg(&$3, arg.kw, arg.expr); $$ = $3; } | T_POLL pollarglist { $$ = $2; } ; pollarglist: pollarg { memset(&$$, 0, sizeof $$); set_poll_arg(&$$, $1.kw, $1.expr); } | pollarglist pollarg { set_poll_arg(&$1, $2.kw, $2.expr); $$ = $1; } ; pollarg : T_FOR expr { $$.kw = T_FOR; $$.expr = $2; } | T_HOST expr { $$.kw = T_HOST; $$.expr = $2; } | T_AS expr { $$.kw = T_AS; $$.expr = $2; } | T_FROM expr { $$.kw = T_FROM; $$.expr = $2; } ; branches : branch { $$.head = $$.tail = $1; } | branches branch { $1.tail->next = $2; $1.tail = $2; $$ = $1; } ; branch : T_WHEN valist ':' stmtlist { struct valist *p; for (p = $2.head; p; p = p->next) { if (p->value.type == dtype_string) { parse_error_locus(&@2, _("invalid data type, " "expected number")); /* Try to continue */ p->value.type = dtype_number; p->value.v.number = 0; } } $$ = mu_alloc(sizeof *$$); $$->next = NULL; mu_locus_range_init (&$$->locus); mu_locus_range_copy (&$$->locus, &@1); $$->valist = $2.head; $$->node = $4.head; } ; %% int yyerror(char const *s) { parse_error("%s", s); return 0; } struct stream_state { int mode; struct mu_locus_range loc; int sevmask; }; void stream_state_save(mu_stream_t str, struct stream_state *st) { mu_stream_ioctl(mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_GET_MODE, &st->mode); mu_locus_range_init(&st->loc); mu_stream_ioctl(mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_GET_LOCUS_RANGE, &st->loc); mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_GET_SEVERITY_MASK, &st->sevmask); } void stream_state_restore(mu_stream_t str, struct stream_state *st) { mu_stream_ioctl(mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_SET_MODE, &st->mode); mu_stream_ioctl(mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_SET_LOCUS_RANGE, &st->loc); mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_SET_SEVERITY_MASK, &st->sevmask); mu_locus_range_deinit(&st->loc); } int parse_program(char *name, int ydebug) { int rc; struct stream_state st; int mode; stream_state_save(mu_strerr, &st); mode = st.mode | MU_LOGMODE_LOCUS | MU_LOGMODE_SEVERITY; mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_SET_MODE, &mode); mode = MU_DEBUG_LEVEL_MASK (MU_DIAG_ERROR); mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_SET_SEVERITY_MASK, &mode); yydebug = ydebug; if (lex_new_source(name, 0)) return -1; outer_context = inner_context = context_none; catch_nesting = 0; code_immediate(NULL, ptr); /* Reserve 0 slot */ rc = yyparse() + error_count; stream_state_restore(mu_strerr, &st); return rc; } static void alloc_locus(struct mu_locus_range const *locus) { struct literal *lit; if (locus->beg.mu_file) { lit = string_alloc(locus->beg.mu_file, strlen(locus->beg.mu_file)); lit->flags |= SYM_REFERENCED; } if (!mu_locus_point_same_file(&locus->beg, &locus->end)) { lit = string_alloc(locus->end.mu_file, strlen(locus->end.mu_file)); lit->flags |= SYM_REFERENCED; } } NODE * alloc_node(enum node_type type, struct mu_locus_range const *locus) { NODE *node = malloc(sizeof(*node)); if (!node) { yyerror("Not enough memory"); abort(); } node->type = type; mu_locus_range_init (&node->locus); mu_locus_range_copy (&node->locus, locus); alloc_locus(locus); node->next = NULL; return node; } void free_node(NODE *node) { mu_locus_range_deinit(&node->locus); free(node); } void copy_node(NODE *dest, NODE *src) { dest->type = src->type; mu_locus_range_copy (&dest->locus, &src->locus); dest->v = src->v; } void free_subtree(NODE *node) { /*FIXME*/ } void free_parser_data() { /*FIXME*/ } /* Print parse tree */ static void print_node_list(NODE *node, int indent); static void print_node_list_reverse(NODE *node, int level); static void print_node(NODE *node, int indent); void print_stat(sfsistat stat); static int dbg_setreply(void *data, char *code, char *xcode, char *message); static void dbg_msgmod(void *data, struct msgmod_closure *clos); static void print_level(int level) { level *= 2; printf("%*.*s", level, level, ""); } static void print_bin_op(enum bin_opcode opcode) { char *p; switch (opcode) { case bin_and: p = "AND"; break; case bin_or: p = "OR"; break; case bin_eq: p = "EQ"; break; case bin_ne: p = "NE"; break; case bin_lt: p = "LT"; break; case bin_le: p = "LE"; break; case bin_gt: p = "GT"; break; case bin_ge: p = "GE"; break; case bin_match: p = "MATCH"; break; case bin_fnmatch: p = "FNMATCH"; break; case bin_add: p = "ADD"; break; case bin_sub: p = "SUB"; break; case bin_mul: p = "MUL"; break; case bin_div: p = "DIV"; break; case bin_mod: p = "MOD"; break; case bin_logand: p = "LOGAND"; break; case bin_logor: p = "LOGOR"; break; case bin_logxor: p = "LOGXOR"; break; case bin_shl: p = "SHL"; break; case bin_shr: p = "SHR"; break; default: p = "UNKNOWN_OP"; } printf("%s", p); } static void print_quoted_string(const char *str) { for (; *str; str++) { if (mu_isprint(*str)) putchar(*str); else { putchar('\\'); switch (*str) { case '\a': putchar('a'); break; case '\b': putchar('b'); break; case '\f': putchar('f'); break; case '\n': putchar('n'); break; case '\r': putchar('r'); break; case '\t': putchar('t'); break; default: printf("%03o", *str); } } } } struct node_drv { void (*print) (NODE *, int); void (*mark) (NODE *); void (*code) (NODE *, struct mu_locus_range const **); void (*optimize) (NODE *); }; static void traverse_tree(NODE *node); static void code_node(NODE *node); static void code_node(NODE *node); static void optimize_node(NODE *node); static void optimize(NODE *node); static void record_switch(struct switch_stmt *sw); #include "drivers.c" #include "node-tab.c" struct node_drv * find_node_drv(enum node_type type) { if (type >= NELEMS(nodetab)) { parse_error(_("INTERNAL ERROR at %s:%d, " "unexpected node type %d"), __FILE__, __LINE__, type); abort(); } return nodetab + type; } static void print_node(NODE *node, int level) { struct node_drv *nd = find_node_drv(node->type); if (nd->print) nd->print(node, level); } static void print_node_list(NODE *node, int level) { for (; node; node = node->next) print_node(node, level); } static void print_node_list_reverse(NODE *node, int level) { if (node) { print_node_list_reverse(node->next, level); print_node(node, level); } } int function_enumerator(void *sym, void *data) { int i; struct function *fsym = sym; struct function *f = (struct function *)symbol_resolve_alias(&fsym->sym); struct module *mod = data; if (f->sym.module != mod) return 0; printf("function %s (", f->sym.name); for (i = 0; i < f->parmcount; i++) { printf("%s", type_to_string(f->parmtype[i])); if (i < f->parmcount-1) putchar(','); } putchar(')'); if (f->rettype != dtype_unspecified) printf(" returns %s", type_to_string(f->rettype)); printf(":\n"); print_node_list(f->node, 0); printf("END function %s\n", f->sym.name); return 0; } void print_syntax_tree() { struct module **modv; size_t i, modc; enum smtp_state tag; printf("State handlers:\n"); printf("---------------\n"); for (tag = smtp_state_first; tag < smtp_state_count; tag++) { if (root_node[tag]) { printf("%s:\n", state_to_string(tag)); print_node_list(root_node[tag], 0); putchar('\n'); } } printf("User functions:\n"); printf("---------------\n"); collect_modules(&modv, &modc); for (i = 0; i < modc; i++) { size_t n; n = printf("Module %s (%s)\n", modv[i]->name, modv[i]->file); while (n--) putchar('-'); putchar('\n'); symtab_enumerate(MODULE_SYMTAB(modv[i], namespace_function), function_enumerator, modv[i]); putchar('\n'); } free(modv); } /* Cross-reference support */ struct collect_data { mu_opool_t pool; size_t count; }; static int variable_enumerator(void *item, void *data) { struct variable *var = item; struct collect_data *p = data; if (var->sym.flags & (SYM_VOLATILE | SYM_REFERENCED)) { mu_opool_append(p->pool, &var, sizeof var); p->count++; } return 0; } int print_locus(void *item, void *data) { struct mu_locus_range *loc = item; struct mu_locus_range **prev = data; int c; if (!*prev) { *prev = loc; printf("%s", loc->beg.mu_file); c = ':'; } else if (mu_locus_point_same_file (&(*prev)->beg, &loc->beg)) { *prev = loc; printf(", %s", loc->beg.mu_file); c = ':'; } else c = ','; printf("%c%u", c, loc->beg.mu_line); return 0; } void print_xref_var(struct variable *var) { struct mu_locus_range *prev = NULL; var = (struct variable *)symbol_resolve_alias(&var->sym); printf("%-32.32s %6s %lu ", var->sym.name, type_to_string(var->type), (unsigned long)var->off); mu_list_foreach(var->xref, print_locus, &prev); printf("\n"); } int vp_comp(const void *a, const void *b) { struct variable * const *va = a, * const *vb = b; return strcmp((*va)->sym.name, (*vb)->sym.name); } void print_xref() { struct collect_data cd; struct variable **vp; size_t i; mu_opool_create(&cd.pool, MU_OPOOL_ENOMEMABRT); cd.count = 0; symtab_enumerate(TOP_MODULE_SYMTAB(namespace_variable), variable_enumerator, &cd); printf("Cross-references:\n"); printf("-----------------\n"); vp = mu_opool_finish(cd.pool, NULL); qsort(vp, cd.count, sizeof *vp, vp_comp); for (i = 0; i < cd.count; i++, vp++) print_xref_var(*vp); mu_opool_destroy(&cd.pool); } static mu_list_t smtp_macro_list[gacopyz_stage_max]; static enum gacopyz_stage smtp_to_gacopyz_stage(enum smtp_state tag) { switch (tag) { case smtp_state_connect: return gacopyz_stage_conn; case smtp_state_helo: return gacopyz_stage_helo; case smtp_state_envfrom: return gacopyz_stage_mail; case smtp_state_envrcpt: return gacopyz_stage_rcpt; case smtp_state_data: case smtp_state_header: case smtp_state_body: return gacopyz_stage_data; case smtp_state_eoh: return gacopyz_stage_eoh; case smtp_state_eom: return gacopyz_stage_eom; default: break; } return gacopyz_stage_none; } static int compare_macro_names (const void *item, const void *data) { const char *elt; size_t elen; const char *arg; size_t alen; elt = item; elen = strlen (elt); if (elt[0] == '{') { elt++; elen -= 2; } arg = data; alen = strlen (arg); if (arg[0] == '{') { arg++; alen -= 2; } if (alen != elen) return 1; return memcmp (elt, arg, alen); } void register_macro(enum smtp_state state, const char *macro) { enum gacopyz_stage ind = smtp_to_gacopyz_stage(state); if (ind == gacopyz_stage_none) return; if (!smtp_macro_list[ind]) { mu_list_create(&smtp_macro_list[ind]); mu_list_set_comparator(smtp_macro_list[ind], compare_macro_names); } /* FIXME: MU: 2nd arg should be const? */ if (mu_list_locate(smtp_macro_list[ind], (void*) macro, NULL)) { char *cmacro; if (macro[1] == 0 || macro[0] == '{') cmacro = mu_strdup (macro); else { size_t mlen = strlen (macro); cmacro = mu_alloc (mlen + 3); cmacro[0] = '{'; memcpy (cmacro + 1, macro, mlen); cmacro[mlen + 1] = '}'; cmacro[mlen + 2] = 0; } mu_list_append(smtp_macro_list[ind], cmacro); } } static int print_macro(void *item, void *data) { int *p = data; if (*p) { printf(" "); *p = 0; } else printf(", "); printf("%s", (char*) item); return 0; } void print_used_macros() { enum gacopyz_stage i; for (i = 0; i < gacopyz_stage_max; i++) { if (smtp_macro_list[i]) { int n = 1; printf("%s", gacopyz_stage_name[i]); mu_list_foreach(smtp_macro_list[i], print_macro, &n); printf("\n"); } } } struct macro_acc { size_t size; char *buf; }; static int add_macro_size(void *item, void *data) { char *macro = (char*) item; struct macro_acc *mp = data; mp->size += strlen(macro) + 1; return 0; } static int concat_macro(void *item, void *data) { char *macro = (char*) item; struct macro_acc *mp = data; size_t len = strlen(macro); char *pbuf = mp->buf + mp->size; memcpy(pbuf, macro, len); pbuf[len++] = ' '; mp->size += len; return 0; } char * get_stage_macro_string(enum gacopyz_stage i) { struct macro_acc acc; size_t size; mu_list_t list = smtp_macro_list[i]; if (!list) return NULL; acc.size = 0; mu_list_foreach(list, add_macro_size, &acc); if (!acc.size) return NULL; size = acc.size; acc.size = 0; acc.buf = mu_alloc (size); mu_list_foreach(list, concat_macro, &acc); acc.buf[size-1] = 0; return acc.buf; } /* Code generation */ static void code_node(NODE *node) { if (!node) error_count++; else { static struct mu_locus_range const *old_locus; struct node_drv *nd = find_node_drv(node->type); if (nd->code) nd->code(node, &old_locus); } } static void traverse_tree(NODE *node) { for (; node; node = node->next) code_node(node); } static void optimize_node(NODE *node) { if (!node) error_count++; else { struct node_drv *nd = find_node_drv(node->type); if (nd->optimize) nd->optimize(node); } } static void optimize(NODE *node) { for (; node; node = node->next) optimize_node(node); } static int optimize_tree(NODE *node) { if (optimization_level) optimize(node); return error_count; } static struct switch_stmt *switch_root; static void record_switch(struct switch_stmt *sw) { sw->next = switch_root; switch_root = sw; } static struct exmask *exmask_root; struct exmask * exmask_create() { struct exmask *p = mu_alloc(sizeof(*p)); p->next = exmask_root; p->off = 0; p->all = 0; bitmask_init(&p->bm); exmask_root = p; return p; } static void mark_node(NODE *node) { if (!node) error_count++; else { struct node_drv *nd = find_node_drv(node->type); if (nd->mark) nd->mark(node); } } static void mark(NODE *node) { for (; node; node = node->next) mark_node(node); } static int codegen(prog_counter_t *pc, NODE *node, struct exmask *exmask, int finalize, size_t nautos) { int save_mask; if (error_count) return 1; *pc = code_get_counter(); jump_pc = 0; if (nautos) { code_op(opcode_stkalloc); code_immediate(nautos, uint); } save_mask = exmask && bitmask_nset(&exmask->bm); if (save_mask) { code_op(opcode_saveex); code_exmask(exmask); } traverse_tree(node); jump_fixup(jump_pc, code_get_counter()); if (save_mask) code_op(opcode_restex); if (finalize) code_op(opcode_nil); else code_op(opcode_return); return 0; } static void compile_tree(NODE *node) { struct node_drv *nd; struct mu_locus_range const *plocus; for (; node; node = node->next) { switch (node->type) { case node_type_progdecl: case node_type_funcdecl: nd = find_node_drv(node->type); if (!nd->code) abort(); nd->code(node, &plocus); break; default: parse_error_locus(&node->locus, _("INTERNAL ERROR at %s:%d, " "unexpected node type %d"), __FILE__, __LINE__, node->type); break; } } } enum regex_mode { regex_enable, regex_disable, regex_set }; static mf_stack_t regex_stack; void regex_push() { if (!regex_stack) regex_stack = mf_stack_create(sizeof regex_flags, 0); mf_stack_push(regex_stack, ®ex_flags); } void regex_pop() { if (!regex_stack || mf_stack_pop(regex_stack, ®ex_flags)) parse_error(_("nothing to pop")); } static void pragma_regex(int argc, char **argv, const char *text) { enum regex_mode mode = regex_set; int i = 1; if (strcmp(argv[i], "push") == 0) { regex_push(); i++; } else if (strcmp(argv[i], "pop") == 0) { regex_pop(); i++; } for (; i < argc; i++) { int bit; char *p = argv[i]; switch (p[0]) { case '+': mode = regex_enable; p++; break; case '-': mode = regex_disable; p++; break; case '=': mode = regex_set; p++; break; } if (strcmp (p, REG_EXTENDED_NAME) == 0) bit = REG_EXTENDED; else if (strcmp (p, REG_ICASE_NAME) == 0) bit = REG_ICASE; else if (strcmp (p, REG_NEWLINE_NAME) == 0) bit = REG_NEWLINE; else { parse_error(_("unknown regexp flag: %s"), p); return; } switch (mode) { case regex_disable: regex_flags &= ~bit; break; case regex_enable: regex_flags |= bit; break; case regex_set: regex_flags = bit; break; } } } static int strtosize(const char *text, size_t *psize) { unsigned long n; size_t size; char *p; n = strtoul(text, &p, 0); size = n; switch (*p) { case 't': case 'T': size *= 1024; case 'g': case 'G': size *= 1024; case 'm': case 'M': size *= 1024; case 'k': case 'K': size *= 1024; p++; if (*p && (*p == 'b' || *p == 'B')) p++; break; case 0: break; default: parse_error(_("invalid size suffix (near %s)"), p); return 1; } if (size < n) { parse_error(_("invalid size: numeric overflow occurred")); return 2; } *psize = size; return 0; } static void pragma_stacksize(int argc, char **argv, const char *text) { size_t size, incr = stack_expand_incr, max_size = stack_max_size; enum stack_expand_policy policy = stack_expand_policy; switch (argc) { case 4: if (strtosize(argv[3], &max_size)) return; case 3: if (strcmp(argv[2], "twice") == 0) policy = stack_expand_twice; else { policy = stack_expand_add; if (strtosize(argv[2], &incr)) return; } case 2: strtosize(argv[1], &size); } stack_size = size; stack_expand_incr = incr; stack_expand_policy = policy; stack_max_size = max_size; } void pragma_setup() { install_pragma("regex", 2, 0, pragma_regex); install_pragma("stacksize", 2, 4, pragma_stacksize); } /* Test run */ struct sfsistat_tab { char *name; sfsistat stat; } sfsistat_tab[] = { { "accept", SMFIS_ACCEPT }, { "continue", SMFIS_CONTINUE }, { "discard", SMFIS_DISCARD }, { "reject", SMFIS_REJECT }, { "tempfail", SMFIS_TEMPFAIL }, { NULL } }; const char * sfsistat_str(sfsistat stat) { struct sfsistat_tab *p; for (p = sfsistat_tab; p->name; p++) if (p->stat == stat) return p->name; return NULL; } void print_stat(sfsistat stat) { struct sfsistat_tab *p; for (p = sfsistat_tab; p->name; p++) if (p->stat == stat) { printf("%s", p->name); return; } printf("%d", stat); } const char * msgmod_opcode_str(enum msgmod_opcode opcode) { switch (opcode) { case header_add: return "ADD HEADER"; case header_replace: return "REPLACE HEADER"; case header_delete: return "DELETE HEADER"; case header_insert: return "INSERT HEADER"; case rcpt_add: return "ADD RECIPIENT"; case rcpt_delete: return "DELETE RECIPIENT"; case quarantine: return "QUARANTINE"; case body_repl: return "REPLACE BODY"; case body_repl_fd: return "REPLACE BODY FROM FILE"; case set_from: return "SET FROM"; } return "UNKNOWN HEADER COMMAND"; } static int dbg_setreply(void *data, char *code, char *xcode, char *message) { if (code) { printf("SET REPLY %s", code); if (xcode) printf(" %s", xcode); if (message) printf(" %s", message); printf("\n"); } return 0; } static void dbg_msgmod(void *data, struct msgmod_closure *clos) { if (!clos) printf("clearing msgmod list\n"); else printf("%s %s: %s %u\n", msgmod_opcode_str(clos->opcode), SP(clos->name), SP(clos->value), clos->idx); } static const char * dbg_dict_getsym (void *data, const char *str) { return dict_getsym ((mu_assoc_t)data, str); } void mailfromd_test(int argc, char **argv) { int i; mu_assoc_t dict = NULL; eval_environ_t env; char *p, *end; long n; sfsistat status; char *args[9] = {0,0,0,0,0,0,0,0,0}; dict_init(&dict); env = create_environment(NULL, dbg_dict_getsym, dbg_setreply, dbg_msgmod, dict); env_init(env); xeval(env, smtp_state_begin); env_init(env); for (i = 0; i < argc; i++) { if (p = strchr(argv[i], '=')) { char *ident = argv[i]; *p++ = 0; if (mu_isdigit(*ident) && *ident != 0 && ident[1] == 0) args[*ident - '0' - 1] = p; else dict_install(dict, argv[i], p); } } for (i = state_parms[test_state].cnt; i--; ) { switch (state_parms[test_state].types[i]) { case dtype_string: env_push_string(env, args[i] ? args[i] : ""); break; case dtype_number: if (args[i]) { n = strtol(args[i], &end, 0); if (*end) mu_error(_("$%d is not a number"), i+1); } else n = 0; env_push_number(env, n); break; default: abort(); } } test_message_data_init(env); env_make_frame(env); xeval(env, test_state); env_leave_frame(env, state_parms[test_state].cnt); env_final_gc(env); status = environment_get_status(env); env_init(env); xeval(env, smtp_state_end); printf("State %s: ", state_to_string(test_state)); print_stat(status); printf("\n"); destroy_environment(env); } void mailfromd_run(prog_counter_t entry_point, int argc, char **argv) { int rc, i; mu_assoc_t dict = NULL; eval_environ_t env; dict_init(&dict); env = create_environment(NULL, dbg_dict_getsym, dbg_setreply, dbg_msgmod, dict); env_init(env); test_message_data_init(env); env_push_number(env, 0); for (i = argc - 1; i >= 0; i--) env_push_string(env, argv[i]); env_push_number(env, argc); env_make_frame0(env); rc = eval_environment(env, entry_point); env_final_gc(env); rc = mf_c_val(env_get_reg(env), int); destroy_environment(env); exit(rc); } static struct tagtable { char *name; enum smtp_state tag; } tagtable[] = { { "none", smtp_state_none }, { "begin", smtp_state_begin }, { "connect", smtp_state_connect }, { "helo", smtp_state_helo }, { "envfrom", smtp_state_envfrom }, { "envrcpt", smtp_state_envrcpt }, { "data", smtp_state_data }, { "header", smtp_state_header }, { "eoh", smtp_state_eoh }, { "body", smtp_state_body }, { "eom", smtp_state_eom }, { "end", smtp_state_end }, }; enum smtp_state string_to_state(const char *name) { struct tagtable *p; for (p = tagtable; p < tagtable + sizeof tagtable/sizeof tagtable[0]; p++) if (strcasecmp (p->name, name) == 0) return p->tag; return smtp_state_none; } const char * state_to_string(enum smtp_state state) { if (state < sizeof tagtable/sizeof tagtable[0]) return tagtable[state].name; abort(); } static NODE * _reverse(NODE *list, NODE **root) { NODE *next; if (list->next == NULL) { *root = list; return list; } next = _reverse(list->next, root); next->next = list; list->next = NULL; return list; } NODE * reverse(NODE *in) { NODE *root; if (!in) return in; _reverse(in, &root); return root; } size_t nodelistlength(NODE *p) { size_t len = 0; for (; p; p = p->next) len++; return len; } static NODE * create_asgn_node(struct variable *var, NODE *expr, struct mu_locus_range const *loc) { NODE *node; data_type_t t = node_type(expr); if (t == dtype_unspecified) { parse_error(_("unspecified value not ignored as it should be")); return NULL; } node = alloc_node(node_type_asgn, loc); node->v.asgn.var = var; node->v.asgn.nframes = catch_nesting; node->v.asgn.node = cast_to(var->type, expr); return node; } NODE * function_call(struct function *function, size_t count, NODE *subtree) { NODE *np = NULL; if (count < function->parmcount - function->optcount) { parse_error(_("too few arguments in call to `%s'"), function->sym.name); } else if (count > function->parmcount && !function->varargs) { parse_error(_("too many arguments in call to `%s'"), function->sym.name); } else { np = alloc_node(node_type_call, &yylloc); np->v.call.func = function; np->v.call.args = reverse(cast_arg_list(subtree, function->parmcount, function->parmtype, 0)); } return np; } data_type_t node_type(NODE *node) { switch (node->type) { case node_type_string: case node_type_symbol: return dtype_string; case node_type_number: return dtype_number; case node_type_if: return dtype_unspecified; case node_type_bin: return dtype_number; case node_type_un: return dtype_number; case node_type_builtin: return node->v.builtin.builtin->rettype; case node_type_concat: return dtype_string; case node_type_variable: return node->v.var_ref.variable->type; case node_type_arg: return node->v.arg.data_type; case node_type_argx: return dtype_string; case node_type_call: return node->v.call.func->rettype; case node_type_return: if (node->v.node) return node_type(node->v.node); break; case node_type_backref: return dtype_string; case node_type_cast: return node->v.cast.data_type; case node_type_offset: return dtype_number; case node_type_vaptr: return dtype_number; case node_type_result: case node_type_header: case node_type_asgn: case node_type_regex: case node_type_regcomp: case node_type_catch: case node_type_try: case node_type_throw: case node_type_echo: case node_type_switch: case node_type_funcdecl: case node_type_progdecl: case node_type_noop: case node_type_next: case node_type_break: case node_type_loop: case max_node_type: break; } return dtype_unspecified; } NODE * cast_to(data_type_t type, NODE *node) { NODE *np; data_type_t ntype = node_type(node); switch (ntype) { case dtype_string: case dtype_number: if (type == ntype) return node; break; case dtype_pointer: if (type == ntype) return node; break; case dtype_unspecified: parse_error(_("cannot convert %s to %s"), type_to_string(ntype), type_to_string(type)); return NULL; default: abort(); } np = alloc_node(node_type_cast, &yylloc); np->v.cast.data_type = type; np->v.cast.node = node; node->next = NULL; return np; } NODE * cast_arg_list(NODE *args, size_t parmc, data_type_t *parmtype, int disable_prom) { NODE *head = NULL, *tail = NULL; while (args) { NODE *next = args->next; NODE *p; data_type_t type; if (parmc) { type = *parmtype++; parmc--; } else if (disable_prom) type = node_type(args); else type = dtype_string; p = cast_to(type, args); if (head) tail->next = p; else head = p; tail = p; args = next; } return head; } void add_xref(struct variable *var, struct mu_locus_range const *locus) { if (script_dump_xref) { /* FIXME: either change type to mu_locus_point, or change print_locus above */ struct mu_locus_range *elt = mu_zalloc(sizeof *elt); if (!var->xref) mu_list_create(&var->xref); mu_locus_range_copy(elt, locus); mu_list_append(var->xref, elt); } } struct variable * vardecl(const char *name, data_type_t type, storage_class_t sc, struct mu_locus_range const *loc) { struct variable *var; const struct constant *cptr; if (!loc) loc = &yylloc; if (type == dtype_unspecified) { parse_error(_("cannot define variable of unspecified type")); return NULL; } var = variable_install(name); if (var->type == dtype_unspecified) { /* the variable has just been added: go straight to initializing it */; } else if (sc != var->storage_class) { struct variable *vp; switch (sc) { case storage_extern: parse_error(_("INTERNAL ERROR at %s:%d, declaring %s %s"), __FILE__, __LINE__, storage_class_str(sc), name); abort(); case storage_auto: if (var->storage_class == storage_param) { parse_warning_locus(loc, _("automatic variable `%s' " "is shadowing a parameter"), var->sym.name); } else parse_warning_locus(loc, _("automatic variable `%s' " "is shadowing a global"), var->sym.name); unregister_auto(var); break; case storage_param: parse_warning_locus(loc, _("parameter `%s' is shadowing a " "global"), name); } /* Do the shadowing */ vp = variable_replace(var->sym.name, NULL); vp->shadowed = var; var = vp; } else { switch (sc) { case storage_extern: if (var->type != type) { parse_error_locus(loc, _("redeclaring `%s' as different " "data type"), name); parse_error_locus(&var->sym.locus, _("this is the location of the " "previous definition")); return NULL; } break; case storage_auto: if (var->type != type) { parse_error_locus(loc, _("redeclaring `%s' as different " "data type"), name); parse_error_locus(&var->sym.locus, _("this is the location of the " "previous definition")); return NULL; } else { parse_error_locus(loc, _("duplicate variable: %s"), name); return NULL; } break; case storage_param: parse_error_locus(loc, _("duplicate parameter: %s"), name); return NULL; } } /* FIXME: This is necessary because constants can be referred to the same way as variables. */ if (cptr = constant_lookup(name)) { parse_warning_locus(loc, _("variable name `%s' clashes with a constant name"), name); parse_warning_locus(&cptr->sym.locus, _("this is the location of the " "previous definition")); } var->type = type; var->storage_class = sc; switch (sc) { case storage_extern: add_xref(var, loc); break; case storage_auto: case storage_param: register_auto(var); } mu_locus_range_copy(&var->sym.locus, loc); return var; } static int cast_value(data_type_t type, struct value *value) { if (type != value->type) { char buf[NUMERIC_BUFSIZE_BOUND]; char *p; switch (type) { default: abort(); case dtype_string: snprintf(buf, sizeof buf, "%ld", value->v.number); value->v.literal = string_alloc(buf, strlen(buf)); break; case dtype_number: value->v.number = strtol(value->v.literal->text, &p, 10); if (*p) { parse_error(_("cannot convert `%s' to number"), value->v.literal->text); return 1; } break; } value->type = type; } return 0; } static struct variable * externdecl(const char *name, struct value *value, struct mu_locus_range const *loc) { struct variable *var = vardecl(name, value->type, storage_extern, loc); if (!var) return NULL; if (initialize_variable(var, value, loc)) return NULL; return var; } struct deferred_decl { struct deferred_decl *next; struct literal *name; struct value value; struct mu_locus_range locus; }; struct deferred_decl *deferred_decl; void defer_initialize_variable(const char *arg, const char *val, struct mu_locus_range const *ploc) { struct deferred_decl *p; struct literal *name = string_alloc(arg, strlen(arg)); for (p = deferred_decl; p; p = p->next) if (p->name == name) { parse_warning_locus(NULL, _("redefining variable %s"), name->text); p->value.type = dtype_string; p->value.v.literal = string_alloc(val, strlen(val)); mu_locus_range_copy (&p->locus, ploc); return; } p = mu_alloc(sizeof *p); p->name = name; p->value.type = dtype_string; p->value.v.literal = string_alloc(val, strlen(val)); mu_locus_range_init (&p->locus); mu_locus_range_copy (&p->locus, ploc); p->next = deferred_decl; deferred_decl = p; } static void apply_deferred_init() { struct deferred_decl *p; for (p = deferred_decl; p; p = p->next) { struct variable *var = variable_lookup(p->name->text); if (!var) { mu_error(_(": warning: " "no such variable: %s"), p->name->text); continue; } if (initialize_variable(var, &p->value, &p->locus)) parse_error_locus(&p->locus, _("error initialising variable %s: incompatible types"), p->name->text); } } struct declvar { struct declvar *next; struct mu_locus_range locus; struct variable *var; struct value val; }; static struct declvar *declvar; void set_poll_arg(struct poll_data *poll, int kw, NODE *expr) { switch (kw) { case T_FOR: poll->email = expr; break; case T_HOST: poll->client_addr = expr; break; case T_AS: poll->mailfrom = expr; break; case T_FROM: poll->ehlo = expr; break; default: abort(); } } int initialize_variable(struct variable *var, struct value *val, struct mu_locus_range const *locus) { struct declvar *dv; if (cast_value(var->type, val)) return 1; for (dv = declvar; dv; dv = dv->next) if (dv->var == var) { if (dv->locus.beg.mu_file) { parse_warning_locus(locus, _("variable `%s' already initialized"), var->sym.name); parse_warning_locus(&dv->locus, _("this is the location of the " "previous initialization")); } if (locus) mu_locus_range_copy (&dv->locus, locus); else mu_locus_range_deinit (&dv->locus); dv->val = *val; return 0; } dv = mu_alloc(sizeof *dv); dv->next = declvar; dv->var = var; mu_locus_range_init (&dv->locus); if (locus) mu_locus_range_copy (&dv->locus, locus); dv->val = *val; declvar = dv; var->sym.flags |= SYM_INITIALIZED; return 0; } void ensure_initialized_variable(const char *name, struct value *val) { struct declvar *dv; struct variable *var = variable_lookup(name); if (!var) { mu_error(_("INTERNAL ERROR at %s:%d: variable to be " "initialized is not declared"), __FILE__, __LINE__); abort(); } if (var->type != val->type) mu_error(_("INTERNAL ERROR at %s:%d: variable to be " "initialized has wrong type"), __FILE__, __LINE__); for (dv = declvar; dv; dv = dv->next) if (dv->var == var) return; dv = mu_alloc(sizeof *dv); mu_locus_range_init (&dv->locus); dv->var = var; dv->val = *val; dv->next = declvar; declvar = dv; } static int _ds_variable_count_fun(void *sym, void *data) { struct variable *var = sym; if ((var->sym.flags & (SYM_VOLATILE | SYM_REFERENCED)) && !(var->sym.flags & SYM_PASSTOGGLE)) { var->sym.flags |= SYM_PASSTOGGLE; variable_count++; if (var->type == dtype_string) dataseg_reloc_count++; if (var->sym.flags & SYM_PRECIOUS) precious_count++; } return 0; } static int _ds_variable_fill_fun(void *sym, void *data) { struct variable *var = sym; if (var->sym.flags & SYM_PASSTOGGLE) { var->sym.flags &= ~SYM_PASSTOGGLE; struct variable ***vtabptr = data; **vtabptr = var; ++*vtabptr; } return 0; } static int _ds_reloc_fun(void *sym, void *data) { struct variable *var = sym; size_t *pi = data; if ((var->sym.flags & (SYM_VOLATILE | SYM_REFERENCED)) && !(var->sym.flags & SYM_PASSTOGGLE) && var->type == dtype_string) { var->sym.flags |= SYM_PASSTOGGLE; dataseg_reloc[(*pi)++] = var->off; } return 0; } static int _ds_literal_count_fun(void *sym, void *data) { struct literal *lit = sym; size_t *offset = data; if (!(lit->flags & SYM_VOLATILE) && (lit->flags & SYM_REFERENCED)) { lit->off = *offset; *offset += B2STACK(strlen(lit->text) + 1); } return 0; } static int _ds_literal_copy_fun(void *sym, void *data) { struct literal *lit = sym; if (!(lit->flags & SYM_VOLATILE) && (lit->flags & SYM_REFERENCED)) strcpy((char*)(dataseg + lit->off), lit->text); return 0; } static int vtab_comp(const void *a, const void *b) { const struct variable *vp1 = *(const struct variable **)a; const struct variable *vp2 = *(const struct variable **)b; if ((vp1->sym.flags & SYM_PRECIOUS) && !(vp2->sym.flags & SYM_PRECIOUS)) return 1; else if ((vp2->sym.flags & SYM_PRECIOUS) && !(vp1->sym.flags & SYM_PRECIOUS)) return -1; return 0; } static int place_exc(const struct constant *cp, const struct literal *lit, void *data) { STKVAL *tab = data; tab[cp->value.v.number] = (STKVAL) lit->off; return 0; } static void dataseg_layout() { struct declvar *dv; size_t i; struct switch_stmt *sw; struct variable **vtab, **pvtab; struct exmask *exmask; /* Count used variables and estimate the number of relocations needed */ dataseg_reloc_count = 0; module_symtab_enumerate(namespace_variable, _ds_variable_count_fun, NULL); /* Fill variable pointer array and make sure precious variables occupy its bottom part */ vtab = mu_calloc(variable_count, sizeof(vtab[0])); pvtab = vtab; module_symtab_enumerate(namespace_variable, _ds_variable_fill_fun, &pvtab); qsort(vtab, variable_count, sizeof(vtab[0]), vtab_comp); /* Compute variable offsets. Offset 0 is reserved for NULL symbol */ for (i = 0; i < variable_count; i++) { vtab[i]->off = i + 1; if (vtab[i]->addrptr) *vtab[i]->addrptr = vtab[i]->off; } /* Free the array */ free(vtab); /* Mark literals used to initialize variables as referenced */ for (dv = declvar; dv; dv = dv->next) { if ((dv->var->sym.flags & (SYM_VOLATILE | SYM_REFERENCED)) && dv->var->type == dtype_string) { dv->val.v.literal->flags |= SYM_REFERENCED; } } datasize = variable_count + 1; dvarsize = datasize - precious_count; /* Count referenced literals and adjust the data size */ symtab_enumerate(stab_literal, _ds_literal_count_fun, &datasize); /* Account for switch translation tables */ for (sw = switch_root; sw; sw = sw->next) { sw->off = datasize; datasize += sw->tabsize; } /* Account for exception masks */ for (exmask = exmask_root; exmask; exmask = exmask->next) { exmask->off = datasize; if (exmask->all) { size_t i; for (i = 0; i < exception_count; i++) bitmask_set(&exmask->bm, i); } datasize += exmask->bm.bm_size + 1; } /* Account for exception name table */ datasize += exception_count; /* Allocate data segment and relocation table */ dataseg = mu_calloc(datasize, sizeof(STKVAL)); dataseg_reloc = mu_calloc(dataseg_reloc_count, sizeof *dataseg_reloc); /* Fill relocation table */ i = 0; module_symtab_enumerate(namespace_variable, _ds_reloc_fun, &i); /* Initialize variables */ for (dv = declvar; dv; dv = dv->next) { if (dv->var->sym.flags & (SYM_VOLATILE | SYM_REFERENCED)) { switch (dv->var->type) { case dtype_string: dataseg[dv->var->off] = (STKVAL) dv->val.v.literal->off; break; case dtype_number: dataseg[dv->var->off] = (STKVAL) dv->val.v.number; break; default: abort(); } } } /* Place literals */ symtab_enumerate(stab_literal, _ds_literal_copy_fun, NULL); /* Initialize exception masks */ for (exmask = exmask_root; exmask; exmask = exmask->next) { size_t i, off = exmask->off; dataseg[off++] = (STKVAL) exmask->bm.bm_size; for (i = 0; i < exmask->bm.bm_size; i++) dataseg[off++] = (STKVAL) exmask->bm.bm_bits[i++]; } /* Initialize exception name table */ enumerate_exceptions(place_exc, dataseg + EXTABIND); } static int _regex_compile_fun(void *sym, void *data) { struct literal *lit = sym; if (lit->regex) { struct sym_regex *rp; for (rp = lit->regex; rp; rp = rp->next) register_regex(rp); } return 0; } void regex_layout() { symtab_enumerate(stab_literal, _regex_compile_fun, NULL); finalize_regex(); } static struct variable *auto_list; static void register_auto(struct variable *var) { var->next = auto_list; auto_list = var; } static void unregister_auto(struct variable *var) { struct variable *p = auto_list, *prev = NULL; while (p) { struct variable *next = p->next; if (p == var) { if (prev) prev->next = next; else auto_list = next; p->next = NULL; return; } prev = p; p = next; } } /* FIXME: Redo shadowing via a separate table? */ static size_t forget_autos(size_t nparam, size_t auto_count, size_t hidden_arg) { size_t param_count = 0; struct variable *var = auto_list; while (var) { struct variable *next = var->next; switch (var->storage_class) { case storage_auto: var->off = auto_count++; break; case storage_param: var->off = nparam - param_count++; var->ord = var->off - (hidden_arg ? 1 : 0) - 1; break; default: abort(); } while (var->storage_class != storage_extern) { struct variable *shadowed = var->shadowed; if (!shadowed) { symtab_remove(TOP_MODULE_SYMTAB(namespace_variable), var->sym.name); break; } var->shadowed = NULL; var = variable_replace(var->sym.name, shadowed); } var = next; } auto_list = NULL; return auto_count; } const char * storage_class_str(storage_class_t sc) { switch (sc) { case storage_extern: return "extern"; case storage_auto: return "auto"; case storage_param: return "param"; } return "unknown?"; } const char * function_name() { switch (outer_context) { case context_function: return func->sym.name; case context_handler: return state_to_string(state_tag); default: return ""; } } NODE * declare_function(struct function *func, struct mu_locus_range const *loc, size_t nautos) { NODE *node = alloc_node(node_type_funcdecl, loc); node->v.funcdecl.func = func; node->v.funcdecl.auto_count = nautos; node->v.funcdecl.tree = func->node; return node; } NODE * create_node_variable(struct variable *var, struct mu_locus_range const *locus) { NODE *node = alloc_node(node_type_variable, locus); node->v.var_ref.variable = var; node->v.var_ref.nframes = catch_nesting; return node; } NODE * create_node_argcount(struct mu_locus_range const *locus) { NODE *node; if (outer_context == context_function) { if (func->optcount || func->varargs) { node = alloc_node(node_type_arg, locus); node->v.arg.data_type = dtype_number; node->v.arg.number = 1; } else { node = alloc_node(node_type_number, locus); node->v.number = parminfo[outer_context].parmcount(); } } else { node = alloc_node(node_type_number, locus); node->v.number = parminfo[outer_context].parmcount(); } return node; } NODE * create_node_arg(long num, struct mu_locus_range const *locus) { NODE *node; if (inner_context == context_function && func->varargs) ; else if (num > PARMCOUNT()) parse_error(_("argument number too high")); node = alloc_node(node_type_arg, locus); node->v.arg.data_type = PARMTYPE(num); node->v.arg.number = num; if (inner_context == context_function) node->v.arg.number += FUNC_HIDDEN_ARGS(func); return node; } NODE * create_node_symbol(struct literal *lit, struct mu_locus_range const *locus) { NODE *node; register_macro(state_tag, lit->text); node = alloc_node(node_type_symbol, locus); node->v.literal = lit; return node; } NODE * create_node_backref(long num, struct mu_locus_range const *locus) { NODE *node = alloc_node(node_type_backref, locus); node->v.number = num; return node; }