1274 lines
28 KiB
C
1274 lines
28 KiB
C
/*
|
|
* 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;
|
|
}
|