Replace readdir_r with readdir
[platform/upstream/ltrace.git] / read_config_file.c
index b4b1b56..e9c050d 100644 (file)
+/*
+ * This file is part of ltrace.
+ * Copyright (C) 2011,2012,2013,2014 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"
-
-static int line_no;
-static char *filename;
-static int error_count = 0;
-
-static arg_type_info *parse_type(char **str);
-
-Function *list_of_functions = NULL;
-
-/* Map of strings to type names. These do not need to be in any
- * particular order */
-static struct list_of_pt_t {
-       char *name;
-       enum arg_type pt;
-} list_of_pt[] = {
-       {
-       "void", ARGTYPE_VOID}, {
-       "int", ARGTYPE_INT}, {
-       "uint", ARGTYPE_UINT}, {
-       "long", ARGTYPE_LONG}, {
-       "ulong", ARGTYPE_ULONG}, {
-       "octal", ARGTYPE_OCTAL}, {
-       "char", ARGTYPE_CHAR}, {
-       "short", ARGTYPE_SHORT}, {
-       "ushort", ARGTYPE_USHORT}, {
-       "float", ARGTYPE_FLOAT}, {
-       "double", ARGTYPE_DOUBLE}, {
-       "addr", ARGTYPE_ADDR}, {
-       "file", ARGTYPE_FILE}, {
-       "format", ARGTYPE_FORMAT}, {
-       "string", ARGTYPE_STRING}, {
-       "array", ARGTYPE_ARRAY}, {
-       "struct", ARGTYPE_STRUCT}, {
-       "enum", ARGTYPE_ENUM}, {
-       NULL, ARGTYPE_UNKNOWN}  /* Must finish with NULL */
+#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;
 };
 
-/* Array of prototype objects for each of the types. The order in this
- * array must exactly match the list of enumerated values in
- * common.h */
-static arg_type_info arg_type_prototypes[] = {
-       { ARGTYPE_VOID },
-       { ARGTYPE_INT },
-       { ARGTYPE_UINT },
-       { ARGTYPE_LONG },
-       { ARGTYPE_ULONG },
-       { ARGTYPE_OCTAL },
-       { ARGTYPE_CHAR },
-       { ARGTYPE_SHORT },
-       { ARGTYPE_USHORT },
-       { ARGTYPE_FLOAT },
-       { ARGTYPE_DOUBLE },
-       { ARGTYPE_ADDR },
-       { ARGTYPE_FILE },
-       { ARGTYPE_FORMAT },
-       { ARGTYPE_STRING },
-       { ARGTYPE_STRING_N },
-       { ARGTYPE_ARRAY },
-       { ARGTYPE_ENUM },
-       { ARGTYPE_STRUCT },
-       { ARGTYPE_POINTER },
-       { ARGTYPE_UNKNOWN }
-};
+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;
 
-arg_type_info *
-lookup_prototype(enum arg_type at) {
-       if (at >= 0 && at <= ARGTYPE_COUNT)
-               return &arg_type_prototypes[at];
-       else
-               return &arg_type_prototypes[ARGTYPE_COUNT]; /* UNKNOWN */
-}
-
-static arg_type_info *
-str2type(char **str) {
-       struct list_of_pt_t *tmp = &list_of_pt[0];
-
-       while (tmp->name) {
-               if (!strncmp(*str, tmp->name, strlen(tmp->name))
-                               && index(" ,()#*;012345[", *(*str + strlen(tmp->name)))) {
-                       *str += strlen(tmp->name);
-                       return lookup_prototype(tmp->pt);
-               }
-               tmp++;
-       }
-       return lookup_prototype(ARGTYPE_UNKNOWN);
+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
@@ -102,529 +142,1027 @@ eat_spaces(char **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) {
+parse_ident(struct locus *loc, char **str)
+{
        char *ident = *str;
 
-       if (!isalnum(**str) && **str != '_') {
-               output_line(0, "Syntax error in `%s', line %d: Bad identifier",
-                               filename, line_no);
-               error_count++;
+       if (!isalpha(CTYPE_CONV(**str)) && **str != '_') {
+               report_error(loc->filename, loc->line_no, "bad identifier");
                return NULL;
        }
 
-       while (**str && (isalnum(**str) || **str == '_')) {
+       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;
+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;
+       }
 
-       if (!strlen(str))
-               return NULL;
+       *str = end;
+       if (ret != NULL)
+               *ret = n;
+       return 0;
+}
 
-       pos = &str[strlen(str)];
-       do {
-               pos--;
-               if (pos < str)
-                       return NULL;
-               while ((pos > str) && (*pos != ')') && (*pos != '('))
-                       pos--;
+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;
+}
 
-               if (*pos == ')')
-                       stacked++;
-               else if (*pos == '(')
-                       stacked--;
-               else
-                       return NULL;
+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;
+}
 
-       } while (stacked > 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;
+       }
 
-       return (stacked == 0) ? pos : NULL;
+       ++*str;
+       return 0;
 }
 
-static int
-parse_int(char **str) {
-       char *end;
-       long n = strtol(*str, &end, 0);
-       if (end == *str) {
-               output_line(0, "Syntax error in `%s', line %d: Bad number (%s)",
-                               filename, line_no, *str);
-               error_count++;
-               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();
        }
+}
 
-       *str = end;
-       return n;
+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 (arg0 = retval)
+ *  argN   : The value of argument #N, counting from 1
  *  eltN   : The value of element #N of the containing structure
  *  retval : The return value
- *  0      : Error
- *  N      : The numeric value N, if N > 0
- *
- * Output:
- * > 0   actual numeric value
- * = 0   return value
- * < 0   (arg -n), counting from one
+ *  N      : The numeric value N
  */
-static int
-parse_argnum(char **str) {
-       int multiplier = 1;
-       int n = 0;
-
-       if (strncmp(*str, "arg", 3) == 0) {
-               (*str) += 3;
-               multiplier = -1;
-       } else if (strncmp(*str, "elt", 3) == 0) {
-               (*str) += 3;
-               multiplier = -1;
-       } else if (strncmp(*str, "retval", 6) == 0) {
-               (*str) += 6;
-               return 0;
-       }
+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;
 
-       n = parse_int(str);
+       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;
 
-       return n * multiplier;
-}
+               expr_init_const_word(expr, l, type_get_simple(ARGTYPE_LONG), 0);
 
-struct typedef_node_t {
-       char *name;
-       arg_type_info *info;
-       struct typedef_node_t *next;
-} *typedefs = NULL;
+               if (zero && wrap_in_zero(&expr) < 0)
+                       goto fail;
 
-static 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;
+               *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);
 
-       for (node = typedefs; node != NULL; node = node->next) {
-               if (strncmp(*str, node->name, end - *str) == 0) {
-                       (*str) += strlen(node->name);
-                       return node->info;
+               } 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 void
-parse_typedef(char **str) {
-       char *name;
-       arg_type_info *info;
-       struct typedef_node_t *binding;
+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);
-
-       // Grab out the name of the type
-       name = parse_ident(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 (**str != '=') {
-               output_line(0,
-                               "Syntax error in `%s', line %d: expected '=', got '%c'",
-                               filename, line_no, **str);
-               error_count++;
-               return;
-       }
-       (*str)++;
+       if (parse_char(loc, str, '=') < 0)
+               goto err;
        eat_spaces(str);
 
-       // Parse the type
-       info = parse_type(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;
+       }
 
-       // Insert onto beginning of linked list
-       binding = malloc(sizeof(*binding));
-       binding->name = name;
-       binding->info = info;
-       binding->next = typedefs;
-       typedefs = binding;
+       /* 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;
 }
 
-static size_t
-arg_sizeof(arg_type_info * arg) {
-       if (arg->type == ARGTYPE_CHAR) {
-               return sizeof(char);
-       } else if (arg->type == ARGTYPE_SHORT || arg->type == ARGTYPE_USHORT) {
-               return sizeof(short);
-       } else if (arg->type == ARGTYPE_FLOAT) {
-               return sizeof(float);
-       } else if (arg->type == ARGTYPE_DOUBLE) {
-               return sizeof(double);
-       } else if (arg->type == ARGTYPE_ENUM) {
-               return sizeof(int);
-       } else if (arg->type == ARGTYPE_STRUCT) {
-               return arg->u.struct_info.size;
-       } else if (arg->type == ARGTYPE_POINTER) {
-               return sizeof(void*);
-       } else if (arg->type == ARGTYPE_ARRAY) {
-               if (arg->u.array_info.len_spec > 0)
-                       return arg->u.array_info.len_spec * arg->u.array_info.elt_size;
-               else
-                       return sizeof(void *);
-       } else {
-               return sizeof(int);
+/* 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;
        }
-}
 
-#undef alignof
-#define alignof(field,st) ((size_t) ((char*) &st.field - (char*) &st))
-static size_t
-arg_align(arg_type_info * arg) {
-       struct { char c; char C; } cC;
-       struct { char c; short s; } cs;
-       struct { char c; int i; } ci;
-       struct { char c; long l; } cl;
-       struct { char c; void* p; } cp;
-       struct { char c; float f; } cf;
-       struct { char c; double d; } cd;
-
-       static size_t char_alignment = alignof(C, cC);
-       static size_t short_alignment = alignof(s, cs);
-       static size_t int_alignment = alignof(i, ci);
-       static size_t long_alignment = alignof(l, cl);
-       static size_t ptr_alignment = alignof(p, cp);
-       static size_t float_alignment = alignof(f, cf);
-       static size_t double_alignment = alignof(d, cd);
-
-       switch (arg->type) {
-               case ARGTYPE_LONG:
-               case ARGTYPE_ULONG:
-                       return long_alignment;
-               case ARGTYPE_CHAR:
-                       return char_alignment;
-               case ARGTYPE_SHORT:
-               case ARGTYPE_USHORT:
-                       return short_alignment;
-               case ARGTYPE_FLOAT:
-                       return float_alignment;
-               case ARGTYPE_DOUBLE:
-                       return double_alignment;
-               case ARGTYPE_ADDR:
-               case ARGTYPE_FILE:
-               case ARGTYPE_FORMAT:
-               case ARGTYPE_STRING:
-               case ARGTYPE_STRING_N:
-               case ARGTYPE_POINTER:
-                       return ptr_alignment;
-
-               case ARGTYPE_ARRAY:
-                       return arg_align(&arg->u.array_info.elt_type[0]);
-
-               case ARGTYPE_STRUCT:
-                       return arg_align(arg->u.struct_info.fields[0]);
-
-               default:
-                       return int_alignment;
+       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;
+               }
        }
 }
 
-static size_t
-align_skip(size_t alignment, size_t offset) {
-       if (offset % alignment)
-               return alignment - (offset % alignment);
-       else
+/* 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;
 }
 
-/* I'm sure this isn't completely correct, but just try to get most of
- * them right for now. */
-static void
-align_struct(arg_type_info* info) {
-       size_t offset;
-       int i;
+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;
 
-       if (info->u.struct_info.size != 0)
-               return;                 // Already done
+       } else {
+               eat_spaces(str);
+               if (**str == '[') {
+                       (*str)++;
+                       eat_spaces(str);
 
-       // Compute internal padding due to alignment constraints for
-       // various types.
-       offset = 0;
-       for (i = 0; info->u.struct_info.fields[i] != NULL; i++) {
-               arg_type_info *field = info->u.struct_info.fields[i];
-               offset += align_skip(arg_align(field), offset);
-               info->u.struct_info.offset[i] = offset;
-               offset += arg_sizeof(field);
+                       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;
+               }
        }
 
-       info->u.struct_info.size = offset;
+       /* 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 arg_type_info *
-parse_nonpointer_type(char **str) {
-       arg_type_info *simple;
-       arg_type_info *info;
+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;
 
-       if (strncmp(*str, "typedef", 7) == 0) {
-               parse_typedef(str);
-               return lookup_prototype(ARGTYPE_UNKNOWN);
+       struct expr_node *node = malloc(sizeof(*node));
+       if (node == NULL) {
+               free(*packp);
+               *packp = NULL;
+               return -1;
        }
 
-       simple = str2type(str);
-       if (simple->type == ARGTYPE_UNKNOWN) {
-               info = lookup_typedef(str);
-               if (info)
-                       return info;
-               else
-                       return simple;          // UNKNOWN
+       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;
+}
 
-       info = malloc(sizeof(*info));
-       info->type = simple->type;
+/* 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);
 
-       /* Code to parse parameterized types will go into the following
-          switch statement. */
+       } 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;
 
-       switch (info->type) {
+               return build_printf_pack(loc, extra_param, param_num);
 
-       /* Syntax: array ( type, N|argN ) */
-       case ARGTYPE_ARRAY:
-               (*str)++;               // Get past open paren
-               eat_spaces(str);
-               if ((info->u.array_info.elt_type = parse_type(str)) == NULL)
-                       return NULL;
-               info->u.array_info.elt_size =
-                       arg_sizeof(info->u.array_info.elt_type);
-               (*str)++;               // Get past comma
+       } 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);
-               info->u.array_info.len_spec = parse_argnum(str);
-               (*str)++;               // Get past close paren
-               return info;
-
-       /* Syntax: enum ( keyname=value,keyname=value,... ) */
-       case ARGTYPE_ENUM:{
-               struct enum_opt {
-                       char *key;
-                       int value;
-                       struct enum_opt *next;
-               };
-               struct enum_opt *list = NULL;
-               struct enum_opt *p;
-               int entries = 0;
-               int ii;
+               *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);
-               (*str)++;               // Get past open paren
+               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;
+               }
 
-               while (**str && **str != ')') {
-                       p = (struct enum_opt *) malloc(sizeof(*p));
-                       eat_spaces(str);
-                       p->key = parse_ident(str);
-                       if (error_count) {
-                               free(p);
-                               return NULL;
-                       }
-                       eat_spaces(str);
-                       if (**str != '=') {
-                               free(p->key);
-                               free(p);
-                               output_line(0,
-                                               "Syntax error in `%s', line %d: expected '=', got '%c'",
-                                               filename, line_no, **str);
-                               error_count++;
-                               return NULL;
-                       }
-                       ++(*str);
-                       eat_spaces(str);
-                       p->value = parse_int(str);
-                       p->next = list;
-                       list = p;
-                       ++entries;
+               /* 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, ',');
 
-                       // Skip comma
-                       eat_spaces(str);
-                       if (**str == ',') {
-                               (*str)++;
-                               eat_spaces(str);
-                       }
+               eat_spaces(str);
+               char *key = parse_ident(loc, str);
+               if (key == NULL) {
+               err:
+                       free(key);
+                       goto fail;
                }
 
-               info->u.enum_info.entries = entries;
-               info->u.enum_info.keys =
-                       (char **) malloc(entries * sizeof(char *));
-               info->u.enum_info.values =
-                       (int *) malloc(entries * sizeof(int));
-               for (ii = 0, p = NULL; list; ++ii, list = list->next) {
-                       if (p)
-                               free(p);
-                       info->u.enum_info.keys[ii] = list->key;
-                       info->u.enum_info.values[ii] = list->value;
-                       p = list;
+               if (**str == '=') {
+                       ++*str;
+                       eat_spaces(str);
+                       if (parse_int(loc, str, &last_val) < 0)
+                               goto err;
                }
-               if (p)
-                       free(p);
 
-               return info;
+               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++;
        }
 
-       case ARGTYPE_STRING:
-               if (!isdigit(**str) && **str != '[') {
-                       /* Oops, was just a simple string after all */
+       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 simple;
+                       return NULL;
                }
+       } else {
+               assert(type == ARGTYPE_STRUCT);
+               if (parse_struct(plib, loc, str, info, forwardp) < 0)
+                       goto fail;
+       }
 
-               info->type = ARGTYPE_STRING_N;
+       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 },
+};
 
-               /* Backwards compatibility for string0, string1, ... */
-               if (isdigit(**str)) {
-                       info->u.string_n_info.size_spec = -parse_int(str);
-                       return info;
+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;
                }
 
-               (*str)++;               // Skip past opening [
-               eat_spaces(str);
-               info->u.string_n_info.size_spec = parse_argnum(str);
+       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);
-               (*str)++;               // Skip past closing ]
-               return info;
-
-       // Syntax: struct ( type,type,type,... )
-       case ARGTYPE_STRUCT:{
-               int field_num = 0;
-               (*str)++;               // Get past open paren
-               info->u.struct_info.fields =
-                       malloc((MAX_ARGS + 1) * sizeof(void *));
-               info->u.struct_info.offset =
-                       malloc((MAX_ARGS + 1) * sizeof(size_t));
-               info->u.struct_info.size = 0;
-               eat_spaces(str); // Empty arg list with whitespace inside
-               while (**str && **str != ')') {
-                       if (field_num == MAX_ARGS) {
-                               output_line(0,
-                                               "Error in `%s', line %d: Too many structure elements",
-                                               filename, line_no);
-                               error_count++;
+               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;
                        }
-                       eat_spaces(str);
-                       if (field_num != 0) {
-                               (*str)++;       // Get past comma
-                               eat_spaces(str);
-                       }
-                       if ((info->u.struct_info.fields[field_num++] =
-                                               parse_type(str)) == NULL)
-                               return NULL;
+                       type_init_pointer(outer, info, *ownp);
+                       *ownp = 1;
+                       (*str)++;
+                       info = outer;
+               } else
+                       break;
+       }
+       return info;
+}
 
-                       // Must trim trailing spaces so the check for
-                       // the closing paren is simple
-                       eat_spaces(str);
+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;
                }
-               (*str)++;               // Get past closing paren
-               info->u.struct_info.fields[field_num] = NULL;
-               align_struct(info);
-               return info;
        }
 
-       default:
-               if (info->type == ARGTYPE_UNKNOWN) {
-                       output_line(0, "Syntax error in `%s', line %d: "
-                                       "Unknown type encountered",
-                                       filename, line_no);
-                       free(info);
-                       error_count++;
+       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;
-               } else {
-                       return info;
                }
        }
-}
 
-static arg_type_info *
-parse_type(char **str) {
-       arg_type_info *info = parse_nonpointer_type(str);
-       while (**str == '*') {
-               arg_type_info *outer = malloc(sizeof(*info));
-               outer->type = ARGTYPE_POINTER;
-               outer->u.ptr_info.info = info;
-               (*str)++;
-               info = outer;
+       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 Function *
-process_line(char *buf) {
-       Function fun;
-       Function *fun_p;
+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;
-       int i;
-       int float_num = 0;
 
-       line_no++;
-       debug(3, "Reading line %d of `%s'", line_no, filename);
+       debug(3, "Reading line %d of `%s'", loc->line_no, loc->filename);
        eat_spaces(&str);
-       fun.return_info = parse_type(&str);
-       if (fun.return_info == NULL)
-               return NULL;
-       if (fun.return_info->type == ARGTYPE_UNKNOWN) {
-               debug(3, " Skipping line %d", line_no);
-               return NULL;
+
+       /* 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) {
-               output_line(0, "Syntax error in `%s', line %d", filename,
-                               line_no);
-               error_count++;
-               return NULL;
-       }
-       *tmp = '\0';
-       fun.name = strdup(str);
-       str = tmp + 1;
-       debug(3, " name = %s", fun.name);
-       fun.params_right = 0;
-       for (i = 0; i < MAX_ARGS; i++) {
+       proto_name = parse_ident(loc, &str);
+       if (proto_name == NULL)
+               goto err;
+
+       eat_spaces(&str);
+       if (parse_char(loc, &str, '(') < 0)
+               goto err;
+       debug(3, " name = %s", proto_name);
+
+       int have_stop = 0;
+
+       while (1) {
                eat_spaces(&str);
-               if (*str == ')') {
+               if (*str == ')')
                        break;
-               }
+
                if (str[0] == '+') {
-                       fun.params_right++;
+                       if (have_stop == 0) {
+                               struct param param;
+                               param_init_stop(&param);
+                               if (prototype_push_param(&fun, &param) < 0) {
+                               oom:
+                                       report_error(loc->filename,
+                                                    loc->line_no,
+                                                    "%s", strerror(errno));
+                                       goto err;
+                               }
+                               have_stop = 1;
+                       }
                        str++;
-               } else if (fun.params_right) {
-                       fun.params_right++;
                }
-               fun.arg_info[i] = parse_type(&str);
-               if (fun.arg_info[i] == NULL) {
-                       output_line(0, "Syntax error in `%s', line %d"
-                                       ": unknown argument type",
-                                       filename, line_no);
-                       error_count++;
-                       return NULL;
+
+               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;
                }
-               if (fun.arg_info[i]->type == ARGTYPE_FLOAT)
-                       fun.arg_info[i]->u.float_info.float_index = float_num++;
-               else if (fun.arg_info[i]->type == ARGTYPE_DOUBLE)
-                       fun.arg_info[i]->u.double_info.float_index = float_num++;
+
+               struct param param;
+               param_init_type(&param, type, own);
+               if (prototype_push_param(&fun, &param) < 0)
+                       goto oom;
+
                eat_spaces(&str);
                if (*str == ',') {
                        str++;
@@ -634,47 +1172,66 @@ process_line(char *buf) {
                } else {
                        if (str[strlen(str) - 1] == '\n')
                                str[strlen(str) - 1] = '\0';
-                       output_line(0, "Syntax error in `%s', line %d at ...\"%s\"",
-                                       filename, line_no, str);
-                       error_count++;
-                       return NULL;
+                       report_error(loc->filename, loc->line_no,
+                                    "syntax error around \"%s\"", str);
+                       goto err;
                }
        }
-       fun.num_params = i;
-       fun_p = malloc(sizeof(Function));
-       if (!fun_p) {
-               perror("ltrace: malloc");
-               exit(1);
-       }
-       memcpy(fun_p, &fun, sizeof(Function));
-       return fun_p;
-}
-
-void
-read_config_file(char *file) {
-       FILE *stream;
-       char buf[1024];
 
-       filename = file;
-       stream = fopen(filename, "r");
-       if (!stream) {
-               return;
+       /* 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);
        }
 
-       debug(1, "Reading config file `%s'...", filename);
+       if (extra_param != NULL) {
+               prototype_push_param(&fun, extra_param);
+               free(extra_param);
+               extra_param = NULL;
+       }
 
-       line_no = 0;
-       while (fgets(buf, 1024, stream)) {
-               Function *tmp;
+       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;
+       }
 
-               error_count = 0;
-               tmp = process_line(buf);
+       return 0;
+}
 
-               if (tmp) {
-                       debug(2, "New function: `%s'", tmp->name);
-                       tmp->next = list_of_functions;
-                       list_of_functions = tmp;
-               }
+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);
        }
-       fclose(stream);
+
+       free(line);
+       return 0;
 }