dfilter: Allow arithmetic expressions as function arguments

This allows writing moderately complex expressions, for example
a float epsilon test (#16483):

Filter: {abs(_ws.ftypes.double - 1) / max(abs(_ws.ftypes.double), abs(1))} < 0.01

Syntax tree:
 0 TEST_LT:
   1 OP_DIVIDE:
     2 FUNCTION(abs#1):
       3 OP_SUBTRACT:
         4 FIELD(_ws.ftypes.double)
         4 FVALUE(1 <FT_DOUBLE>)
     2 FUNCTION(max#2):
       3 FUNCTION(abs#1):
         4 FIELD(_ws.ftypes.double)
       3 FUNCTION(abs#1):
         4 FVALUE(1 <FT_DOUBLE>)
   1 FVALUE(0.01 <FT_DOUBLE>)

Instructions:
00000 READ_TREE		_ws.ftypes.double -> reg#1
00001 IF_FALSE_GOTO	3
00002 SUBRACT		reg#1 - 1 <FT_DOUBLE> -> reg#2
00003 STACK_PUSH	reg#2
00004 CALL_FUNCTION	abs(reg#2) -> reg#0
00005 STACK_POP	1
00006 IF_FALSE_GOTO	24
00007 READ_TREE		_ws.ftypes.double -> reg#1
00008 IF_FALSE_GOTO	9
00009 STACK_PUSH	reg#1
00010 CALL_FUNCTION	abs(reg#1) -> reg#4
00011 STACK_POP	1
00012 IF_FALSE_GOTO	13
00013 STACK_PUSH	reg#4
00014 STACK_PUSH	1 <FT_DOUBLE>
00015 CALL_FUNCTION	abs(1 <FT_DOUBLE>) -> reg#5
00016 STACK_POP	1
00017 IF_FALSE_GOTO	18
00018 STACK_PUSH	reg#5
00019 CALL_FUNCTION	max(reg#5, reg#4) -> reg#3
00020 STACK_POP	2
00021 IF_FALSE_GOTO	24
00022 DIVIDE		reg#0 / reg#3 -> reg#6
00023 ANY_LT		reg#6 < 0.01 <FT_DOUBLE>
00024 RETURN

We now use a stack to pass arguments to the function. The
stack is implemented as a list of lists (list of registers).
Arguments may still be non-existent to functions (this is
a feature). Functions must check for nil arguments (NULL lists)
and handle that case.

It's somewhat complicated to allow literal values and test compatibility
for different types, both because of lack of type information with
unparsed/literal and also because it is an underdeveloped area in the
code. In my limited testing it was good enough and useful, further
enhancements are left for future work.
This commit is contained in:
João Valverde 2022-04-16 02:42:20 +01:00
parent 92c1519dfe
commit fab32ea0cb
16 changed files with 416 additions and 261 deletions

View File

@ -69,6 +69,8 @@ They previously shipped with Npcap 1.55.
curly brackets (not parenthesis).
** Logical AND now has higher precedence than logical OR, in line with most programming languages.
** Adds new display filter functions max(), min() and abs().
** Functions can accept expressions as arguments, including other functions. Previously only protocol
fields and slices were syntactically valid function arguments.
* text2pcap and "Import from Hex Dump":
** text2pcap supports writing the output file in all the capture file formats

View File

@ -29,6 +29,8 @@ struct epan_dfilter {
char *expanded_text;
GHashTable *references;
char *syntax_tree_str;
/* Used to pass arguments to functions. List of Lists (list of registers). */
GSList *function_stack;
};
typedef struct {

View File

@ -188,6 +188,8 @@ dfilter_new(GPtrArray *deprecated)
if (deprecated)
df->deprecated = g_ptr_array_ref(deprecated);
df->function_stack = NULL;
return df;
}
@ -223,6 +225,11 @@ dfilter_free(dfilter_t *df)
if (df->deprecated)
g_ptr_array_unref(df->deprecated);
if (df->function_stack != NULL) {
ws_critical("Function stack list should be NULL");
g_slist_free(df->function_stack);
}
g_free(df->registers);
g_free(df->attempted_load);
g_free(df->free_registers);

View File

@ -13,6 +13,7 @@
#include "dfilter-int.h"
#include "dfunctions.h"
#include "sttype-pointer.h"
#include "semcheck.h"
#include <string.h>
@ -25,7 +26,7 @@
/* Convert an FT_STRING using a callback function */
static gboolean
string_walk(GSList **args, guint32 arg_count, GSList **retval, gchar(*conv_func)(gchar))
string_walk(GSList *args, guint32 arg_count, GSList **retval, gchar(*conv_func)(gchar))
{
GSList *arg1;
fvalue_t *arg_fvalue;
@ -33,7 +34,7 @@ string_walk(GSList **args, guint32 arg_count, GSList **retval, gchar(*conv_func)
char *s, *c;
ws_assert(arg_count == 1);
arg1 = args[0];
arg1 = args->data;
if (arg1 == NULL)
return FALSE;
@ -59,28 +60,28 @@ string_walk(GSList **args, guint32 arg_count, GSList **retval, gchar(*conv_func)
/* dfilter function: lower() */
static gboolean
df_func_lower(GSList **args, guint32 arg_count, GSList **retval)
df_func_lower(GSList *args, guint32 arg_count, GSList **retval)
{
return string_walk(args, arg_count, retval, g_ascii_tolower);
}
/* dfilter function: upper() */
static gboolean
df_func_upper(GSList **args, guint32 arg_count, GSList **retval)
df_func_upper(GSList *args, guint32 arg_count, GSList **retval)
{
return string_walk(args, arg_count, retval, g_ascii_toupper);
}
/* dfilter function: len() */
static gboolean
df_func_len(GSList **args, guint32 arg_count, GSList **retval)
df_func_len(GSList *args, guint32 arg_count, GSList **retval)
{
GSList *arg1;
fvalue_t *arg_fvalue;
fvalue_t *ft_len;
ws_assert(arg_count == 1);
arg1 = args[0];
arg1 = args->data;
if (arg1 == NULL)
return FALSE;
@ -97,14 +98,14 @@ df_func_len(GSList **args, guint32 arg_count, GSList **retval)
/* dfilter function: count() */
static gboolean
df_func_count(GSList **args, guint32 arg_count, GSList **retval)
df_func_count(GSList *args, guint32 arg_count, GSList **retval)
{
GSList *arg1;
fvalue_t *ft_ret;
guint32 num_items;
ws_assert(arg_count == 1);
arg1 = args[0];
arg1 = args->data;
if (arg1 == NULL)
return FALSE;
@ -118,7 +119,7 @@ df_func_count(GSList **args, guint32 arg_count, GSList **retval)
/* dfilter function: string() */
static gboolean
df_func_string(GSList **args, guint32 arg_count, GSList **retval)
df_func_string(GSList *args, guint32 arg_count, GSList **retval)
{
GSList *arg1;
fvalue_t *arg_fvalue;
@ -126,7 +127,7 @@ df_func_string(GSList **args, guint32 arg_count, GSList **retval)
char *s;
ws_assert(arg_count == 1);
arg1 = args[0];
arg1 = args->data;
if (arg1 == NULL)
return FALSE;
@ -187,17 +188,17 @@ df_func_string(GSList **args, guint32 arg_count, GSList **retval)
}
static gboolean
df_func_compare(GSList **args, guint32 arg_count, GSList **retval,
df_func_compare(GSList *args, guint32 arg_count, GSList **retval,
gboolean (*fv_cmp)(const fvalue_t *a, const fvalue_t *b))
{
fvalue_t *fv_ret = NULL;
GSList *l;
GSList *l1, *l2;
guint32 i;
for (i = 0; i < arg_count; i++) {
for (l = args[i]; l != NULL; l = l->next) {
if (fv_ret == NULL || fv_cmp(l->data, fv_ret)) {
fv_ret = l->data;
for (l1 = args, i = 0; i < arg_count; l1 = l1->next, i++) {
for (l2 = l1->data; l2 != NULL; l2 = l2->next) {
if (fv_ret == NULL || fv_cmp(l2->data, fv_ret)) {
fv_ret = l2->data;
}
}
}
@ -212,20 +213,20 @@ df_func_compare(GSList **args, guint32 arg_count, GSList **retval,
/* Find maximum value. */
static gboolean
df_func_max(GSList **args, guint32 arg_count, GSList **retval)
df_func_max(GSList *args, guint32 arg_count, GSList **retval)
{
return df_func_compare(args, arg_count, retval, fvalue_gt);
}
/* Find minimum value. */
static gboolean
df_func_min(GSList **args, guint32 arg_count, GSList **retval)
df_func_min(GSList *args, guint32 arg_count, GSList **retval)
{
return df_func_compare(args, arg_count, retval, fvalue_lt);
}
static gboolean
df_func_abs(GSList **args, guint32 arg_count, GSList **retval)
df_func_abs(GSList *args, guint32 arg_count, GSList **retval)
{
GSList *arg1;
fvalue_t *fv_arg, *new_fv;
@ -233,7 +234,7 @@ df_func_abs(GSList **args, guint32 arg_count, GSList **retval)
GSList *result = NULL;
ws_assert(arg_count == 1);
arg1 = args[0];
arg1 = args->data;
if (arg1 == NULL)
return FALSE;
@ -263,8 +264,8 @@ df_func_abs(GSList **args, guint32 arg_count, GSList **retval)
/* For upper() and lower() checks that the parameter passed to
* it is an FT_STRING */
static void
ul_semcheck_is_field_string(dfwork_t *dfw, const char *func_name,
static ftenum_t
ul_semcheck_is_field_string(dfwork_t *dfw, const char *func_name, ftenum_t lhs_ftype _U_,
GSList *param_list, stloc_t *func_loc _U_)
{
header_field_info *hfinfo;
@ -277,14 +278,14 @@ ul_semcheck_is_field_string(dfwork_t *dfw, const char *func_name,
if (stnode_type_id(st_node) == STTYPE_FIELD) {
hfinfo = stnode_data(st_node);
if (IS_FT_STRING(hfinfo->type)) {
return;
return FT_STRING;
}
}
FAIL(dfw, st_node, "Only string type fields can be used as parameter for %s()", func_name);
}
static void
ul_semcheck_is_field(dfwork_t *dfw, const char *func_name,
static ftenum_t
ul_semcheck_is_field(dfwork_t *dfw, const char *func_name, ftenum_t lhs_ftype _U_,
GSList *param_list, stloc_t *func_loc _U_)
{
ws_assert(g_slist_length(param_list) == 1);
@ -293,13 +294,13 @@ ul_semcheck_is_field(dfwork_t *dfw, const char *func_name,
dfw_resolve_unparsed(dfw, st_node);
if (stnode_type_id(st_node) == STTYPE_FIELD)
return;
return FT_UINT32;
FAIL(dfw, st_node, "Only fields can be used as parameter for %s()", func_name);
}
static void
ul_semcheck_string_param(dfwork_t *dfw, const char *func_name,
static ftenum_t
ul_semcheck_string_param(dfwork_t *dfw, const char *func_name, ftenum_t lhs_ftype _U_,
GSList *param_list, stloc_t *func_loc _U_)
{
header_field_info *hfinfo;
@ -344,7 +345,7 @@ ul_semcheck_string_param(dfwork_t *dfw, const char *func_name,
case FT_FCWWN:
case FT_IEEE_11073_SFLOAT:
case FT_IEEE_11073_FLOAT:
return;
return FT_STRING;
default:
break;
}
@ -354,109 +355,126 @@ ul_semcheck_string_param(dfwork_t *dfw, const char *func_name,
}
/* Check arguments are all the same type and they can be compared. */
static void
ul_semcheck_compare(dfwork_t *dfw, const char *func_name,
GSList *param_list, stloc_t *func_loc)
static ftenum_t
ul_semcheck_compare(dfwork_t *dfw, const char *func_name, ftenum_t lhs_ftype,
GSList *param_list, stloc_t *func_loc _U_)
{
stnode_t *arg;
ftenum_t ftype, ft_arg;
GSList *l;
const header_field_info *hfinfo;
fvalue_t *fv;
/* First argument must be a field not FT_NONE. */
arg = param_list->data;
dfw_resolve_unparsed(dfw, arg);
ftype = sttype_pointer_ftenum(arg);
if (ftype == FT_NONE) {
FAIL(dfw, arg, "First argument to %s() must be a field, not %s",
func_name, stnode_type_name(arg));
if (stnode_type_id(arg) == STTYPE_ARITHMETIC) {
ftype = check_arithmetic_expr(dfw, arg, lhs_ftype);
}
else if (stnode_type_id(arg) == STTYPE_LITERAL && lhs_ftype != FT_NONE) {
fv = dfilter_fvalue_from_literal(dfw, lhs_ftype, arg, FALSE, NULL);
stnode_replace(arg, STTYPE_FVALUE, fv);
ftype = fvalue_type_ftenum(fv);
}
else if (stnode_type_id(arg) == STTYPE_FUNCTION) {
ftype = check_function(dfw, arg, lhs_ftype);
}
else {
ftype = sttype_pointer_ftenum(arg);
}
for (l = param_list; l != NULL; l = l->next) {
if (ftype == FT_NONE) {
FAIL(dfw, arg, "Argument '%s' (FT_NONE) is not valid for %s()",
stnode_todisplay(arg), func_name);
}
for (l = param_list->next; l != NULL; l = l->next) {
arg = l->data;
dfw_resolve_unparsed(dfw, arg);
switch (stnode_type_id(arg)) {
case STTYPE_FIELD:
case STTYPE_REFERENCE:
hfinfo = stnode_data(arg);
ft_arg = hfinfo->type;
break;
case STTYPE_LITERAL:
fv = dfilter_fvalue_from_literal(dfw, ftype, arg, FALSE, NULL);
stnode_replace(arg, STTYPE_FVALUE, fv);
ft_arg = fvalue_type_ftenum(stnode_data(arg));
break;
case STTYPE_FVALUE:
ft_arg = fvalue_type_ftenum(stnode_data(arg));
break;
default:
FAIL(dfw, arg, "Type %s is not valid for %s",
stnode_type_name(arg), func_name);
if (stnode_type_id(arg) == STTYPE_ARITHMETIC) {
ft_arg = check_arithmetic_expr(dfw, arg, ftype);
}
else if (stnode_type_id(arg) == STTYPE_LITERAL && ftype != FT_NONE) {
fv = dfilter_fvalue_from_literal(dfw, ftype, arg, FALSE, NULL);
stnode_replace(arg, STTYPE_FVALUE, fv);
ft_arg = fvalue_type_ftenum(fv);
}
else if (stnode_type_id(arg) == STTYPE_FUNCTION) {
ft_arg = check_function(dfw, arg, ftype);
}
else {
ft_arg = sttype_pointer_ftenum(arg);
}
if (ft_arg == FT_NONE) {
dfilter_fail_throw(dfw, func_loc,
"Argument '%s' (FT_NONE) is not valid for %s()",
FAIL(dfw, arg, "Argument '%s' (FT_NONE) is not valid for %s()",
stnode_todisplay(arg), func_name);
}
if (ftype == FT_NONE) {
ftype = ft_arg;
}
if (ft_arg != ftype) {
dfilter_fail_throw(dfw, func_loc,
"Arguments to '%s' must have the same type",
func_name);
FAIL(dfw, arg, "Arguments to '%s' must have the same type (expected %s, got %s)",
func_name, ftype_name(ftype), ftype_name(ft_arg));
}
if (!ftype_can_cmp(ft_arg)) {
dfilter_fail_throw(dfw, func_loc,
"Argument '%s' to '%s' cannot be ordered",
FAIL(dfw, arg, "Argument '%s' to '%s' cannot be ordered",
stnode_todisplay(arg), func_name);
}
}
return ftype;
}
static void
ul_semcheck_absolute_value(dfwork_t *dfw, const char *func_name,
static ftenum_t
ul_semcheck_absolute_value(dfwork_t *dfw, const char *func_name, ftenum_t lhs_ftype,
GSList *param_list, stloc_t *func_loc _U_)
{
ws_assert(g_slist_length(param_list) == 1);
stnode_t *st_node;
ftenum_t ftype;
const header_field_info *hfinfo;
fvalue_t *fv;
st_node = param_list->data;
dfw_resolve_unparsed(dfw, st_node);
switch (stnode_type_id(st_node)) {
case STTYPE_FIELD:
case STTYPE_REFERENCE:
hfinfo = stnode_data(st_node);
ftype = hfinfo->type;
break;
default:
FAIL(dfw, st_node, "Type %s is not valid for %s",
stnode_type_name(st_node), func_name);
if (stnode_type_id(st_node) == STTYPE_ARITHMETIC) {
ftype = check_arithmetic_expr(dfw, st_node, lhs_ftype);
}
else if (stnode_type_id(st_node) == STTYPE_LITERAL && lhs_ftype != FT_NONE) {
fv = dfilter_fvalue_from_literal(dfw, lhs_ftype, st_node, FALSE, NULL);
stnode_replace(st_node, STTYPE_FVALUE, fv);
ftype = fvalue_type_ftenum(fv);
}
else if (stnode_type_id(st_node) == STTYPE_FUNCTION) {
ftype = check_function(dfw, st_node, lhs_ftype);
}
else {
ftype = sttype_pointer_ftenum(st_node);
}
if (ftype == FT_NONE) {
FAIL(dfw, st_node, "Type %s is not valid for %s",
stnode_type_name(st_node), func_name);
}
if (!ftype_can_is_negative(ftype)) {
FAIL(dfw, st_node, "'%s' is not a valid argument to '%s'()",
stnode_todisplay(st_node), func_name);
}
return ftype;
}
/* The table of all display-filter functions */
static df_func_def_t
df_functions[] = {
{ "lower", df_func_lower, FT_STRING, 1, 1, ul_semcheck_is_field_string },
{ "upper", df_func_upper, FT_STRING, 1, 1, ul_semcheck_is_field_string },
{ "len", df_func_len, FT_UINT32, 1, 1, ul_semcheck_is_field },
{ "count", df_func_count, FT_UINT32, 1, 1, ul_semcheck_is_field },
{ "string", df_func_string, FT_STRING, 1, 1, ul_semcheck_string_param },
{ "max", df_func_max, 0, 1, 0, ul_semcheck_compare },
{ "min", df_func_min, 0, 1, 0, ul_semcheck_compare },
{ "abs", df_func_abs, 0, 1, 1, ul_semcheck_absolute_value },
{ NULL, NULL, FT_NONE, 0, 0, NULL }
{ "lower", df_func_lower, 1, 1, ul_semcheck_is_field_string },
{ "upper", df_func_upper, 1, 1, ul_semcheck_is_field_string },
{ "len", df_func_len, 1, 1, ul_semcheck_is_field },
{ "count", df_func_count, 1, 1, ul_semcheck_is_field },
{ "string", df_func_string, 1, 1, ul_semcheck_string_param },
{ "max", df_func_max, 1, 0, ul_semcheck_compare },
{ "min", df_func_min, 1, 0, ul_semcheck_compare },
{ "abs", df_func_abs, 1, 1, ul_semcheck_absolute_value },
{ NULL, NULL, 0, 0, NULL }
};
/* Lookup a display filter function record by name */

View File

@ -17,10 +17,10 @@
/* Functions take any number of arguments and return 1. */
/* The run-time logic of the dfilter function */
typedef gboolean (*DFFuncType)(GSList **arg_list, guint32 arg_count, GSList **retval);
typedef gboolean (*DFFuncType)(GSList *arg_list, guint32 arg_count, GSList **retval);
/* The semantic check for the dfilter function */
typedef void (*DFSemCheckType)(dfwork_t *dfw, const char *func_name,
typedef ftenum_t (*DFSemCheckType)(dfwork_t *dfw, const char *func_name, ftenum_t lhs_ftype,
GSList *param_list, stloc_t *func_loc);
/* This is a "function definition" record, holding everything
@ -28,7 +28,6 @@ typedef void (*DFSemCheckType)(dfwork_t *dfw, const char *func_name,
typedef struct {
const char *name;
DFFuncType function;
ftenum_t retval_ftype; /* 0 means return value is the same as input argument(s) */
guint min_nargs;
guint max_nargs; /* 0 for no limit */
DFSemCheckType semcheck_param_function;

View File

@ -17,6 +17,46 @@
static void
debug_register(GSList *reg, guint32 num);
const char *
dfvm_opcode_tostr(dfvm_opcode_t code)
{
switch (code) {
case IF_TRUE_GOTO: return "IF_TRUE_GOTO";
case IF_FALSE_GOTO: return "IF_FALSE_GOTO";
case CHECK_EXISTS: return "CHECK_EXISTS";
case NOT: return "NOT";
case RETURN: return "RETURN";
case READ_TREE: return "READ_TREE";
case READ_REFERENCE: return "READ_REFERENCE";
case PUT_FVALUE: return "PUT_FVALUE";
case ALL_EQ: return "ALL_EQ";
case ANY_EQ: return "ANY_EQ";
case ALL_NE: return "ALL_NE";
case ANY_NE: return "ANY_NE";
case ANY_GT: return "ANY_GT";
case ANY_GE: return "ANY_GE";
case ANY_LT: return "ANY_LT";
case ANY_LE: return "ANY_LE";
case ANY_ZERO: return "ANY_ZERO";
case ALL_ZERO: return "ALL_ZERO";
case ANY_CONTAINS: return "ANY_CONTAINS";
case ANY_MATCHES: return "ANY_MATCHES";
case MK_RANGE: return "MK_RANGE";
case MK_BITWISE_AND: return "MK_BITWISE_AND";
case MK_MINUS: return "MK_MINUS";
case DFVM_ADD: return "DFVM_ADD";
case DFVM_SUBTRACT: return "DFVM_SUBTRACT";
case DFVM_MULTIPLY: return "DFVM_MULTIPLY";
case DFVM_DIVIDE: return "DFVM_DIVIDE";
case DFVM_MODULO: return "DFMV_MODULO";
case CALL_FUNCTION: return "CALL_FUNCTION";
case STACK_PUSH: return "STACK_PUSH";
case STACK_POP: return "STACK_POP";
case ANY_IN_RANGE: return "ANY_IN_RANGE";
}
return "(fix-opcode-string)";
}
dfvm_insn_t*
dfvm_insn_new(dfvm_opcode_t op)
{
@ -148,6 +188,14 @@ dfvm_value_new_pcre(ws_regex_t *re)
return v;
}
dfvm_value_t*
dfvm_value_new_guint(guint num)
{
dfvm_value_t *v = dfvm_value_new(INTEGER);
v->value.numeric = num;
return v;
}
char *
dfvm_value_tostr(dfvm_value_t *v)
{
@ -187,6 +235,23 @@ dfvm_value_tostr(dfvm_value_t *v)
return s;
}
static GSList *
dump_str_stack_push(GSList *stack, const char *str)
{
return g_slist_prepend(stack, g_strdup(str));
}
static GSList *
dump_str_stack_pop(GSList *stack)
{
char *str;
str = stack->data;
stack = g_slist_delete_link(stack, stack);
g_free(str);
return stack;
}
char *
dfvm_dump_str(wmem_allocator_t *alloc, dfilter_t *df, gboolean print_references)
{
@ -198,6 +263,8 @@ dfvm_dump_str(wmem_allocator_t *alloc, dfilter_t *df, gboolean print_references)
GHashTableIter ref_iter;
gpointer key, value;
char *str;
GSList *stack_print = NULL, *l;
guint i;
buf = wmem_strbuf_new(alloc, NULL);
@ -240,16 +307,27 @@ dfvm_dump_str(wmem_allocator_t *alloc, dfilter_t *df, gboolean print_references)
case CALL_FUNCTION:
wmem_strbuf_append_printf(buf, "%05d CALL_FUNCTION\t%s(",
id, arg1_str);
if (arg3_str) {
wmem_strbuf_append_printf(buf, "%s", arg3_str);
}
for (guint32 i = 1; i <= arg4->value.numeric; i++) {
wmem_strbuf_append_printf(buf, ", reg#%"G_GUINT32_FORMAT,
arg3->value.numeric + i);
for (l = stack_print, i = 0; i < arg3->value.numeric; i++, l = l->next) {
if (l != stack_print) {
wmem_strbuf_append(buf, ", ");
}
wmem_strbuf_append(buf, l->data);
}
wmem_strbuf_append_printf(buf, ") -> %s\n", arg2_str);
break;
case STACK_PUSH:
wmem_strbuf_append_printf(buf, "%05d STACK_PUSH\t%s\n", id, arg1_str);
stack_print = dump_str_stack_push(stack_print, arg1_str);
break;
case STACK_POP:
wmem_strbuf_append_printf(buf, "%05d STACK_POP\t%s\n", id, arg1_str);
for (i = 0; i < arg1->value.numeric; i ++) {
stack_print = dump_str_stack_pop(stack_print);
}
break;
case MK_RANGE:
wmem_strbuf_append_printf(buf, "%05d MK_RANGE\t\t%s[%s] -> %s\n",
id, arg1_str, arg3_str, arg2_str);
@ -639,13 +717,8 @@ any_matches(dfilter_t *df, dfvm_value_t *arg1, dfvm_value_t *arg2)
}
static gboolean
any_in_range(dfilter_t *df, dfvm_value_t *arg1,
dfvm_value_t *arg_low, dfvm_value_t *arg_high)
any_in_range_internal(GSList *list1, fvalue_t *low, fvalue_t *high)
{
GSList *list1 = df->registers[arg1->value.numeric];
fvalue_t *low = arg_low->value.fvalue;
fvalue_t *high = arg_high->value.fvalue;
while (list1) {
if (fvalue_ge(list1->data, low) &&
fvalue_le(list1->data, high)) {
@ -656,6 +729,39 @@ any_in_range(dfilter_t *df, dfvm_value_t *arg1,
return FALSE;
}
static gboolean
any_in_range(dfilter_t *df, dfvm_value_t *arg1,
dfvm_value_t *arg_low, dfvm_value_t *arg_high)
{
GSList *list1 = df->registers[arg1->value.numeric];
GSList *_low, *_high;
fvalue_t *low, *high;
if (arg_low->type == REGISTER) {
_low = df->registers[arg_low->value.numeric];
ws_assert(g_slist_length(_low) == 1);
low = _low->data;
}
else if (arg_low->type == FVALUE) {
low = arg_low->value.fvalue;
}
else {
ws_assert_not_reached();
}
if (arg_high->type == REGISTER) {
_high = df->registers[arg_high->value.numeric];
ws_assert(g_slist_length(_high) == 1);
high = _high->data;
}
else if (arg_high->type == FVALUE) {
high = arg_high->value.fvalue;
}
else {
ws_assert_not_reached();
}
return any_in_range_internal(list1, low, high);
}
/* Clear registers that were populated during evaluation.
* If we created the values, then these will be freed as well. */
static void
@ -716,19 +822,18 @@ mk_range(dfilter_t *df, dfvm_value_t *from_arg, dfvm_value_t *to_arg,
*/
static gboolean
call_function(dfilter_t *df, dfvm_value_t *arg1, dfvm_value_t *arg2,
dfvm_value_t *arg3, dfvm_value_t *arg4)
dfvm_value_t *arg3)
{
df_func_def_t *funcdef;
GSList *retval = NULL;
gboolean accum;
guint32 reg_return, reg_first_arg, more_args_count;
guint32 reg_return, arg_count;
funcdef = arg1->value.funcdef;
reg_return = arg2->value.numeric;
reg_first_arg = arg3->value.numeric;
more_args_count = arg4->value.numeric;
arg_count = arg3->value.numeric;
accum = funcdef->function(&df->registers[reg_first_arg], 1 + more_args_count, &retval);
accum = funcdef->function(df->function_stack, arg_count, &retval);
/* Write return registers. */
df->registers[reg_return] = retval;
@ -892,6 +997,42 @@ put_fvalue(dfilter_t *df, dfvm_value_t *arg1, dfvm_value_t *to_arg)
df->free_registers[to_arg->value.numeric] = NULL;
}
static void
stack_push(dfilter_t *df, dfvm_value_t *arg1)
{
GSList *arg;
if (arg1->type == FVALUE) {
arg = g_slist_prepend(NULL, arg1->value.fvalue);
}
else if (arg1->type == REGISTER) {
arg = g_slist_copy(df->registers[arg1->value.numeric]);
}
else {
ws_assert_not_reached();
}
df->function_stack = g_slist_prepend(df->function_stack, arg);
}
static void
stack_pop(dfilter_t *df, dfvm_value_t *arg1)
{
guint count;
GSList *reg;
count = arg1->value.numeric;
for (guint i = 0; i < count; i++) {
/* Free top of stack and register contained there. The register
* contentes are not owned by us. */
reg = df->function_stack->data;
/* Free the list but not the data it contains. */
g_slist_free(reg);
/* remove top of stack */
df->function_stack = g_slist_delete_link(df->function_stack, df->function_stack);
}
}
gboolean
dfvm_apply(dfilter_t *df, proto_tree *tree)
{
@ -901,7 +1042,6 @@ dfvm_apply(dfilter_t *df, proto_tree *tree)
dfvm_value_t *arg1;
dfvm_value_t *arg2;
dfvm_value_t *arg3 = NULL;
dfvm_value_t *arg4 = NULL;
header_field_info *hfinfo;
ws_assert(tree);
@ -915,7 +1055,8 @@ dfvm_apply(dfilter_t *df, proto_tree *tree)
arg1 = insn->arg1;
arg2 = insn->arg2;
arg3 = insn->arg3;
arg4 = insn->arg4;
ws_noisy("ID: %d; OP: %s", id, dfvm_opcode_tostr(insn->op));
switch (insn->op) {
case CHECK_EXISTS:
@ -945,7 +1086,15 @@ dfvm_apply(dfilter_t *df, proto_tree *tree)
break;
case CALL_FUNCTION:
accum = call_function(df, arg1, arg2, arg3, arg4);
accum = call_function(df, arg1, arg2, arg3);
break;
case STACK_PUSH:
stack_push(df, arg1);
break;
case STACK_POP:
stack_pop(df, arg1);
break;
case MK_RANGE:

View File

@ -76,10 +76,15 @@ typedef enum {
DFVM_DIVIDE,
DFVM_MODULO,
CALL_FUNCTION,
STACK_PUSH,
STACK_POP,
ANY_IN_RANGE
} dfvm_opcode_t;
const char *
dfvm_opcode_tostr(dfvm_opcode_t code);
typedef struct {
int id;
dfvm_opcode_t op;
@ -122,6 +127,9 @@ dfvm_value_new_funcdef(df_func_def_t *funcdef);
dfvm_value_t*
dfvm_value_new_pcre(ws_regex_t *re);
dfvm_value_t*
dfvm_value_new_guint(guint num);
void
dfvm_dump(FILE *f, dfilter_t *df);

View File

@ -36,9 +36,44 @@ dfw_append_insn(dfwork_t *dfw, dfvm_insn_t *insn)
g_ptr_array_add(dfw->insns, insn);
}
static void
dfw_append_stack_push(dfwork_t *dfw, dfvm_value_t *arg1)
{
dfvm_insn_t *insn;
insn = dfvm_insn_new(STACK_PUSH);
insn->arg1 = dfvm_value_ref(arg1);
dfw_append_insn(dfw, insn);
}
static void
dfw_append_stack_pop(dfwork_t *dfw, guint count)
{
dfvm_insn_t *insn;
dfvm_value_t *val;
insn = dfvm_insn_new(STACK_POP);
val = dfvm_value_new_guint(count);
insn->arg1 = dfvm_value_ref(val);
dfw_append_insn(dfw, insn);
}
static dfvm_value_t *
dfw_append_jump(dfwork_t *dfw)
{
dfvm_insn_t *insn;
dfvm_value_t *jmp;
insn = dfvm_insn_new(IF_FALSE_GOTO);
jmp = dfvm_value_new(INSN_NUMBER);
insn->arg1 = dfvm_value_ref(jmp);
dfw_append_insn(dfw, insn);
return jmp;
}
/* returns register number */
static dfvm_value_t *
dfw_append_read_tree(dfwork_t *dfw, header_field_info *hfinfo, gboolean reuse_register)
dfw_append_read_tree(dfwork_t *dfw, header_field_info *hfinfo)
{
dfvm_insn_t *insn;
int reg = -1;
@ -56,19 +91,13 @@ dfw_append_read_tree(dfwork_t *dfw, header_field_info *hfinfo, gboolean reuse_re
* can re-use registers. */
loaded_key = g_hash_table_lookup(dfw->loaded_fields, hfinfo);
if (loaded_key != NULL) {
/* Already loaded at least once. */
if (reuse_register) {
/*
* Reg's are stored in has as reg+1, so
* that the non-existence of a hfinfo in
* the hash, or 0, can be differentiated from
* a hfinfo being loaded into register #0.
*/
reg = GPOINTER_TO_INT(loaded_key) - 1;
}
else {
reg = dfw->next_register++;
}
/*
* Reg's are stored in has as reg+1, so
* that the non-existence of a hfinfo in
* the hash, or 0, can be differentiated from
* a hfinfo being loaded into register #0.
*/
reg = GPOINTER_TO_INT(loaded_key) - 1;
}
else {
reg = dfw->next_register++;
@ -169,7 +198,7 @@ dfw_append_mk_range(dfwork_t *dfw, stnode_t *node, GSList **jumps_ptr)
}
/* returns register number */
static dfvm_value_t *
_U_ static dfvm_value_t *
dfw_append_put_fvalue(dfwork_t *dfw, fvalue_t *fv)
{
dfvm_insn_t *insn;
@ -190,11 +219,11 @@ static dfvm_value_t *
dfw_append_function(dfwork_t *dfw, stnode_t *node, GSList **jumps_ptr)
{
GSList *params;
GSList *params_jumps = NULL;
dfvm_value_t *jmp;
dfvm_insn_t *insn;
dfvm_value_t *reg_val, *val1, *val3, *val4, *val_arg;
guint32 reg_first, more_args_count;
stnode_t *arg;
dfvm_value_t *reg_val, *val1, *val3, *val_arg;
guint count;
/* Create the new DFVM instruction */
insn = dfvm_insn_new(CALL_FUNCTION);
@ -206,40 +235,22 @@ dfw_append_function(dfwork_t *dfw, stnode_t *node, GSList **jumps_ptr)
/* Create input arguments */
params = sttype_function_params(node);
ws_assert(params);
val3 = dfw_append_read_tree(dfw, stnode_steal_data(params->data), FALSE);
insn->arg3 = dfvm_value_ref(val3);
params = params->next;
reg_first = val3->value.numeric;
more_args_count = 0;
count = 0;
while (params) {
arg = params->data;
switch (stnode_type_id(arg)) {
case STTYPE_FVALUE:
dfw_append_put_fvalue(dfw, stnode_steal_data(arg));
break;
case STTYPE_FIELD:
/* We cannot reuse registers here because the function calling
* convention is to pass input arguments sequentially. */
val_arg = dfw_append_read_tree(dfw, stnode_data(arg), FALSE);
/* Assert the registers are numbered sequentially. */
ws_assert(val_arg->value.numeric == reg_first + more_args_count + 1);
break;
default:
ws_assert_not_reached();
}
more_args_count++;
val_arg = gen_entity(dfw, params->data, &params_jumps);
/* If a parameter fails to generate jump here.
* Note: stack_push NULL register is valid. */
g_slist_foreach(params_jumps, fixup_jumps, dfw);
g_slist_free(params_jumps);
params_jumps = NULL;
dfw_append_stack_push(dfw, val_arg);
count++;
params = params->next;
}
val4 = dfvm_value_new(INTEGER);
val4->value.numeric = more_args_count;
insn->arg4 = dfvm_value_ref(val4);
val3 = dfvm_value_new_guint(count);
insn->arg3 = dfvm_value_ref(val3);
dfw_append_insn(dfw, insn);
/* There is no jump if READ_TREE fails for a function parameter. It
* is up to the function to return TRUE/FALSE for any combination
* of (missing or not) arguments. */
dfw_append_stack_pop(dfw, count);
/* We need another instruction to jump to another exit
* place, if the call() of our function failed for some reaosn */
@ -422,30 +433,19 @@ static dfvm_value_t *
gen_entity(dfwork_t *dfw, stnode_t *st_arg, GSList **jumps_ptr)
{
sttype_id_t e_type;
dfvm_insn_t *insn;
dfvm_value_t *val, *jmp;
dfvm_value_t *val;
header_field_info *hfinfo;
e_type = stnode_type_id(st_arg);
if (e_type == STTYPE_FIELD) {
hfinfo = stnode_data(st_arg);
val = dfw_append_read_tree(dfw, hfinfo, TRUE);
insn = dfvm_insn_new(IF_FALSE_GOTO);
jmp = dfvm_value_new(INSN_NUMBER);
insn->arg1 = dfvm_value_ref(jmp);
dfw_append_insn(dfw, insn);
*jumps_ptr = g_slist_prepend(*jumps_ptr, jmp);
val = dfw_append_read_tree(dfw, hfinfo);
*jumps_ptr = g_slist_prepend(*jumps_ptr, dfw_append_jump(dfw));
}
else if (e_type == STTYPE_REFERENCE) {
hfinfo = stnode_data(st_arg);
val = dfw_append_read_reference(dfw, hfinfo);
insn = dfvm_insn_new(IF_FALSE_GOTO);
jmp = dfvm_value_new(INSN_NUMBER);
insn->arg1 = dfvm_value_ref(jmp);
dfw_append_insn(dfw, insn);
*jumps_ptr = g_slist_prepend(*jumps_ptr, jmp);
*jumps_ptr = g_slist_prepend(*jumps_ptr, dfw_append_jump(dfw));
}
else if (e_type == STTYPE_FVALUE) {
val = dfvm_value_new_fvalue(stnode_steal_data(st_arg));
@ -463,7 +463,7 @@ gen_entity(dfwork_t *dfw, stnode_t *st_arg, GSList **jumps_ptr)
val = gen_arithmetic(dfw, st_arg, jumps_ptr);
}
else {
/* printf("sttype_id is %u\n", (unsigned)e_type); */
WS_DEBUG_HERE("sttype is %s", stnode_type_name(st_arg));
ws_assert_not_reached();
}
return val;

View File

@ -343,12 +343,12 @@ function(F) ::= UNPARSED(U) LPAREN RPAREN.
F = new_function(dfw, U);
}
function_params(P) ::= entity(E).
function_params(P) ::= arithmetic_expr(E).
{
P = g_slist_append(NULL, E);
}
function_params(P) ::= function_params(L) COMMA entity(E).
function_params(P) ::= function_params(L) COMMA arithmetic_expr(E).
{
P = g_slist_append(L, E);
}

View File

@ -38,13 +38,6 @@
static void
semcheck(dfwork_t *dfw, stnode_t *st_node);
static void
check_function(dfwork_t *dfw, stnode_t *st_node);
static
ftenum_t
check_arithmetic_expr(dfwork_t *dfw, stnode_t *st_node, ftenum_t lhs_ftype);
static fvalue_t *
mk_fvalue_from_val_string(dfwork_t *dfw, header_field_info *hfinfo, const char *s);
@ -552,7 +545,7 @@ check_exists(dfwork_t *dfw, stnode_t *st_arg1)
}
static void
check_drange_sanity(dfwork_t *dfw, stnode_t *st)
check_drange_sanity(dfwork_t *dfw, stnode_t *st, ftenum_t lhs_ftype)
{
stnode_t *entity1;
header_field_info *hfinfo1;
@ -573,8 +566,7 @@ check_drange_sanity(dfwork_t *dfw, stnode_t *st)
hfinfo1->abbrev, ftype_pretty_name(ftype1));
}
} else if (stnode_type_id(entity1) == STTYPE_FUNCTION) {
check_function(dfw, entity1);
ftype1 = sttype_function_retval_ftype(entity1);
ftype1 = check_function(dfw, entity1, lhs_ftype);
if (!ftype_can_slice(ftype1)) {
FAIL(dfw, entity1, "Return value of function \"%s\" is a %s and cannot be converted into a sequence of bytes.",
@ -582,7 +574,7 @@ check_drange_sanity(dfwork_t *dfw, stnode_t *st)
}
} else if (stnode_type_id(entity1) == STTYPE_RANGE) {
/* Should this be rejected instead? */
check_drange_sanity(dfw, entity1);
check_drange_sanity(dfw, entity1, lhs_ftype);
} else {
FAIL(dfw, entity1, "Range is not supported for entity %s",
stnode_todisplay(entity1));
@ -604,8 +596,8 @@ convert_to_bytes(stnode_t *arg)
sttype_range_set1(arg, entity1, rn);
}
static void
check_function(dfwork_t *dfw, stnode_t *st_node)
ftenum_t
check_function(dfwork_t *dfw, stnode_t *st_node, ftenum_t lhs_ftype)
{
df_func_def_t *funcdef;
GSList *params;
@ -625,7 +617,7 @@ check_function(dfwork_t *dfw, stnode_t *st_node)
funcdef->name, funcdef->max_nargs);
}
funcdef->semcheck_param_function(dfw, funcdef->name, params,
return funcdef->semcheck_param_function(dfw, funcdef->name, lhs_ftype, params,
stnode_location(st_node));
}
@ -718,7 +710,7 @@ again:
stnode_replace(st_arg2, STTYPE_FVALUE, fvalue);
}
else if (type2 == STTYPE_RANGE) {
check_drange_sanity(dfw, st_arg2);
check_drange_sanity(dfw, st_arg2, ftype1);
if (!is_bytes_type(ftype1)) {
if (!ftype_can_slice(ftype1)) {
FAIL(dfw, st_arg1, "\"%s\" is a %s and cannot be converted into a sequence of bytes.",
@ -731,8 +723,7 @@ again:
}
}
else if (type2 == STTYPE_FUNCTION) {
check_function(dfw, st_arg2);
ftype2 = sttype_function_retval_ftype(st_arg2);
ftype2 = check_function(dfw, st_arg2, ftype1);
if (!compatible_ftypes(ftype1, ftype2)) {
FAIL(dfw, st_arg2, "%s (type=%s) and return value of %s() (type=%s) are not of compatible types.",
@ -780,7 +771,7 @@ check_relation_LHS_RANGE(dfwork_t *dfw, test_op_t st_op,
LOG_NODE(st_node);
check_drange_sanity(dfw, st_arg1);
check_drange_sanity(dfw, st_arg1, FT_NONE);
again:
type2 = stnode_type_id(st_arg2);
@ -821,11 +812,10 @@ again:
stnode_replace(st_arg2, STTYPE_FVALUE, fvalue);
}
else if (type2 == STTYPE_RANGE) {
check_drange_sanity(dfw, st_arg2);
check_drange_sanity(dfw, st_arg2, FT_BYTES);
}
else if (type2 == STTYPE_FUNCTION) {
check_function(dfw, st_arg2);
ftype2 = sttype_function_retval_ftype(st_arg2);
ftype2 = check_function(dfw, st_arg2, FT_BYTES);
if (!is_bytes_type(ftype2)) {
if (!ftype_can_slice(ftype2)) {
@ -874,8 +864,7 @@ check_relation_LHS_FUNCTION(dfwork_t *dfw, test_op_t st_op,
LOG_NODE(st_node);
check_function(dfw, st_arg1);
ftype1 = sttype_function_retval_ftype(st_arg1);
ftype1 = check_function(dfw, st_arg1, FT_NONE);
if (!can_func(ftype1)) {
FAIL(dfw, st_arg1, "Function %s (type=%s) cannot participate in %s comparison.",
@ -922,7 +911,7 @@ again:
stnode_replace(st_arg2, STTYPE_FVALUE, fvalue);
}
else if (type2 == STTYPE_RANGE) {
check_drange_sanity(dfw, st_arg2);
check_drange_sanity(dfw, st_arg2, ftype1);
if (!is_bytes_type(ftype1)) {
if (!ftype_can_slice(ftype1)) {
FAIL(dfw, st_arg1, "Function \"%s\" is a %s and cannot be converted into a sequence of bytes.",
@ -935,8 +924,7 @@ again:
}
}
else if (type2 == STTYPE_FUNCTION) {
check_function(dfw, st_arg2);
ftype2 = sttype_function_retval_ftype(st_arg2);
ftype2 = check_function(dfw, st_arg2, ftype1);
if (!compatible_ftypes(ftype1, ftype2)) {
FAIL(dfw, st_arg2, "Return values of function %s (type=%s) and function %s (type=%s) are not of compatible types.",
@ -1241,11 +1229,10 @@ check_arithmetic_entity(dfwork_t *dfw, stnode_t *st_arg, ftenum_t lhs_ftype)
ftype = hfinfo->type;
}
else if (type == STTYPE_FUNCTION) {
check_function(dfw, st_arg);
ftype = sttype_function_retval_ftype(st_arg);
ftype = check_function(dfw, st_arg, lhs_ftype);
}
else if (type == STTYPE_RANGE) {
check_drange_sanity(dfw, st_arg);
check_drange_sanity(dfw, st_arg, lhs_ftype);
ftype = FT_BYTES;
}

View File

@ -14,5 +14,10 @@
gboolean
dfw_semcheck(dfwork_t *dfw);
ftenum_t
check_arithmetic_expr(dfwork_t *dfw, stnode_t *st_node, ftenum_t lhs_ftype);
ftenum_t
check_function(dfwork_t *dfw, stnode_t *st_node, ftenum_t lhs_ftype);
#endif

View File

@ -60,14 +60,20 @@ function_tostr(const void *data, gboolean pretty)
ws_assert(def);
g_string_printf(repr, "%s: ", def->name);
while (params != NULL) {
ws_assert(params->data);
g_string_append(repr, stnode_tostr(params->data, pretty));
params = params->next;
if (params != NULL) {
g_string_append(repr, ", ");
if (pretty) {
g_string_printf(repr, "%s(", def->name);
while (params != NULL) {
ws_assert(params->data);
g_string_append(repr, stnode_tostr(params->data, pretty));
params = params->next;
if (params != NULL) {
g_string_append(repr, ", ");
}
}
g_string_append_c(repr, ')');
}
else {
g_string_printf(repr, "%s#%u", def->name, g_slist_length(params));
}
return g_string_free(repr, FALSE);
@ -119,23 +125,6 @@ sttype_function_funcdef(stnode_t *node)
return stfuncrec->funcdef;
}
ftenum_t
sttype_function_retval_ftype(stnode_t *node)
{
function_t *stfuncrec;
stfuncrec = stnode_data(node);
ws_assert_magic(stfuncrec, FUNCTION_MAGIC);
if (stfuncrec->funcdef->retval_ftype != 0)
return stfuncrec->funcdef->retval_ftype;
if (stfuncrec->params) {
stnode_t *first_arg = stfuncrec->params->data;
return stnode_ftenum(first_arg);
}
return FT_NONE;
}
const char *
sttype_function_name(stnode_t *node)
{

View File

@ -21,8 +21,6 @@ sttype_function_set_params(stnode_t *node, GSList *params);
/* Get the function-definition record for a function stnode_t. */
df_func_def_t* sttype_function_funcdef(stnode_t *node);
ftenum_t sttype_function_retval_ftype(stnode_t *node);
const char *sttype_function_name(stnode_t *node);
/* Get the parameters for a function stnode_t. */

View File

@ -64,12 +64,12 @@ test_free(gpointer value)
g_free(test);
}
static const char *
test_todisplay(test_op_t op)
static char *
test_todisplay(const test_t *test)
{
const char *s = "<notset>";
switch(op) {
switch(test->op) {
case TEST_OP_NOT:
s = "!";
break;
@ -135,15 +135,15 @@ test_todisplay(test_op_t op)
s = "<uninitialized>";
break;
}
return s;
return g_strdup(s);
}
static const char *
test_todebug(test_op_t op)
static char *
test_todebug(const test_t *test)
{
const char *s = "<notset>";
switch(op) {
switch(test->op) {
case TEST_OP_NOT:
s = "TEST_NOT";
break;
@ -211,7 +211,8 @@ test_todebug(test_op_t op)
s = "<uninitialized>";
break;
}
return s;
return g_strdup(s);
}
static char *
@ -219,13 +220,10 @@ test_tostr(const void *value, gboolean pretty)
{
const test_t *test = value;
ws_assert_magic(test, TEST_MAGIC);
const char *s;
if (pretty)
s = test_todisplay(test->op);
else
s = test_todebug(test->op);
return g_strdup(s);
return test_todisplay(test);
return test_todebug(test);
}
static int

View File

@ -15,7 +15,6 @@
#include <wsutil/str_util.h>
#include <wsutil/glib-compat.h>
#include "sttype-test.h"
#include "sttype-pointer.h"
#include "sttype-function.h"
#include "dfilter-int.h"
@ -213,22 +212,6 @@ stnode_type_id(stnode_t *node)
return STTYPE_UNINITIALIZED;
}
ftenum_t
stnode_ftenum(stnode_t *node)
{
ws_assert_magic(node, STNODE_MAGIC);
switch (node->type->id) {
case STTYPE_FVALUE:
case STTYPE_FIELD:
return sttype_pointer_ftenum(node);
case STTYPE_FUNCTION:
return sttype_function_retval_ftype(node);
default:
break;
}
return FT_NONE;
}
gpointer
stnode_data(stnode_t *node)
{
@ -397,6 +380,7 @@ static void
visit_tree(wmem_strbuf_t *buf, stnode_t *node, int level)
{
stnode_t *left, *right;
GSList *params;
if (stnode_type_id(node) == STTYPE_TEST ||
stnode_type_id(node) == STTYPE_ARITHMETIC) {
@ -417,6 +401,18 @@ visit_tree(wmem_strbuf_t *buf, stnode_t *node, int level)
ws_assert_not_reached();
}
}
else if (stnode_type_id(node) == STTYPE_FUNCTION) {
wmem_strbuf_append_printf(buf, "%s:\n", stnode_todebug(node));
params = sttype_function_params(node);
while (params) {
indent(buf, level + 1);
visit_tree(buf, params->data, level + 1);
if (params->next != NULL) {
wmem_strbuf_append_c(buf, '\n');
}
params = params->next;
}
}
else {
wmem_strbuf_append(buf, stnode_todebug(node));
}

View File

@ -138,9 +138,6 @@ stnode_type_name(stnode_t *node);
sttype_id_t
stnode_type_id(stnode_t *node);
ftenum_t
stnode_ftenum(stnode_t *node);
gpointer
stnode_data(stnode_t *node);