| /* |
| * This file is part of ltrace. |
| * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc. |
| * Copyright (C) 1998,1999,2003,2007,2008,2009 Juan Cespedes |
| * Copyright (C) 2006 Ian Wienand |
| * Copyright (C) 2006 Steve Fink |
| * |
| * 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 2 of the |
| * License, 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, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA |
| * 02110-1301 USA |
| */ |
| |
| #include "config.h" |
| |
| #include <string.h> |
| #include <stdlib.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <error.h> |
| #include <assert.h> |
| |
| #include "common.h" |
| #include "output.h" |
| #include "expr.h" |
| #include "param.h" |
| #include "printf.h" |
| #include "zero.h" |
| #include "type.h" |
| #include "lens.h" |
| #include "lens_default.h" |
| #include "lens_enum.h" |
| |
| static int line_no; |
| static char *filename; |
| |
| static struct arg_type_info *parse_nonpointer_type(char **str, |
| struct param **extra_param, |
| size_t param_num, int *ownp); |
| static struct arg_type_info *parse_type(char **str, struct param **extra_param, |
| size_t param_num, int *ownp); |
| static struct arg_type_info *parse_lens(char **str, struct param **extra_param, |
| size_t param_num, int *ownp); |
| static int parse_enum(char **str, struct arg_type_info **retp, int *ownp); |
| |
| Function *list_of_functions = NULL; |
| |
| static int |
| parse_arg_type(char **name, enum arg_type *ret) |
| { |
| char *rest = NULL; |
| enum arg_type candidate; |
| |
| #define KEYWORD(KWD, TYPE) \ |
| do { \ |
| if (strncmp(*name, KWD, sizeof(KWD) - 1) == 0) { \ |
| rest = *name + sizeof(KWD) - 1; \ |
| candidate = TYPE; \ |
| goto ok; \ |
| } \ |
| } while (0) |
| |
| KEYWORD("void", ARGTYPE_VOID); |
| KEYWORD("int", ARGTYPE_INT); |
| KEYWORD("uint", ARGTYPE_UINT); |
| KEYWORD("long", ARGTYPE_LONG); |
| KEYWORD("ulong", ARGTYPE_ULONG); |
| KEYWORD("char", ARGTYPE_CHAR); |
| KEYWORD("short", ARGTYPE_SHORT); |
| KEYWORD("ushort", ARGTYPE_USHORT); |
| KEYWORD("float", ARGTYPE_FLOAT); |
| KEYWORD("double", ARGTYPE_DOUBLE); |
| KEYWORD("array", ARGTYPE_ARRAY); |
| KEYWORD("struct", ARGTYPE_STRUCT); |
| |
| assert(rest == NULL); |
| return -1; |
| |
| #undef KEYWORD |
| |
| ok: |
| if (isalnum(*rest)) |
| return -1; |
| |
| *name = rest; |
| *ret = candidate; |
| return 0; |
| } |
| |
| static void |
| eat_spaces(char **str) { |
| while (**str == ' ') { |
| (*str)++; |
| } |
| } |
| |
| static char * |
| xstrndup(char *str, size_t len) { |
| char *ret = (char *) malloc(len + 1); |
| if (ret == NULL) { |
| report_global_error("malloc: %s", strerror(errno)); |
| return NULL; |
| } |
| strncpy(ret, str, len); |
| ret[len] = 0; |
| return ret; |
| } |
| |
| static char * |
| parse_ident(char **str) { |
| char *ident = *str; |
| |
| if (!isalpha(**str) && **str != '_') { |
| report_error(filename, line_no, "bad identifier"); |
| return NULL; |
| } |
| |
| while (**str && (isalnum(**str) || **str == '_')) { |
| ++(*str); |
| } |
| |
| return xstrndup(ident, *str - ident); |
| } |
| |
| /* |
| Returns position in string at the left parenthesis which starts the |
| function's argument signature. Returns NULL on error. |
| */ |
| static char * |
| start_of_arg_sig(char *str) { |
| char *pos; |
| int stacked = 0; |
| |
| if (!strlen(str)) |
| return NULL; |
| |
| pos = &str[strlen(str)]; |
| do { |
| pos--; |
| if (pos < str) |
| return NULL; |
| while ((pos > str) && (*pos != ')') && (*pos != '(')) |
| pos--; |
| |
| if (*pos == ')') |
| stacked++; |
| else if (*pos == '(') |
| stacked--; |
| else |
| return NULL; |
| |
| } while (stacked > 0); |
| |
| return (stacked == 0) ? pos : NULL; |
| } |
| |
| static int |
| parse_int(char **str, long *ret) |
| { |
| char *end; |
| long n = strtol(*str, &end, 0); |
| if (end == *str) { |
| report_error(filename, line_no, "bad number"); |
| return -1; |
| } |
| |
| *str = end; |
| if (ret != NULL) |
| *ret = n; |
| return 0; |
| } |
| |
| static int |
| check_nonnegative(long l) |
| { |
| if (l < 0) { |
| report_error(filename, line_no, |
| "expected non-negative value, got %ld", l); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static int |
| check_int(long l) |
| { |
| int i = l; |
| if ((long)i != l) { |
| report_error(filename, line_no, |
| "Number too large: %ld", l); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static int |
| parse_char(char **str, char expected) |
| { |
| if (**str != expected) { |
| report_error(filename, line_no, |
| "expected '%c', got '%c'", expected, **str); |
| return -1; |
| } |
| |
| ++*str; |
| return 0; |
| } |
| |
| static struct expr_node *parse_argnum(char **str, int zero); |
| |
| static struct expr_node * |
| parse_zero(char **str, struct expr_node *ret) |
| { |
| eat_spaces(str); |
| if (**str == '(') { |
| ++*str; |
| struct expr_node *arg = parse_argnum(str, 0); |
| if (arg == NULL) |
| return NULL; |
| if (parse_char(str, ')') < 0) { |
| fail: |
| expr_destroy(arg); |
| free(arg); |
| return NULL; |
| } |
| |
| struct expr_node *ret = build_zero_w_arg(arg, 1); |
| if (ret == NULL) |
| goto fail; |
| return ret; |
| |
| } else { |
| return expr_node_zero(); |
| } |
| } |
| |
| static int |
| wrap_in_zero(struct expr_node **nodep) |
| { |
| struct expr_node *n = build_zero_w_arg(*nodep, 1); |
| if (n == NULL) |
| return -1; |
| *nodep = n; |
| return 0; |
| } |
| |
| /* |
| * Input: |
| * argN : The value of argument #N, counting from 1 |
| * eltN : The value of element #N of the containing structure |
| * retval : The return value |
| * N : The numeric value N |
| */ |
| static struct expr_node * |
| parse_argnum(char **str, int zero) |
| { |
| struct expr_node *expr = malloc(sizeof(*expr)); |
| if (expr == NULL) |
| return NULL; |
| |
| if (isdigit(**str)) { |
| long l; |
| if (parse_int(str, &l) < 0 |
| || check_nonnegative(l) < 0 |
| || check_int(l) < 0) |
| goto fail; |
| |
| expr_init_const_word(expr, l, type_get_simple(ARGTYPE_LONG), 0); |
| |
| if (zero && wrap_in_zero(&expr) < 0) |
| goto fail; |
| |
| return expr; |
| |
| } else { |
| char *name = parse_ident(str); |
| if (name == NULL) |
| goto fail; |
| |
| int is_arg = strncmp(name, "arg", 3) == 0; |
| int is_elt = !is_arg && strncmp(name, "elt", 3) == 0; |
| if (is_arg || is_elt) { |
| long l; |
| name += 3; |
| if (parse_int(&name, &l) < 0 |
| || check_int(l) < 0) |
| goto fail; |
| |
| if (is_arg) { |
| expr_init_argno(expr, l - 1); |
| } else { |
| struct expr_node *e_up = malloc(sizeof(*e_up)); |
| struct expr_node *e_ix = malloc(sizeof(*e_ix)); |
| if (e_up == NULL || e_ix == NULL) { |
| free(e_up); |
| free(e_ix); |
| goto fail; |
| } |
| |
| expr_init_up(e_up, expr_self(), 0); |
| struct arg_type_info *ti |
| = type_get_simple(ARGTYPE_LONG); |
| expr_init_const_word(e_ix, l - 1, ti, 0); |
| expr_init_index(expr, e_up, 1, e_ix, 1); |
| } |
| |
| } else if (strcmp(name, "retval") == 0) { |
| expr_init_named(expr, "retval", 0); |
| |
| } else if (strcmp(name, "zero") == 0) { |
| struct expr_node *ret = parse_zero(str, expr); |
| if (ret == NULL) |
| goto fail; |
| return ret; |
| |
| } else { |
| report_error(filename, line_no, |
| "Unknown length specifier: '%s'", name); |
| goto fail; |
| } |
| |
| if (zero && wrap_in_zero(&expr) < 0) |
| goto fail; |
| |
| return expr; |
| } |
| |
| fail: |
| free(expr); |
| return NULL; |
| } |
| |
| struct typedef_node_t { |
| char *name; |
| struct arg_type_info *info; |
| int own_type; |
| struct typedef_node_t *next; |
| } *typedefs = NULL; |
| |
| static struct arg_type_info * |
| lookup_typedef(char **str) { |
| struct typedef_node_t *node; |
| char *end = *str; |
| while (*end && (isalnum(*end) || *end == '_')) |
| ++end; |
| if (end == *str) |
| return NULL; |
| |
| for (node = typedefs; node != NULL; node = node->next) { |
| if (strncmp(*str, node->name, end - *str) == 0) { |
| (*str) += strlen(node->name); |
| return node->info; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static struct typedef_node_t * |
| insert_typedef(char *name, struct arg_type_info *info, int own_type) |
| { |
| struct typedef_node_t *binding = malloc(sizeof(*binding)); |
| binding->name = name; |
| binding->info = info; |
| binding->own_type = own_type; |
| binding->next = typedefs; |
| typedefs = binding; |
| return binding; |
| } |
| |
| static void |
| parse_typedef(char **str) { |
| char *name; |
| struct arg_type_info *info; |
| |
| (*str) += strlen("typedef"); |
| eat_spaces(str); |
| |
| // Grab out the name of the type |
| name = parse_ident(str); |
| |
| // Skip = sign |
| eat_spaces(str); |
| if (parse_char(str, '=') < 0) |
| return; |
| eat_spaces(str); |
| |
| // Parse the type |
| int own; |
| info = parse_type(str, NULL, 0, &own); |
| |
| insert_typedef(name, info, own); |
| } |
| |
| static void |
| destroy_fun(Function *fun) |
| { |
| size_t i; |
| if (fun == NULL) |
| return; |
| if (fun->own_return_info) { |
| type_destroy(fun->return_info); |
| free(fun->return_info); |
| } |
| for (i = 0; i < fun->num_params; ++i) |
| param_destroy(&fun->params[i]); |
| free(fun->params); |
| } |
| |
| /* Syntax: struct ( type,type,type,... ) */ |
| static int |
| parse_struct(char **str, struct arg_type_info *info) |
| { |
| eat_spaces(str); |
| if (parse_char(str, '(') < 0) |
| return -1; |
| |
| eat_spaces(str); // Empty arg list with whitespace inside |
| |
| type_init_struct(info); |
| |
| while (1) { |
| eat_spaces(str); |
| if (**str == 0 || **str == ')') { |
| parse_char(str, ')'); |
| return 0; |
| } |
| |
| /* Field delimiter. */ |
| if (type_struct_size(info) > 0) |
| parse_char(str, ','); |
| |
| eat_spaces(str); |
| int own; |
| struct arg_type_info *field = parse_lens(str, NULL, 0, &own); |
| if (field == NULL || type_struct_add(info, field, own)) { |
| type_destroy(info); |
| return -1; |
| } |
| } |
| } |
| |
| static int |
| parse_string(char **str, struct arg_type_info **retp, int *ownp) |
| { |
| struct arg_type_info *info = malloc(sizeof(*info) * 2); |
| if (info == NULL) { |
| fail: |
| free(info); |
| return -1; |
| } |
| |
| struct expr_node *length; |
| int own_length; |
| int with_arg = 0; |
| |
| if (isdigit(**str)) { |
| /* string0 is string[retval], length is zero(retval) |
| * stringN is string[argN], length is zero(argN) */ |
| long l; |
| if (parse_int(str, &l) < 0 |
| || check_int(l) < 0) |
| goto fail; |
| |
| struct expr_node *length_arg = malloc(sizeof(*length_arg)); |
| if (length_arg == NULL) |
| goto fail; |
| |
| if (l == 0) |
| expr_init_named(length_arg, "retval", 0); |
| else |
| expr_init_argno(length_arg, l - 1); |
| |
| length = build_zero_w_arg(length_arg, 1); |
| if (length == NULL) { |
| expr_destroy(length_arg); |
| free(length_arg); |
| goto fail; |
| } |
| own_length = 1; |
| |
| } else { |
| eat_spaces(str); |
| if (**str == '[') { |
| (*str)++; |
| eat_spaces(str); |
| |
| length = parse_argnum(str, 1); |
| if (length == NULL) |
| goto fail; |
| own_length = 1; |
| |
| eat_spaces(str); |
| parse_char(str, ']'); |
| |
| } else if (**str == '(') { |
| /* Usage of "string" as lens. */ |
| ++*str; |
| |
| free(info); |
| |
| eat_spaces(str); |
| info = parse_type(str, NULL, 0, ownp); |
| if (info == NULL) |
| goto fail; |
| |
| eat_spaces(str); |
| parse_char(str, ')'); |
| |
| with_arg = 1; |
| |
| } else { |
| /* It was just a simple string after all. */ |
| length = expr_node_zero(); |
| own_length = 0; |
| } |
| } |
| |
| /* String is a pointer to array of chars. */ |
| if (!with_arg) { |
| type_init_array(&info[1], type_get_simple(ARGTYPE_CHAR), 0, |
| length, own_length); |
| |
| type_init_pointer(&info[0], &info[1], 0); |
| *ownp = 1; |
| } |
| |
| info->lens = &string_lens; |
| info->own_lens = 0; |
| |
| *retp = info; |
| return 0; |
| } |
| |
| static int |
| build_printf_pack(struct param **packp, size_t param_num) |
| { |
| if (packp == NULL) { |
| report_error(filename, line_no, |
| "'format' type in unexpected context"); |
| return -1; |
| } |
| if (*packp != NULL) { |
| report_error(filename, line_no, |
| "only one 'format' type per function supported"); |
| return -1; |
| } |
| |
| *packp = malloc(sizeof(**packp)); |
| if (*packp == NULL) |
| return -1; |
| |
| struct expr_node *node = malloc(sizeof(*node)); |
| if (node == NULL) { |
| free(*packp); |
| return -1; |
| } |
| |
| expr_init_argno(node, param_num); |
| |
| param_pack_init_printf(*packp, node, 1); |
| |
| return 0; |
| } |
| |
| /* Match and consume KWD if it's next in stream, and return 0. |
| * Otherwise return negative number. */ |
| static int |
| try_parse_kwd(char **str, const char *kwd) |
| { |
| size_t len = strlen(kwd); |
| if (strncmp(*str, kwd, len) == 0 |
| && !isalnum((*str)[len])) { |
| (*str) += len; |
| return 0; |
| } |
| return -1; |
| } |
| |
| /* Make a copy of INFO and set the *OWN bit if it's not already |
| * owned. */ |
| static int |
| unshare_type_info(struct arg_type_info **infop, int *ownp) |
| { |
| if (*ownp) |
| return 0; |
| |
| struct arg_type_info *ninfo = malloc(sizeof(*ninfo)); |
| if (ninfo == NULL) { |
| report_error(filename, line_no, |
| "malloc: %s", strerror(errno)); |
| return -1; |
| } |
| *ninfo = **infop; |
| *infop = ninfo; |
| *ownp = 1; |
| return 0; |
| } |
| |
| /* XXX extra_param and param_num are a kludge to get in |
| * backward-compatible support for "format" parameter type. The |
| * latter is only valid if the former is non-NULL, which is only in |
| * top-level context. */ |
| static int |
| parse_alias(char **str, struct arg_type_info **retp, int *ownp, |
| struct param **extra_param, size_t param_num) |
| { |
| /* For backward compatibility, we need to support things like |
| * stringN (which is like string[argN], string[N], and also |
| * bare string. We might, in theory, replace this by |
| * preprocessing configure file sources with M4, but for now, |
| * "string" is syntax. */ |
| if (strncmp(*str, "string", 6) == 0) { |
| (*str) += 6; |
| return parse_string(str, retp, ownp); |
| |
| } else if (try_parse_kwd(str, "format") >= 0 |
| && extra_param != NULL) { |
| /* For backward compatibility, format is parsed as |
| * "string", but it smuggles to the parameter list of |
| * a function a "printf" argument pack with this |
| * parameter as argument. */ |
| if (parse_string(str, retp, ownp) < 0) |
| return -1; |
| |
| return build_printf_pack(extra_param, param_num); |
| |
| } else if (try_parse_kwd(str, "enum") >=0) { |
| |
| return parse_enum(str, retp, ownp); |
| |
| } else { |
| *retp = NULL; |
| return 0; |
| } |
| } |
| |
| /* Syntax: array ( type, N|argN ) */ |
| static int |
| parse_array(char **str, struct arg_type_info *info) |
| { |
| eat_spaces(str); |
| if (parse_char(str, '(') < 0) |
| return -1; |
| |
| eat_spaces(str); |
| int own; |
| struct arg_type_info *elt_info = parse_lens(str, NULL, 0, &own); |
| if (elt_info == NULL) |
| return -1; |
| |
| eat_spaces(str); |
| parse_char(str, ','); |
| |
| eat_spaces(str); |
| struct expr_node *length = parse_argnum(str, 0); |
| if (length == NULL) { |
| if (own) { |
| type_destroy(elt_info); |
| free(elt_info); |
| } |
| return -1; |
| } |
| |
| type_init_array(info, elt_info, own, length, 1); |
| |
| eat_spaces(str); |
| parse_char(str, ')'); |
| return 0; |
| } |
| |
| /* Syntax: |
| * enum (keyname[=value],keyname[=value],... ) |
| * enum<type> (keyname[=value],keyname[=value],... ) |
| */ |
| static int |
| parse_enum(char **str, struct arg_type_info **retp, int *ownp) |
| { |
| /* Optional type argument. */ |
| eat_spaces(str); |
| if (**str == '[') { |
| parse_char(str, '['); |
| eat_spaces(str); |
| *retp = parse_nonpointer_type(str, NULL, 0, ownp); |
| if (*retp == NULL) |
| return -1; |
| |
| if (!type_is_integral((*retp)->type)) { |
| report_error(filename, line_no, |
| "integral type required as enum argument"); |
| fail: |
| if (*ownp) { |
| /* This also releases associated lens |
| * if any was set so far. */ |
| type_destroy(*retp); |
| free(*retp); |
| } |
| return -1; |
| } |
| |
| eat_spaces(str); |
| if (parse_char(str, ']') < 0) |
| goto fail; |
| |
| } else { |
| *retp = type_get_simple(ARGTYPE_INT); |
| *ownp = 0; |
| } |
| |
| /* We'll need to set the lens, so unshare. */ |
| if (unshare_type_info(retp, ownp) < 0) |
| goto fail; |
| |
| eat_spaces(str); |
| if (parse_char(str, '(') < 0) |
| goto fail; |
| |
| struct enum_lens *lens = malloc(sizeof(*lens)); |
| if (lens == NULL) { |
| report_error(filename, line_no, |
| "malloc enum lens: %s", strerror(errno)); |
| return -1; |
| } |
| |
| lens_init_enum(lens); |
| (*retp)->lens = &lens->super; |
| (*retp)->own_lens = 1; |
| |
| long last_val = 0; |
| while (1) { |
| eat_spaces(str); |
| if (**str == 0 || **str == ')') { |
| parse_char(str, ')'); |
| return 0; |
| } |
| |
| /* Field delimiter. XXX should we support the C |
| * syntax, where the enumeration can end in pending |
| * comma? */ |
| if (lens_enum_size(lens) > 0) |
| parse_char(str, ','); |
| |
| eat_spaces(str); |
| char *key = parse_ident(str); |
| if (key == NULL) { |
| err: |
| free(key); |
| goto fail; |
| } |
| |
| if (**str == '=') { |
| ++*str; |
| eat_spaces(str); |
| if (parse_int(str, &last_val) < 0) |
| goto err; |
| } |
| |
| struct value *value = malloc(sizeof(*value)); |
| if (value == NULL) |
| goto err; |
| value_init_detached(value, NULL, *retp, 0); |
| value_set_long(value, last_val); |
| |
| if (lens_enum_add(lens, key, 1, value, 1) < 0) |
| goto err; |
| |
| last_val++; |
| } |
| |
| return 0; |
| } |
| |
| static struct arg_type_info * |
| parse_nonpointer_type(char **str, struct param **extra_param, size_t param_num, |
| int *ownp) |
| { |
| enum arg_type type; |
| if (parse_arg_type(str, &type) < 0) { |
| struct arg_type_info *simple; |
| if (parse_alias(str, &simple, ownp, extra_param, param_num) < 0) |
| return NULL; |
| if (simple == NULL) |
| simple = lookup_typedef(str); |
| if (simple != NULL) { |
| *ownp = 0; |
| return simple; |
| } |
| |
| report_error(filename, line_no, |
| "unknown type around '%s'", *str); |
| return NULL; |
| } |
| |
| int (*parser) (char **, struct arg_type_info *) = NULL; |
| |
| /* For some types that's all we need. */ |
| switch (type) { |
| case ARGTYPE_VOID: |
| case ARGTYPE_INT: |
| case ARGTYPE_UINT: |
| case ARGTYPE_LONG: |
| case ARGTYPE_ULONG: |
| case ARGTYPE_CHAR: |
| case ARGTYPE_SHORT: |
| case ARGTYPE_USHORT: |
| case ARGTYPE_FLOAT: |
| case ARGTYPE_DOUBLE: |
| *ownp = 0; |
| return type_get_simple(type); |
| |
| case ARGTYPE_ARRAY: |
| parser = parse_array; |
| break; |
| |
| case ARGTYPE_STRUCT: |
| parser = parse_struct; |
| break; |
| |
| case ARGTYPE_POINTER: |
| /* Pointer syntax is not based on keyword, so we |
| * should never get this type. */ |
| assert(type != ARGTYPE_POINTER); |
| abort(); |
| } |
| |
| struct arg_type_info *info = malloc(sizeof(*info)); |
| if (info == NULL) { |
| report_error(filename, line_no, |
| "malloc: %s", strerror(errno)); |
| return NULL; |
| } |
| *ownp = 1; |
| |
| if (parser(str, info) < 0) { |
| free(info); |
| return NULL; |
| } |
| |
| return info; |
| } |
| |
| static struct named_lens { |
| const char *name; |
| struct lens *lens; |
| } lenses[] = { |
| { "hide", &blind_lens }, |
| { "octal", &octal_lens }, |
| { "hex", &hex_lens }, |
| { "bool", &bool_lens }, |
| { "guess", &guess_lens }, |
| }; |
| |
| static struct lens * |
| name2lens(char **str, int *own_lensp) |
| { |
| size_t i; |
| for (i = 0; i < sizeof(lenses)/sizeof(*lenses); ++i) |
| if (try_parse_kwd(str, lenses[i].name) == 0) { |
| *own_lensp = 0; |
| return lenses[i].lens; |
| } |
| |
| return NULL; |
| } |
| |
| static struct arg_type_info * |
| parse_type(char **str, struct param **extra_param, size_t param_num, int *ownp) |
| { |
| struct arg_type_info *info |
| = parse_nonpointer_type(str, extra_param, param_num, ownp); |
| if (info == NULL) |
| return NULL; |
| |
| while (1) { |
| eat_spaces(str); |
| if (**str == '*') { |
| struct arg_type_info *outer = malloc(sizeof(*outer)); |
| if (outer == NULL) { |
| if (*ownp) { |
| type_destroy(info); |
| free(info); |
| } |
| report_error(filename, line_no, |
| "malloc: %s", strerror(errno)); |
| return NULL; |
| } |
| type_init_pointer(outer, info, *ownp); |
| *ownp = 1; |
| (*str)++; |
| info = outer; |
| } else |
| break; |
| } |
| return info; |
| } |
| |
| static struct arg_type_info * |
| parse_lens(char **str, struct param **extra_param, size_t param_num, int *ownp) |
| { |
| int own_lens; |
| struct lens *lens = name2lens(str, &own_lens); |
| int has_args = 1; |
| struct arg_type_info *info; |
| if (lens != NULL) { |
| eat_spaces(str); |
| |
| /* Octal lens gets special treatment, because of |
| * backward compatibility. */ |
| if (lens == &octal_lens && **str != '(') { |
| has_args = 0; |
| info = type_get_simple(ARGTYPE_INT); |
| *ownp = 0; |
| } else if (parse_char(str, '(') < 0) { |
| report_error(filename, line_no, |
| "expected type argument after the lens"); |
| return NULL; |
| } |
| } |
| |
| if (has_args) { |
| eat_spaces(str); |
| info = parse_type(str, extra_param, param_num, ownp); |
| if (info == NULL) { |
| fail: |
| if (own_lens && lens != NULL) |
| lens_destroy(lens); |
| return NULL; |
| } |
| } |
| |
| if (lens != NULL && has_args) { |
| eat_spaces(str); |
| parse_char(str, ')'); |
| } |
| |
| /* We can't modify shared types. Make a copy if we have a |
| * lens. */ |
| if (lens != NULL && unshare_type_info(&info, ownp) < 0) |
| goto fail; |
| |
| if (lens != NULL) { |
| info->lens = lens; |
| info->own_lens = own_lens; |
| } |
| |
| return info; |
| } |
| |
| static int |
| add_param(Function *fun, size_t *allocdp) |
| { |
| size_t allocd = *allocdp; |
| /* XXX +1 is for the extra_param handling hack. */ |
| if ((fun->num_params + 1) >= allocd) { |
| allocd = allocd > 0 ? 2 * allocd : 8; |
| void *na = realloc(fun->params, sizeof(*fun->params) * allocd); |
| if (na == NULL) |
| return -1; |
| |
| fun->params = na; |
| *allocdp = allocd; |
| } |
| return 0; |
| } |
| |
| static Function * |
| process_line(char *buf) { |
| char *str = buf; |
| char *tmp; |
| |
| line_no++; |
| debug(3, "Reading line %d of `%s'", line_no, filename); |
| eat_spaces(&str); |
| |
| /* A comment or empty line. */ |
| if (*str == ';' || *str == 0 || *str == '\n') |
| return NULL; |
| |
| if (strncmp(str, "typedef", 7) == 0) { |
| parse_typedef(&str); |
| return NULL; |
| } |
| |
| Function *fun = calloc(1, sizeof(*fun)); |
| if (fun == NULL) { |
| report_error(filename, line_no, |
| "alloc function: %s", strerror(errno)); |
| return NULL; |
| } |
| |
| fun->return_info = parse_lens(&str, NULL, 0, &fun->own_return_info); |
| if (fun->return_info == NULL) { |
| err: |
| debug(3, " Skipping line %d", line_no); |
| destroy_fun(fun); |
| return NULL; |
| } |
| debug(4, " return_type = %d", fun->return_info->type); |
| |
| eat_spaces(&str); |
| tmp = start_of_arg_sig(str); |
| if (tmp == NULL) { |
| report_error(filename, line_no, "syntax error"); |
| goto err; |
| } |
| *tmp = '\0'; |
| fun->name = strdup(str); |
| str = tmp + 1; |
| debug(3, " name = %s", fun->name); |
| |
| size_t allocd = 0; |
| struct param *extra_param = NULL; |
| |
| int have_stop = 0; |
| |
| while (1) { |
| eat_spaces(&str); |
| if (*str == ')') |
| break; |
| |
| if (str[0] == '+') { |
| if (have_stop == 0) { |
| if (add_param(fun, &allocd) < 0) |
| goto add_err; |
| param_init_stop |
| (&fun->params[fun->num_params++]); |
| have_stop = 1; |
| } |
| str++; |
| } |
| |
| if (add_param(fun, &allocd) < 0) { |
| add_err: |
| report_error(filename, line_no, "(re)alloc params: %s", |
| strerror(errno)); |
| goto err; |
| } |
| |
| int own; |
| struct arg_type_info *type = parse_lens(&str, &extra_param, |
| fun->num_params, &own); |
| if (type == NULL) { |
| report_error(filename, line_no, |
| "unknown argument type"); |
| goto err; |
| } |
| |
| /* XXX We used to allow void parameter as a synonym to |
| * an argument that shouldn't be displayed. We may |
| * wish to re-introduce this when lenses are |
| * implemented, as a synonym, but backends generally |
| * need to know the type, so disallow bare void for |
| * now. */ |
| if (type->type == ARGTYPE_VOID) { |
| report_warning(filename, line_no, |
| "void parameter assumed to be 'int'"); |
| if (own) { |
| type_destroy(type); |
| free(type); |
| } |
| type = type_get_simple(ARGTYPE_INT); |
| own = 0; |
| } |
| |
| param_init_type(&fun->params[fun->num_params++], type, own); |
| |
| eat_spaces(&str); |
| if (*str == ',') { |
| str++; |
| continue; |
| } else if (*str == ')') { |
| continue; |
| } else { |
| if (str[strlen(str) - 1] == '\n') |
| str[strlen(str) - 1] = '\0'; |
| report_error(filename, line_no, |
| "syntax error around \"%s\"", str); |
| goto err; |
| } |
| } |
| |
| if (extra_param != NULL) { |
| assert(fun->num_params < allocd); |
| memcpy(&fun->params[fun->num_params++], extra_param, |
| sizeof(*extra_param)); |
| } |
| |
| return fun; |
| } |
| |
| void |
| init_global_config(void) |
| { |
| struct arg_type_info *info = malloc(2 * sizeof(*info)); |
| if (info == NULL) |
| error(1, errno, "malloc in init_global_config"); |
| |
| memset(info, 0, 2 * sizeof(*info)); |
| info[0].type = ARGTYPE_POINTER; |
| info[0].u.ptr_info.info = &info[1]; |
| info[1].type = ARGTYPE_VOID; |
| |
| insert_typedef(strdup("addr"), info, 0); |
| insert_typedef(strdup("file"), info, 1); |
| } |
| |
| void |
| read_config_file(char *file) { |
| FILE *stream; |
| char buf[1024]; |
| |
| filename = file; |
| stream = fopen(filename, "r"); |
| if (!stream) { |
| return; |
| } |
| |
| debug(1, "Reading config file `%s'...", filename); |
| |
| line_no = 0; |
| while (fgets(buf, 1024, stream)) { |
| Function *tmp; |
| |
| tmp = process_line(buf); |
| |
| if (tmp) { |
| debug(2, "New function: `%s'", tmp->name); |
| tmp->next = list_of_functions; |
| list_of_functions = tmp; |
| } |
| } |
| fclose(stream); |
| } |