diff mbox series

[v2,2/4] Added support for global variable definitions.

Message ID 20180605143051.19274-3-david.fabian@bosson.cz
State RFC
Delegated to: Pablo Neira
Headers show
Series Added support for per-file variable scopes and global variables | expand

Commit Message

David Fabian June 5, 2018, 2:30 p.m. UTC
Global variables live only in the top-level scope and can be accessed from anywhere.
They are unloaded at the end of parsing.

Global definitions cannot contain local variables because those may get deleted when
the local scope goes away and the subsequent use of the global would lead to a 
SIGSEGV.

---
 include/rule.h     |   5 +-
 src/parser_bison.y | 335 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 src/rule.c         |  15 ++-
 src/scanner.l      |   1 +
 4 files changed, 347 insertions(+), 9 deletions(-)
 mode change 100644 => 100755 src/parser_bison.y
 mode change 100644 => 100755 src/scanner.l
diff mbox series

Patch

diff --git a/include/rule.h b/include/rule.h
index cfecf7f..8991025 100644
--- a/include/rule.h
+++ b/include/rule.h
@@ -109,8 +109,9 @@  extern void symbol_bind(struct scope *scope, const char *identifier,
 			struct expr *expr);
 extern int symbol_unbind(const struct scope *scope, const char *identifier);
 extern struct symbol *symbol_lookup(const struct scope *scope,
-				    const char *identifier);
-struct symbol *symbol_get(const struct scope *scope, const char *identifier);
+									const char *identifier, int *global);
+struct symbol *symbol_get(const struct scope *scope, const char *identifier,
+						  int *global);
 
 enum table_flags {
 	TABLE_F_DORMANT		= (1 << 0),
diff --git a/src/parser_bison.y b/src/parser_bison.y
old mode 100644
new mode 100755
index 16a1f75..4500aac
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -62,6 +62,11 @@  static void yyerror(struct location *loc, struct nft_ctx *nft, void *scanner,
 	erec_queue(error(loc, "%s", s), state->msgs);
 }
 
+static struct scope *toplevel_scope(const struct parser_state *state)
+{
+	return state->scopes[0];
+}
+
 struct scope *current_scope(const struct parser_state *state)
 {
 	return state->scopes[state->scope];
@@ -192,6 +197,7 @@  int nft_lex(void *, void *, void *);
 %token DEFINE			"define"
 %token REDEFINE			"redefine"
 %token UNDEFINE			"undefine"
+%token GLOBAL			"global"
 
 %token FIB			"fib"
 
@@ -739,6 +745,18 @@  int nft_lex(void *, void *, void *);
 
 %type <val>			ct_l4protoname ct_obj_type
 
+%type <expr>		global_list_rhs_expr global_rhs_expr global_variable_expr global_basic_rhs_expr global_primary_rhs_expr global_symbol_expr
+%destructor { expr_free($$); }	global_list_rhs_expr global_rhs_expr global_variable_expr global_basic_rhs_expr global_primary_rhs_expr global_symbol_expr
+
+%type <expr>		 global_set_lhs_expr global_set_rhs_expr global_shift_rhs_expr global_and_rhs_expr global_exclusive_or_rhs_expr global_inclusive_or_rhs_expr
+%destructor { expr_free($$); }	 global_set_lhs_expr global_set_rhs_expr global_shift_rhs_expr global_and_rhs_expr global_exclusive_or_rhs_expr global_inclusive_or_rhs_expr
+
+%type <expr>		global_concat_rhs_expr global_prefix_rhs_expr global_initializer_expr
+%destructor { expr_free($$); }	global_concat_rhs_expr global_prefix_rhs_expr global_initializer_expr
+
+%type <expr>		global_range_rhs_expr global_set_list_expr global_set_list_member_expr global_set_elem_expr_alloc global_set_expr global_set_elem_expr global_multiton_rhs_expr
+%destructor { expr_free($$); }	global_range_rhs_expr global_set_list_expr global_set_list_member_expr global_set_elem_expr_alloc global_set_expr global_set_elem_expr global_multiton_rhs_expr
+
 %%
 
 input			:	/* empty */
@@ -780,7 +798,7 @@  common_block		:	INCLUDE		QUOTED_STRING	stmt_separator
 			{
 				struct scope *scope = current_scope(state);
 
-				if (symbol_lookup(scope, $2) != NULL) {
+				if (symbol_lookup(scope, $2, NULL) != NULL) {
 					erec_queue(error(&@2, "redefinition of symbol '%s'", $2),
 						   state->msgs);
 					xfree($2);
@@ -807,8 +825,43 @@  common_block		:	INCLUDE		QUOTED_STRING	stmt_separator
 						   state->msgs);
 					YYERROR;
 				}
+
 				xfree($2);
 			}
+			|	GLOBAL	DEFINE	identifier	'='	global_initializer_expr	stmt_separator
+			{
+				struct scope *scope = toplevel_scope(state);
+
+				if (symbol_lookup(scope, $3, NULL) != NULL) {
+					erec_queue(error(&@2, "redefinition of global symbol '%s'", $3),
+						   state->msgs);
+					xfree($3);
+					expr_free($5);
+					YYERROR;
+				}
+
+				symbol_bind(scope, $3, $5);
+				xfree($3);
+			}
+			|	GLOBAL	REDEFINE	identifier	'='	global_initializer_expr	stmt_separator
+			{
+				struct scope *scope = toplevel_scope(state);
+
+				symbol_bind(scope, $3, $5);
+				xfree($3);
+			}
+			|	GLOBAL	UNDEFINE	identifier	stmt_separator
+			{
+				struct scope *scope = toplevel_scope(state);
+
+				if (symbol_unbind(scope, $3) < 0) {
+					erec_queue(error(&@2, "undefined global symbol '%s'", $3),
+						   state->msgs);
+					YYERROR;
+				}
+
+				xfree($3);
+			}
 			|	error		stmt_separator
 			{
 				if (++state->nerrs == nft->parser_max_errors)
@@ -2823,8 +2876,9 @@  variable_expr		:	'$'	identifier
 			{
 				struct scope *scope = current_scope(state);
 				struct symbol *sym;
+				int global;
 
-				sym = symbol_get(scope, $2);
+				sym = symbol_get(scope, $2, &global);
 				if (!sym) {
 					erec_queue(error(&@2, "unknown identifier '%s'", $2),
 						   state->msgs);
@@ -2832,7 +2886,13 @@  variable_expr		:	'$'	identifier
 					YYERROR;
 				}
 
-				$$ = variable_expr_alloc(&@$, scope, sym);
+				if (global)
+					/* global variables can only access top-level scope
+					   this prevents SIGSEGVs when the local scope in which
+					   the global is used has been freed */
+					$$ = variable_expr_alloc(&@$, toplevel_scope(state), sym);
+				else
+					$$ = variable_expr_alloc(&@$, scope, sym);
 				xfree($2);
 			}
 			;
@@ -4093,4 +4153,273 @@  exthdr_key		:	HBH	{ $$ = IPPROTO_HOPOPTS; }
 			|	MH	{ $$ = IPPROTO_MH; }
 			;
 
+global_initializer_expr	:	global_rhs_expr
+			|	global_list_rhs_expr
+			;
+
+global_list_rhs_expr		:	global_basic_rhs_expr		COMMA		global_basic_rhs_expr
+			{
+				$$ = list_expr_alloc(&@$);
+				compound_expr_add($$, $1);
+				compound_expr_add($$, $3);
+			}
+			|	global_list_rhs_expr		COMMA		global_basic_rhs_expr
+			{
+				$1->location = @$;
+				compound_expr_add($1, $3);
+				$$ = $1;
+			}
+			;
+
+global_rhs_expr		:	global_concat_rhs_expr		{ $$ = $1; }
+			|	global_multiton_rhs_expr	{ $$ = $1; }
+			|	global_set_expr		{ $$ = $1; }
+			;
+
+global_variable_expr		:	'$'	identifier
+			{
+				struct scope *scope = toplevel_scope(state);
+				struct symbol *sym;
+
+				sym = symbol_get(scope, $2, NULL);
+				if (!sym) {
+					scope = current_scope(state);
+					if (symbol_lookup(scope, $2, NULL) != NULL) {
+						erec_queue(error(&@2, "local variable '%s' not allowed in global definition", $2),
+							state->msgs);
+					} else {
+						erec_queue(error(&@2, "unknown identifier '%s'", $2),
+							state->msgs);
+					}
+					xfree($2);
+					YYERROR;
+				}
+				$$ = variable_expr_alloc(&@$, scope, sym);
+				xfree($2);
+			}
+			;
+
+global_symbol_expr		:	global_variable_expr
+			|	string
+			{
+				$$ = symbol_expr_alloc(&@$, SYMBOL_VALUE,
+						       toplevel_scope(state),
+						       $1);
+				xfree($1);
+			}
+			|	AT	identifier
+			{
+				$$ = symbol_expr_alloc(&@$, SYMBOL_SET,
+						       toplevel_scope(state),
+						       $2);
+				xfree($2);
+			}
+			;
+
+global_primary_rhs_expr	:	global_symbol_expr		{ $$ = $1; }
+			|	integer_expr		{ $$ = $1; }
+			|	boolean_expr		{ $$ = $1; }
+			|	keyword_expr		{ $$ = $1; }
+			|	TCP
+			{
+				uint8_t data = IPPROTO_TCP;
+				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
+							 BYTEORDER_HOST_ENDIAN,
+							 sizeof(data) * BITS_PER_BYTE, &data);
+			}
+			|	UDP
+			{
+				uint8_t data = IPPROTO_UDP;
+				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
+							 BYTEORDER_HOST_ENDIAN,
+							 sizeof(data) * BITS_PER_BYTE, &data);
+			}
+			|	UDPLITE
+			{
+				uint8_t data = IPPROTO_UDPLITE;
+				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
+							 BYTEORDER_HOST_ENDIAN,
+							 sizeof(data) * BITS_PER_BYTE, &data);
+			}
+			|	ESP
+			{
+				uint8_t data = IPPROTO_ESP;
+				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
+							 BYTEORDER_HOST_ENDIAN,
+							 sizeof(data) * BITS_PER_BYTE, &data);
+			}
+			|	AH
+			{
+				uint8_t data = IPPROTO_AH;
+				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
+							 BYTEORDER_HOST_ENDIAN,
+							 sizeof(data) * BITS_PER_BYTE, &data);
+			}
+			|	ICMP
+			{
+				uint8_t data = IPPROTO_ICMP;
+				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
+							 BYTEORDER_HOST_ENDIAN,
+							 sizeof(data) * BITS_PER_BYTE, &data);
+			}
+			|	ICMP6
+			{
+				uint8_t data = IPPROTO_ICMPV6;
+				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
+							 BYTEORDER_HOST_ENDIAN,
+							 sizeof(data) * BITS_PER_BYTE, &data);
+			}
+			|	COMP
+			{
+				uint8_t data = IPPROTO_COMP;
+				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
+							 BYTEORDER_HOST_ENDIAN,
+							 sizeof(data) * BITS_PER_BYTE, &data);
+			}
+			|	DCCP
+			{
+				uint8_t data = IPPROTO_DCCP;
+				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
+							 BYTEORDER_HOST_ENDIAN,
+							 sizeof(data) * BITS_PER_BYTE, &data);
+			}
+			|	SCTP
+			{
+				uint8_t data = IPPROTO_SCTP;
+				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
+							 BYTEORDER_HOST_ENDIAN,
+							 sizeof(data) * BITS_PER_BYTE, &data);
+			}
+			|	REDIRECT
+			{
+				uint8_t data = ICMP_REDIRECT;
+				$$ = constant_expr_alloc(&@$, &icmp_type_type,
+							 BYTEORDER_HOST_ENDIAN,
+							 sizeof(data) * BITS_PER_BYTE, &data);
+			}
+			;
+
+global_shift_rhs_expr		:	global_primary_rhs_expr
+			|	global_shift_rhs_expr		LSHIFT		global_primary_rhs_expr
+			{
+				$$ = binop_expr_alloc(&@$, OP_LSHIFT, $1, $3);
+			}
+			|	global_shift_rhs_expr		RSHIFT		global_primary_rhs_expr
+			{
+				$$ = binop_expr_alloc(&@$, OP_RSHIFT, $1, $3);
+			}
+			;
+
+global_and_rhs_expr		:	global_shift_rhs_expr
+			|	global_and_rhs_expr		AMPERSAND	global_shift_rhs_expr
+			{
+				$$ = binop_expr_alloc(&@$, OP_AND, $1, $3);
+			}
+			;
+
+global_exclusive_or_rhs_expr	:	global_and_rhs_expr
+			|	global_exclusive_or_rhs_expr	CARET		global_and_rhs_expr
+			{
+				$$ = binop_expr_alloc(&@$, OP_XOR, $1, $3);
+			}
+			;
+
+global_inclusive_or_rhs_expr	:	global_exclusive_or_rhs_expr
+			|	global_inclusive_or_rhs_expr	'|'		global_exclusive_or_rhs_expr
+			{
+				$$ = binop_expr_alloc(&@$, OP_OR, $1, $3);
+			}
+			;
+
+global_basic_rhs_expr		:	global_inclusive_or_rhs_expr
+			;
+
+global_concat_rhs_expr		:	global_basic_rhs_expr
+			|	global_concat_rhs_expr	DOT	global_basic_rhs_expr
+			{
+				if ($$->ops->type != EXPR_CONCAT) {
+					$$ = concat_expr_alloc(&@$);
+					compound_expr_add($$, $1);
+				} else {
+					struct location rhs[] = {
+						[1]	= @2,
+						[2]	= @3,
+					};
+					location_update(&$3->location, rhs, 2);
+
+					$$ = $1;
+					$$->location = @$;
+				}
+				compound_expr_add($$, $3);
+			}
+			;
+
+global_prefix_rhs_expr		:	global_basic_rhs_expr	SLASH	NUM
+			{
+				$$ = prefix_expr_alloc(&@$, $1, $3);
+			}
+			;
+
+global_range_rhs_expr		:	global_basic_rhs_expr	DASH	global_basic_rhs_expr
+			{
+				$$ = range_expr_alloc(&@$, $1, $3);
+			}
+			;
+
+global_multiton_rhs_expr	:	global_prefix_rhs_expr
+			|	global_range_rhs_expr
+			|	wildcard_expr
+			;
+
+global_set_list_expr		:	global_set_list_member_expr
+			{
+				$$ = set_expr_alloc(&@$, NULL);
+				compound_expr_add($$, $1);
+			}
+			|	global_set_list_expr		COMMA	global_set_list_member_expr
+			{
+				compound_expr_add($1, $3);
+				$$ = $1;
+			}
+			|	global_set_list_expr		COMMA	opt_newline
+			;
+
+global_set_list_member_expr	:	opt_newline	global_set_expr	opt_newline
+			{
+				$$ = $2;
+			}
+			|	opt_newline	global_set_elem_expr	opt_newline
+			{
+				$$ = $2;
+			}
+			|	opt_newline	global_set_elem_expr	COLON	global_set_rhs_expr	opt_newline
+			{
+				$$ = mapping_expr_alloc(&@$, $2, $4);
+			}
+			;
+
+global_set_lhs_expr		:	global_concat_rhs_expr
+			|	global_multiton_rhs_expr
+			;
+
+global_set_rhs_expr		:	global_concat_rhs_expr
+			|	verdict_expr
+			;
+
+global_set_elem_expr		:	global_set_elem_expr_alloc
+			|	global_set_elem_expr_alloc		set_elem_options
+			;
+
+global_set_elem_expr_alloc	:	global_set_lhs_expr
+			{
+				$$ = set_elem_expr_alloc(&@1, $1);
+			}
+			;
+
+global_set_expr		:	'{'	global_set_list_expr		'}'
+			{
+				$2->location = @$;
+				$$ = $2;
+			}
+			;
 %%
diff --git a/src/rule.c b/src/rule.c
index 38529af..d40ec50 100644
--- a/src/rule.c
+++ b/src/rule.c
@@ -526,11 +526,12 @@  void symbol_bind(struct scope *scope, const char *identifier, struct expr *expr)
 	list_add(&sym->list, &scope->symbols);
 }
 
-struct symbol *symbol_get(const struct scope *scope, const char *identifier)
+struct symbol *symbol_get(const struct scope *scope, const char *identifier,
+						  int *global)
 {
 	struct symbol *sym;
 
-	sym = symbol_lookup(scope, identifier);
+	sym = symbol_lookup(scope, identifier, global);
 	if (!sym)
 		return NULL;
 
@@ -566,14 +567,20 @@  int symbol_unbind(const struct scope *scope, const char *identifier)
 	return 0;
 }
 
-struct symbol *symbol_lookup(const struct scope *scope, const char *identifier)
+struct symbol *symbol_lookup(const struct scope *scope, const char *identifier,
+							 int *global)
 {
 	struct symbol *sym;
 
+	if (global)
+		*global = 0;
 	while (scope != NULL) {
 		list_for_each_entry(sym, &scope->symbols, list) {
-			if (!strcmp(sym->identifier, identifier))
+			if (!strcmp(sym->identifier, identifier)) {
+				if (global && scope->parent == NULL)
+					*global = 1;
 				return sym;
+			}
 		}
 		scope = scope->parent;
 	}
diff --git a/src/scanner.l b/src/scanner.l
old mode 100644
new mode 100755
index e6e255d..96bb307
--- a/src/scanner.l
+++ b/src/scanner.l
@@ -236,6 +236,7 @@  addrstring	({macaddr}|{ip4addr}|{ip6addr})
 "define"		{ return DEFINE; }
 "redefine"		{ return REDEFINE; }
 "undefine"		{ return UNDEFINE; }
+"global"		{ return GLOBAL; }
 
 "describe"		{ return DESCRIBE; }