| /* |
| * This file is part of ltrace. |
| * Copyright (C) 2011,2012,2013 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 |
| */ |
| |
| /* getline is POSIX.1-2008. It was originally a GNU extension, and |
| * chances are uClibc still needs _GNU_SOURCE, but for now try it this |
| * way. */ |
| #define _POSIX_C_SOURCE 200809L |
| |
| #include "config.h" |
| |
| #include <string.h> |
| #include <stdlib.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <assert.h> |
| |
| #include "common.h" |
| #include "output.h" |
| #include "expr.h" |
| #include "param.h" |
| #include "printf.h" |
| #include "prototype.h" |
| #include "zero.h" |
| #include "type.h" |
| #include "lens.h" |
| #include "lens_default.h" |
| #include "lens_enum.h" |
| |
| /* Lifted from GCC: The ctype functions are often implemented as |
| * macros which do lookups in arrays using the parameter as the |
| * offset. If the ctype function parameter is a char, then gcc will |
| * (appropriately) warn that a "subscript has type char". Using a |
| * (signed) char as a subscript is bad because you may get negative |
| * offsets and thus it is not 8-bit safe. The CTYPE_CONV macro |
| * ensures that the parameter is cast to an unsigned char when a char |
| * is passed in. When an int is passed in, the parameter is left |
| * alone so we don't lose EOF. */ |
| |
| #define CTYPE_CONV(CH) \ |
| (sizeof(CH) == sizeof(unsigned char) ? (int)(unsigned char)(CH) : (int)(CH)) |
| |
| struct locus |
| { |
| const char *filename; |
| int line_no; |
| }; |
| |
| static struct arg_type_info *parse_nonpointer_type(struct protolib *plib, |
| struct locus *loc, |
| char **str, |
| struct param **extra_param, |
| size_t param_num, |
| int *ownp, int *forwardp); |
| static struct arg_type_info *parse_type(struct protolib *plib, |
| struct locus *loc, |
| char **str, struct param **extra_param, |
| size_t param_num, int *ownp, |
| int *forwardp); |
| static struct arg_type_info *parse_lens(struct protolib *plib, |
| struct locus *loc, |
| char **str, struct param **extra_param, |
| size_t param_num, int *ownp, |
| int *forwardp); |
| static int parse_enum(struct protolib *plib, struct locus *loc, |
| char **str, struct arg_type_info **retp, int *ownp); |
| |
| struct prototype *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); |
| |
| /* Misspelling of int used in ltrace.conf that we used to |
| * ship. */ |
| KEYWORD("itn", ARGTYPE_INT); |
| |
| assert(rest == NULL); |
| return -1; |
| |
| #undef KEYWORD |
| |
| ok: |
| if (isalnum(CTYPE_CONV(*rest)) || *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(struct locus *loc, char **str) |
| { |
| char *ident = *str; |
| |
| if (!isalpha(CTYPE_CONV(**str)) && **str != '_') { |
| report_error(loc->filename, loc->line_no, "bad identifier"); |
| return NULL; |
| } |
| |
| while (**str && (isalnum(CTYPE_CONV(**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(struct locus *loc, char **str, long *ret) |
| { |
| char *end; |
| long n = strtol(*str, &end, 0); |
| if (end == *str) { |
| report_error(loc->filename, loc->line_no, "bad number"); |
| return -1; |
| } |
| |
| *str = end; |
| if (ret != NULL) |
| *ret = n; |
| return 0; |
| } |
| |
| static int |
| check_nonnegative(struct locus *loc, long l) |
| { |
| if (l < 0) { |
| report_error(loc->filename, loc->line_no, |
| "expected non-negative value, got %ld", l); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static int |
| check_int(struct locus *loc, long l) |
| { |
| int i = l; |
| if ((long)i != l) { |
| report_error(loc->filename, loc->line_no, |
| "Number too large: %ld", l); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static int |
| parse_char(struct locus *loc, char **str, char expected) |
| { |
| if (**str != expected) { |
| report_error(loc->filename, loc->line_no, |
| "expected '%c', got '%c'", expected, **str); |
| return -1; |
| } |
| |
| ++*str; |
| return 0; |
| } |
| |
| static struct expr_node *parse_argnum(struct locus *loc, |
| char **str, int *ownp, int zero); |
| |
| static struct expr_node * |
| parse_zero(struct locus *loc, char **str, int *ownp) |
| { |
| eat_spaces(str); |
| if (**str == '(') { |
| ++*str; |
| int own; |
| struct expr_node *arg = parse_argnum(loc, str, &own, 0); |
| if (arg == NULL) |
| return NULL; |
| if (parse_char(loc, str, ')') < 0) { |
| fail: |
| expr_destroy(arg); |
| free(arg); |
| return NULL; |
| } |
| |
| struct expr_node *ret = build_zero_w_arg(arg, own); |
| if (ret == NULL) |
| goto fail; |
| *ownp = 1; |
| return ret; |
| |
| } else { |
| *ownp = 0; |
| 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(struct locus *loc, char **str, int *ownp, int zero) |
| { |
| struct expr_node *expr = malloc(sizeof(*expr)); |
| if (expr == NULL) |
| return NULL; |
| |
| if (isdigit(CTYPE_CONV(**str))) { |
| long l; |
| if (parse_int(loc, str, &l) < 0 |
| || check_nonnegative(loc, l) < 0 |
| || check_int(loc, 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; |
| |
| *ownp = 1; |
| return expr; |
| |
| } else { |
| char *const name = parse_ident(loc, str); |
| if (name == NULL) { |
| fail_ident: |
| free(name); |
| goto fail; |
| } |
| |
| int is_arg = strncmp(name, "arg", 3) == 0; |
| if (is_arg || strncmp(name, "elt", 3) == 0) { |
| long l; |
| char *num = name + 3; |
| if (parse_int(loc, &num, &l) < 0 |
| || check_int(loc, l) < 0) |
| goto fail_ident; |
| |
| if (is_arg) { |
| if (l == 0) |
| expr_init_named(expr, "retval", 0); |
| else |
| 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_ident; |
| } |
| |
| 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(loc, str, ownp); |
| if (ret == NULL) |
| goto fail_ident; |
| free(expr); |
| free(name); |
| return ret; |
| |
| } else { |
| report_error(loc->filename, loc->line_no, |
| "Unknown length specifier: '%s'", name); |
| goto fail_ident; |
| } |
| |
| if (zero && wrap_in_zero(&expr) < 0) |
| goto fail_ident; |
| |
| free(name); |
| *ownp = 1; |
| return expr; |
| } |
| |
| fail: |
| free(expr); |
| return NULL; |
| } |
| |
| static struct arg_type_info * |
| parse_typedef_name(struct protolib *plib, char **str) |
| { |
| char *end = *str; |
| while (*end && (isalnum(CTYPE_CONV(*end)) || *end == '_')) |
| ++end; |
| if (end == *str) |
| return NULL; |
| |
| size_t len = end - *str; |
| char buf[len + 1]; |
| memcpy(buf, *str, len); |
| *str += len; |
| buf[len] = 0; |
| |
| struct named_type *nt = protolib_lookup_type(plib, buf, true); |
| if (nt == NULL) |
| return NULL; |
| return nt->info; |
| } |
| |
| static int |
| parse_typedef(struct protolib *plib, struct locus *loc, char **str) |
| { |
| (*str) += strlen("typedef"); |
| eat_spaces(str); |
| char *name = parse_ident(loc, str); |
| |
| /* Look through the typedef list whether we already have a |
| * forward of this type. If we do, it must be forward |
| * structure. */ |
| struct named_type *forward = protolib_lookup_type(plib, name, true); |
| if (forward != NULL |
| && (forward->info->type != ARGTYPE_STRUCT |
| || !forward->forward)) { |
| report_error(loc->filename, loc->line_no, |
| "Redefinition of typedef '%s'", name); |
| err: |
| free(name); |
| return -1; |
| } |
| |
| // Skip = sign |
| eat_spaces(str); |
| if (parse_char(loc, str, '=') < 0) |
| goto err; |
| eat_spaces(str); |
| |
| int fwd = 0; |
| int own = 0; |
| struct arg_type_info *info |
| = parse_lens(plib, loc, str, NULL, 0, &own, &fwd); |
| if (info == NULL) |
| goto err; |
| |
| struct named_type this_nt; |
| named_type_init(&this_nt, info, own); |
| this_nt.forward = fwd; |
| |
| if (forward == NULL) { |
| if (protolib_add_named_type(plib, name, 1, &this_nt) < 0) { |
| named_type_destroy(&this_nt); |
| goto err; |
| } |
| return 0; |
| } |
| |
| /* If we are defining a forward, make sure the definition is a |
| * structure as well. */ |
| if (this_nt.info->type != ARGTYPE_STRUCT) { |
| report_error(loc->filename, loc->line_no, |
| "Definition of forward '%s' must be a structure.", |
| name); |
| named_type_destroy(&this_nt); |
| goto err; |
| } |
| |
| /* Now move guts of the actual type over to the forward type. |
| * We can't just move pointers around, because references to |
| * forward must stay intact. */ |
| assert(this_nt.own_type); |
| type_destroy(forward->info); |
| *forward->info = *this_nt.info; |
| forward->forward = 0; |
| free(this_nt.info); |
| free(name); |
| return 0; |
| } |
| |
| /* Syntax: struct ( type,type,type,... ) */ |
| static int |
| parse_struct(struct protolib *plib, struct locus *loc, |
| char **str, struct arg_type_info *info, |
| int *forwardp) |
| { |
| eat_spaces(str); |
| |
| if (**str == ';') { |
| if (forwardp == NULL) { |
| report_error(loc->filename, loc->line_no, |
| "Forward struct can be declared only " |
| "directly after a typedef."); |
| return -1; |
| } |
| |
| /* Forward declaration is currently handled as an |
| * empty struct. */ |
| type_init_struct(info); |
| *forwardp = 1; |
| return 0; |
| } |
| |
| if (parse_char(loc, 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(loc, str, ')'); |
| return 0; |
| } |
| |
| /* Field delimiter. */ |
| if (type_struct_size(info) > 0) |
| parse_char(loc, str, ','); |
| |
| eat_spaces(str); |
| int own; |
| struct arg_type_info *field |
| = parse_lens(plib, loc, str, NULL, 0, &own, NULL); |
| if (field == NULL || type_struct_add(info, field, own)) { |
| type_destroy(info); |
| return -1; |
| } |
| } |
| } |
| |
| /* Make a copy of INFO and set the *OWN bit if it's not already |
| * owned. */ |
| static int |
| unshare_type_info(struct locus *loc, struct arg_type_info **infop, int *ownp) |
| { |
| if (*ownp) |
| return 0; |
| |
| struct arg_type_info *ninfo = malloc(sizeof(*ninfo)); |
| if (ninfo == NULL || type_clone(ninfo, *infop) < 0) { |
| report_error(loc->filename, loc->line_no, |
| "malloc: %s", strerror(errno)); |
| free(ninfo); |
| return -1; |
| } |
| *infop = ninfo; |
| *ownp = 1; |
| return 0; |
| } |
| |
| static int |
| parse_string(struct protolib *plib, struct locus *loc, |
| char **str, struct arg_type_info **retp, int *ownp) |
| { |
| struct arg_type_info *info = NULL; |
| struct expr_node *length; |
| int own_length; |
| |
| if (isdigit(CTYPE_CONV(**str))) { |
| /* string0 is string[retval], length is zero(retval) |
| * stringN is string[argN], length is zero(argN) */ |
| long l; |
| if (parse_int(loc, str, &l) < 0 |
| || check_int(loc, l) < 0) |
| return -1; |
| |
| struct expr_node *length_arg = malloc(sizeof(*length_arg)); |
| if (length_arg == NULL) |
| return -1; |
| |
| 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); |
| return -1; |
| } |
| own_length = 1; |
| |
| } else { |
| eat_spaces(str); |
| if (**str == '[') { |
| (*str)++; |
| eat_spaces(str); |
| |
| length = parse_argnum(loc, str, &own_length, 1); |
| if (length == NULL) |
| return -1; |
| |
| eat_spaces(str); |
| parse_char(loc, str, ']'); |
| |
| } else if (**str == '(') { |
| /* Usage of "string" as lens. */ |
| ++*str; |
| |
| eat_spaces(str); |
| info = parse_type(plib, loc, str, NULL, 0, ownp, NULL); |
| if (info == NULL) |
| return -1; |
| |
| length = NULL; |
| own_length = 0; |
| |
| eat_spaces(str); |
| parse_char(loc, str, ')'); |
| |
| } 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 (info == NULL) { |
| struct arg_type_info *info1 = malloc(sizeof(*info1)); |
| struct arg_type_info *info2 = malloc(sizeof(*info2)); |
| if (info1 == NULL || info2 == NULL) { |
| free(info1); |
| free(info2); |
| fail: |
| if (own_length) { |
| assert(length != NULL); |
| expr_destroy(length); |
| free(length); |
| } |
| return -1; |
| } |
| type_init_array(info2, type_get_simple(ARGTYPE_CHAR), 0, |
| length, own_length); |
| type_init_pointer(info1, info2, 1); |
| |
| info = info1; |
| *ownp = 1; |
| } |
| |
| /* We'll need to set the lens, so unshare. */ |
| if (unshare_type_info(loc, &info, ownp) < 0) |
| /* If unshare_type_info failed, it must have been as a |
| * result of cloning attempt because *OWNP was 0. |
| * Thus we don't need to destroy INFO. */ |
| goto fail; |
| |
| info->lens = &string_lens; |
| info->own_lens = 0; |
| |
| *retp = info; |
| return 0; |
| } |
| |
| static int |
| build_printf_pack(struct locus *loc, struct param **packp, size_t param_num) |
| { |
| if (packp == NULL) { |
| report_error(loc->filename, loc->line_no, |
| "'format' type in unexpected context"); |
| return -1; |
| } |
| if (*packp != NULL) { |
| report_error(loc->filename, loc->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); |
| *packp = NULL; |
| 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(CTYPE_CONV((*str)[len])) |
| && (*str)[len] != '_') { |
| (*str) += len; |
| return 0; |
| } |
| return -1; |
| } |
| |
| /* 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(struct protolib *plib, struct locus *loc, |
| 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(plib, loc, 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(plib, loc, str, retp, ownp) < 0) |
| return -1; |
| |
| return build_printf_pack(loc, extra_param, param_num); |
| |
| } else if (try_parse_kwd(str, "enum") >=0) { |
| |
| return parse_enum(plib, loc, str, retp, ownp); |
| |
| } else { |
| *retp = NULL; |
| return 0; |
| } |
| } |
| |
| /* Syntax: array ( type, N|argN ) */ |
| static int |
| parse_array(struct protolib *plib, struct locus *loc, |
| char **str, struct arg_type_info *info) |
| { |
| eat_spaces(str); |
| if (parse_char(loc, str, '(') < 0) |
| return -1; |
| |
| eat_spaces(str); |
| int own; |
| struct arg_type_info *elt_info |
| = parse_lens(plib, loc, str, NULL, 0, &own, NULL); |
| if (elt_info == NULL) |
| return -1; |
| |
| eat_spaces(str); |
| parse_char(loc, str, ','); |
| |
| eat_spaces(str); |
| int own_length; |
| struct expr_node *length = parse_argnum(loc, str, &own_length, 0); |
| if (length == NULL) { |
| if (own) { |
| type_destroy(elt_info); |
| free(elt_info); |
| } |
| return -1; |
| } |
| |
| type_init_array(info, elt_info, own, length, own_length); |
| |
| eat_spaces(str); |
| parse_char(loc, str, ')'); |
| return 0; |
| } |
| |
| /* Syntax: |
| * enum (keyname[=value],keyname[=value],... ) |
| * enum<type> (keyname[=value],keyname[=value],... ) |
| */ |
| static int |
| parse_enum(struct protolib *plib, struct locus *loc, char **str, |
| struct arg_type_info **retp, int *ownp) |
| { |
| /* Optional type argument. */ |
| eat_spaces(str); |
| if (**str == '[') { |
| parse_char(loc, str, '['); |
| eat_spaces(str); |
| *retp = parse_nonpointer_type(plib, loc, str, NULL, 0, ownp, 0); |
| if (*retp == NULL) |
| return -1; |
| |
| if (!type_is_integral((*retp)->type)) { |
| report_error(loc->filename, loc->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(loc, 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(loc, retp, ownp) < 0) |
| goto fail; |
| |
| eat_spaces(str); |
| if (parse_char(loc, str, '(') < 0) |
| goto fail; |
| |
| struct enum_lens *lens = malloc(sizeof(*lens)); |
| if (lens == NULL) { |
| report_error(loc->filename, loc->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(loc, 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(loc, str, ','); |
| |
| eat_spaces(str); |
| char *key = parse_ident(loc, str); |
| if (key == NULL) { |
| err: |
| free(key); |
| goto fail; |
| } |
| |
| if (**str == '=') { |
| ++*str; |
| eat_spaces(str); |
| if (parse_int(loc, 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_word(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(struct protolib *plib, struct locus *loc, |
| char **str, struct param **extra_param, size_t param_num, |
| int *ownp, int *forwardp) |
| { |
| const char *orig_str = *str; |
| enum arg_type type; |
| if (parse_arg_type(str, &type) < 0) { |
| struct arg_type_info *type; |
| if (parse_alias(plib, loc, str, &type, |
| ownp, extra_param, param_num) < 0) |
| return NULL; |
| else if (type != NULL) |
| return type; |
| |
| *ownp = 0; |
| if ((type = parse_typedef_name(plib, str)) == NULL) |
| report_error(loc->filename, loc->line_no, |
| "unknown type around '%s'", orig_str); |
| return type; |
| } |
| |
| /* 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: |
| case ARGTYPE_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(loc->filename, loc->line_no, |
| "malloc: %s", strerror(errno)); |
| return NULL; |
| } |
| *ownp = 1; |
| |
| if (type == ARGTYPE_ARRAY) { |
| if (parse_array(plib, loc, str, info) < 0) { |
| fail: |
| free(info); |
| return NULL; |
| } |
| } else { |
| assert(type == ARGTYPE_STRUCT); |
| if (parse_struct(plib, loc, str, info, forwardp) < 0) |
| goto fail; |
| } |
| |
| return info; |
| } |
| |
| static struct named_lens { |
| const char *name; |
| struct lens *lens; |
| } lenses[] = { |
| { "hide", &blind_lens }, |
| { "octal", &octal_lens }, |
| { "oct", &octal_lens }, |
| { "bitvec", &bitvect_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(struct protolib *plib, struct locus *loc, char **str, |
| struct param **extra_param, size_t param_num, |
| int *ownp, int *forwardp) |
| { |
| struct arg_type_info *info |
| = parse_nonpointer_type(plib, loc, str, extra_param, |
| param_num, ownp, forwardp); |
| 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(loc->filename, loc->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(struct protolib *plib, struct locus *loc, |
| char **str, struct param **extra_param, |
| size_t param_num, int *ownp, int *forwardp) |
| { |
| 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(loc, str, '(') < 0) { |
| report_error(loc->filename, loc->line_no, |
| "expected type argument after the lens"); |
| return NULL; |
| } |
| } |
| |
| if (has_args) { |
| eat_spaces(str); |
| info = parse_type(plib, loc, str, extra_param, param_num, |
| ownp, forwardp); |
| if (info == NULL) { |
| fail: |
| if (own_lens && lens != NULL) |
| lens_destroy(lens); |
| return NULL; |
| } |
| } |
| |
| if (lens != NULL && has_args) { |
| eat_spaces(str); |
| parse_char(loc, str, ')'); |
| } |
| |
| /* We can't modify shared types. Make a copy if we have a |
| * lens. */ |
| if (lens != NULL && unshare_type_info(loc, &info, ownp) < 0) |
| goto fail; |
| |
| if (lens != NULL) { |
| info->lens = lens; |
| info->own_lens = own_lens; |
| } |
| |
| return info; |
| } |
| |
| static int |
| param_is_void(struct param *param) |
| { |
| return param->flavor == PARAM_FLAVOR_TYPE |
| && param->u.type.type->type == ARGTYPE_VOID; |
| } |
| |
| static struct arg_type_info * |
| get_hidden_int(void) |
| { |
| static struct arg_type_info info, *pinfo = NULL; |
| if (pinfo != NULL) |
| return pinfo; |
| |
| info = *type_get_simple(ARGTYPE_INT); |
| info.lens = &blind_lens; |
| pinfo = &info; |
| |
| return pinfo; |
| } |
| |
| static enum callback_status |
| void_to_hidden_int(struct prototype *proto, struct param *param, void *data) |
| { |
| struct locus *loc = data; |
| if (param_is_void(param)) { |
| report_warning(loc->filename, loc->line_no, |
| "void parameter assumed to be 'hide(int)'"); |
| |
| static struct arg_type_info *type = NULL; |
| if (type == NULL) |
| type = get_hidden_int(); |
| param_destroy(param); |
| param_init_type(param, type, 0); |
| } |
| return CBS_CONT; |
| } |
| |
| static int |
| process_line(struct protolib *plib, struct locus *loc, char *buf) |
| { |
| char *str = buf; |
| char *tmp; |
| |
| debug(3, "Reading line %d of `%s'", loc->line_no, loc->filename); |
| eat_spaces(&str); |
| |
| /* A comment or empty line. */ |
| if (*str == ';' || *str == 0 || *str == '\n' || *str == '#') |
| return 0; |
| |
| if (strncmp(str, "typedef", 7) == 0) { |
| parse_typedef(plib, loc, &str); |
| return 0; |
| } |
| |
| struct prototype fun; |
| prototype_init(&fun); |
| |
| struct param *extra_param = NULL; |
| char *proto_name = NULL; |
| int own; |
| fun.return_info = parse_lens(plib, loc, &str, NULL, 0, &own, NULL); |
| if (fun.return_info == NULL) { |
| err: |
| debug(3, " Skipping line %d", loc->line_no); |
| |
| if (extra_param != NULL) { |
| param_destroy(extra_param); |
| free(extra_param); |
| } |
| |
| prototype_destroy(&fun); |
| free(proto_name); |
| return -1; |
| } |
| fun.own_return_info = own; |
| debug(4, " return_type = %d", fun.return_info->type); |
| |
| eat_spaces(&str); |
| tmp = start_of_arg_sig(str); |
| if (tmp == NULL) { |
| report_error(loc->filename, loc->line_no, "syntax error"); |
| goto err; |
| } |
| *tmp = '\0'; |
| |
| proto_name = strdup(str); |
| if (proto_name == NULL) { |
| oom: |
| report_error(loc->filename, loc->line_no, |
| "%s", strerror(errno)); |
| goto err; |
| } |
| |
| str = tmp + 1; |
| debug(3, " name = %s", proto_name); |
| |
| int have_stop = 0; |
| |
| while (1) { |
| eat_spaces(&str); |
| if (*str == ')') |
| break; |
| |
| if (str[0] == '+') { |
| if (have_stop == 0) { |
| struct param param; |
| param_init_stop(¶m); |
| if (prototype_push_param(&fun, ¶m) < 0) |
| goto oom; |
| have_stop = 1; |
| } |
| str++; |
| } |
| |
| int own; |
| size_t param_num = prototype_num_params(&fun) - have_stop; |
| struct arg_type_info *type |
| = parse_lens(plib, loc, &str, &extra_param, |
| param_num, &own, NULL); |
| if (type == NULL) { |
| report_error(loc->filename, loc->line_no, |
| "unknown argument type"); |
| goto err; |
| } |
| |
| struct param param; |
| param_init_type(¶m, type, own); |
| if (prototype_push_param(&fun, ¶m) < 0) |
| goto oom; |
| |
| 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(loc->filename, loc->line_no, |
| "syntax error around \"%s\"", str); |
| goto err; |
| } |
| } |
| |
| /* We used to allow void parameter as a synonym to an argument |
| * that shouldn't be displayed. But backends really need to |
| * know the exact type that they are dealing with. The proper |
| * way to do this these days is to use the hide lens. |
| * |
| * So if there are any voids in the parameter list, show a |
| * warning and assume that they are ints. If there's a sole |
| * void, assume the function doesn't take any arguments. The |
| * latter is conservative, we can drop the argument |
| * altogether, instead of fetching and then not showing it, |
| * without breaking any observable behavior. */ |
| if (prototype_num_params(&fun) == 1 |
| && param_is_void(prototype_get_nth_param(&fun, 0))) { |
| if (0) |
| /* Don't show this warning. Pre-0.7.0 |
| * ltrace.conf often used this idiom. This |
| * should be postponed until much later, when |
| * extant uses are likely gone. */ |
| report_warning(loc->filename, loc->line_no, |
| "sole void parameter ignored"); |
| prototype_destroy_nth_param(&fun, 0); |
| } else { |
| prototype_each_param(&fun, NULL, void_to_hidden_int, loc); |
| } |
| |
| if (extra_param != NULL) { |
| prototype_push_param(&fun, extra_param); |
| free(extra_param); |
| extra_param = NULL; |
| } |
| |
| if (protolib_add_prototype(plib, proto_name, 1, &fun) < 0) { |
| report_error(loc->filename, loc->line_no, |
| "couldn't add prototype: %s", |
| strerror(errno)); |
| goto err; |
| } |
| |
| return 0; |
| } |
| |
| int |
| read_config_file(FILE *stream, const char *path, struct protolib *plib) |
| { |
| debug(DEBUG_FUNCTION, "Reading config file `%s'...", path); |
| |
| struct locus loc = { path, 0 }; |
| char *line = NULL; |
| size_t len = 0; |
| while (getline(&line, &len, stream) >= 0) { |
| loc.line_no++; |
| process_line(plib, &loc, line); |
| } |
| |
| free(line); |
| return 0; |
| } |