/*
* 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;
}