From 6b828f69519a50e6e2961b62ea552bf89d287199 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Thu, 19 Dec 2019 16:15:09 -0500 Subject: [PATCH] analyzer: add testcases for PR 93355 (intl/localealias.c leak) PR analyzer/93355 reports a missing diagnostic about a FILE leak in intl/localealias.c. This appears to be due to a issue in the feasibility-checking code, though there is also a state explosion. This patch adds test cases that I've been using when investigating this, two of them currently requiring -fno-analyzer-feasibility, and one currently requiring -Wno-analyzer-too-complex. gcc/testsuite/ChangeLog: PR analyzer/93355 * gcc.dg/analyzer/pr93355-localealias-feasibility.c: New test. * gcc.dg/analyzer/pr93355-localealias-simplified.c: New test. * gcc.dg/analyzer/pr93355-localealias.c: New test. --- .../pr93355-localealias-feasibility.c | 79 ++++ .../analyzer/pr93355-localealias-simplified.c | 45 ++ .../gcc.dg/analyzer/pr93355-localealias.c | 390 ++++++++++++++++++ 3 files changed, 514 insertions(+) create mode 100644 gcc/testsuite/gcc.dg/analyzer/pr93355-localealias-feasibility.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/pr93355-localealias-simplified.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/pr93355-localealias.c diff --git a/gcc/testsuite/gcc.dg/analyzer/pr93355-localealias-feasibility.c b/gcc/testsuite/gcc.dg/analyzer/pr93355-localealias-feasibility.c new file mode 100644 index 00000000000..0d470d6602b --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/pr93355-localealias-feasibility.c @@ -0,0 +1,79 @@ +/* Simplified version of test to ensure we issue a FILE * leak diagnostic, + reproducing a feasibility issue. + Adapted from intl/localealias.c, with all #includes removed. */ + +/* { dg-do "compile" } */ +/* { dg-additional-options "-fno-analyzer-feasibility" } */ +/* TODO: remove the need for this option. */ + +/* Handle aliases for locale names. + Copyright (C) 1995-1999, 2000-2001, 2003 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published + by the Free Software Foundation; either version 2, 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, + USA. */ + +/* Minimal version of system headers. */ + +typedef __SIZE_TYPE__ size_t; +#define NULL ((void *)0) + +typedef struct _IO_FILE FILE; +extern FILE *fopen (const char *__restrict __filename, + const char *__restrict __modes); +extern int fclose (FILE *__stream); + +extern int isspace (int) __attribute__((__nothrow__, __leaf__)); + +/* Cleaned-up body of localealias.c follows. */ + +size_t +read_alias_file (const char *fname, int fname_len) +{ + FILE *fp; + size_t added; + char buf[400]; + char *alias; + char *value; + char *cp; + + fp = fopen (fname, "r"); /* { dg-message "opened here" } */ + if (fp == NULL) + return 0; + + cp = buf; + + /* Ignore leading white space. */ + while (isspace ((unsigned char)cp[0])) + ++cp; + + if (cp[0] != '\0' && cp[0] != '#') + { + alias = cp++; + while (cp[0] != '\0' && !isspace ((unsigned char)cp[0])) + ++cp; + if (cp[0] != '\0') + *cp++ = '\0'; + + while (isspace ((unsigned char)cp[0])) + ++cp; + + if (cp[0] != '\0') + return 42; /* { dg-warning "leak of FILE 'fp'" } */ + } + + fclose(fp); + + return 0; +} diff --git a/gcc/testsuite/gcc.dg/analyzer/pr93355-localealias-simplified.c b/gcc/testsuite/gcc.dg/analyzer/pr93355-localealias-simplified.c new file mode 100644 index 00000000000..6f65add346b --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/pr93355-localealias-simplified.c @@ -0,0 +1,45 @@ +/* Simplified version of test for ensuring we issue a FILE * leak diagnostic, + made trivial. + Adapted from intl/localealias.c, with all #includes removed. */ + +/* { dg-do "compile" } */ + +/* Handle aliases for locale names. + Copyright (C) 1995-1999, 2000-2001, 2003 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published + by the Free Software Foundation; either version 2, 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, + USA. */ + +/* Minimal version of system headers. */ +#define NULL ((void *) 0) +typedef struct _IO_FILE FILE; +extern FILE *fopen(const char *__restrict __filename, + const char *__restrict __modes); +extern int fclose(FILE *__stream); + +void +read_alias_file (int flag) +{ + FILE *fp; + + fp = fopen ("name", "r"); /* { dg-message "opened here" } */ + if (fp == NULL) + return; + + if (flag) + return; /* { dg-warning "leak of FILE 'fp'" } */ + + fclose (fp); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/pr93355-localealias.c b/gcc/testsuite/gcc.dg/analyzer/pr93355-localealias.c new file mode 100644 index 00000000000..a5cb0d56e70 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/pr93355-localealias.c @@ -0,0 +1,390 @@ +/* Integration test to ensure we issue a FILE * leak diagnostic for + this particular non-trivial case. + Adapted from intl/localealias.c, with all #includes removed. */ + +/* { dg-do "compile" } */ +/* { dg-additional-options "-Wno-analyzer-too-complex -fno-analyzer-feasibility" } */ +/* TODO: remove the need for these options. */ + +/* Handle aliases for locale names. + Copyright (C) 1995-1999, 2000-2001, 2003 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published + by the Free Software Foundation; either version 2, 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, + USA. */ + +/* Minimal version of system headers. */ + +typedef __SIZE_TYPE__ size_t; +#define NULL ((void *) 0) + +#define PATH_SEPARATOR ':' +typedef struct _IO_FILE FILE; +extern FILE *fopen(const char *__restrict __filename, + const char *__restrict __modes); +extern int feof_unlocked(FILE *__stream) __attribute__((__nothrow__, __leaf__)); +extern char *fgets_unlocked(char *__restrict __s, int __n, + FILE *__restrict __stream); +extern int fclose(FILE *__stream); + +#define alloca __builtin_alloca + +extern char *strchr(const char *__s, int __c) + __attribute__((__nothrow__, __leaf__)) __attribute__((__pure__)) + __attribute__((__nonnull__(1))); +extern void *memcpy(void *__restrict __dest, const void *__restrict __src, + size_t __n) __attribute__((__nothrow__, __leaf__)) + __attribute__((__nonnull__(1, 2))); +extern void *mempcpy(void *__restrict __dest, const void *__restrict __src, + size_t __n) __attribute__((__nothrow__, __leaf__)) + __attribute__((__nonnull__(1, 2))); +#define HAVE_MEMPCPY 1 +extern size_t strlen(const char *__s) __attribute__((__nothrow__, __leaf__)) + __attribute__((__pure__)) __attribute__((__nonnull__(1))); + +extern int strcasecmp(const char *__s1, const char *__s2) + __attribute__((__nothrow__, __leaf__)) __attribute__((__pure__)) + __attribute__((__nonnull__(1, 2))); + +extern int isspace(int) __attribute__((__nothrow__, __leaf__)); + +extern void *realloc(void *__ptr, size_t __size) + __attribute__((__nothrow__, __leaf__)) + __attribute__((__warn_unused_result__)); + +typedef int (*__compar_fn_t)(const void *, const void *); +extern void *bsearch(const void *__key, const void *__base, size_t __nmemb, + size_t __size, __compar_fn_t __compar) + __attribute__((__nonnull__(1, 2, 5))); + +extern __inline __attribute__((__gnu_inline__)) void * +bsearch(const void *__key, const void *__base, size_t __nmemb, size_t __size, + __compar_fn_t __compar) { + size_t __l, __u, __idx; + const void *__p; + int __comparison; + + __l = 0; + __u = __nmemb; + while (__l < __u) { + __idx = (__l + __u) / 2; + __p = (void *)(((const char *)__base) + (__idx * __size)); + __comparison = (*__compar)(__key, __p); + if (__comparison < 0) + __u = __idx; + else if (__comparison > 0) + __l = __idx + 1; + else + return (void *)__p; + } + + return ((void *)0); +} + +extern void qsort(void *__base, size_t __nmemb, size_t __size, + __compar_fn_t __compar) __attribute__((__nonnull__(1, 4))); + +/* Minimal version of intl headers. */ + +#define PARAMS(args) args + +#define relocate libintl_relocate +extern const char *libintl_relocate(const char *pathname); + +#define LOCALE_ALIAS_PATH "value for LOCALE_ALIAS_PATH" + +/* Cleaned-up body of localealias.c follows. */ + +#ifndef internal_function +# define internal_function +#endif + +/* Some optimizations for glibc. */ +# define FEOF(fp) feof_unlocked (fp) +# define FGETS(buf, n, fp) fgets_unlocked (buf, n, fp) + +/* For those losing systems which don't have `alloca' we have to add + some additional code emulating it. */ +# define freea(p) /* nothing */ + +struct alias_map +{ + const char *alias; + const char *value; +}; + +# define libc_freeres_ptr(decl) decl + +libc_freeres_ptr (static char *string_space); +static size_t string_space_act; +static size_t string_space_max; +libc_freeres_ptr (static struct alias_map *map); +static size_t nmap; +static size_t maxmap; + + +/* Prototypes for local functions. */ +static size_t read_alias_file PARAMS ((const char *fname, int fname_len)) + internal_function; +static int extend_alias_table PARAMS ((void)); +static int alias_compare PARAMS ((const struct alias_map *map1, + const struct alias_map *map2)); + + +const char * +_nl_expand_alias (name) + const char *name; +{ + static const char *locale_alias_path; + struct alias_map *retval; + const char *result = NULL; + size_t added; + +#ifdef _LIBC + __libc_lock_lock (lock); +#endif + + if (locale_alias_path == NULL) + locale_alias_path = LOCALE_ALIAS_PATH; + + do + { + struct alias_map item; + + item.alias = name; + + if (nmap > 0) + retval = (struct alias_map *) bsearch (&item, map, nmap, + sizeof (struct alias_map), + (int (*) PARAMS ((const void *, + const void *)) + ) alias_compare); + else + retval = NULL; + + /* We really found an alias. Return the value. */ + if (retval != NULL) + { + result = retval->value; + break; + } + + /* Perhaps we can find another alias file. */ + added = 0; + while (added == 0 && locale_alias_path[0] != '\0') + { + const char *start; + + while (locale_alias_path[0] == PATH_SEPARATOR) + ++locale_alias_path; + start = locale_alias_path; + + while (locale_alias_path[0] != '\0' + && locale_alias_path[0] != PATH_SEPARATOR) + ++locale_alias_path; + + if (start < locale_alias_path) + added = read_alias_file (start, locale_alias_path - start); + } + } + while (added != 0); + +#ifdef _LIBC + __libc_lock_unlock (lock); +#endif + + return result; +} + + +static size_t +internal_function +read_alias_file (fname, fname_len) + const char *fname; + int fname_len; +{ + FILE *fp; + char *full_fname; + size_t added; + static const char aliasfile[] = "/locale.alias"; + + full_fname = (char *) alloca (fname_len + sizeof aliasfile); +#ifdef HAVE_MEMPCPY + mempcpy (mempcpy (full_fname, fname, fname_len), + aliasfile, sizeof aliasfile); +#else + memcpy (full_fname, fname, fname_len); + memcpy (&full_fname[fname_len], aliasfile, sizeof aliasfile); +#endif + + fp = fopen (relocate (full_fname), "r"); /* { dg-message "opened here" } */ + freea (full_fname); + if (fp == NULL) + return 0; + +#ifdef HAVE___FSETLOCKING + /* No threads present. */ + __fsetlocking (fp, FSETLOCKING_BYCALLER); +#endif + + added = 0; + while (!FEOF (fp)) + { + /* It is a reasonable approach to use a fix buffer here because + a) we are only interested in the first two fields + b) these fields must be usable as file names and so must not + be that long + We avoid a multi-kilobyte buffer here since this would use up + stack space which we might not have if the program ran out of + memory. */ + char buf[400]; + char *alias; + char *value; + char *cp; + + if (FGETS (buf, sizeof buf, fp) == NULL) + /* EOF reached. */ + break; + + cp = buf; + /* Ignore leading white space. */ + while (isspace ((unsigned char) cp[0])) + ++cp; + + /* A leading '#' signals a comment line. */ + if (cp[0] != '\0' && cp[0] != '#') + { + alias = cp++; + while (cp[0] != '\0' && !isspace ((unsigned char) cp[0])) + ++cp; + /* Terminate alias name. */ + if (cp[0] != '\0') + *cp++ = '\0'; + + /* Now look for the beginning of the value. */ + while (isspace ((unsigned char) cp[0])) + ++cp; + + if (cp[0] != '\0') + { + size_t alias_len; + size_t value_len; + + value = cp++; + while (cp[0] != '\0' && !isspace ((unsigned char) cp[0])) + ++cp; + /* Terminate value. */ + if (cp[0] == '\n') + { + /* This has to be done to make the following test + for the end of line possible. We are looking for + the terminating '\n' which do not overwrite here. */ + *cp++ = '\0'; + *cp = '\n'; + } + else if (cp[0] != '\0') + *cp++ = '\0'; + + if (nmap >= maxmap) + if (__builtin_expect (extend_alias_table (), 0)) + return added; /* { dg-warning "leak of FILE 'fp'" } */ + + alias_len = strlen (alias) + 1; + value_len = strlen (value) + 1; + + if (string_space_act + alias_len + value_len > string_space_max) + { + /* Increase size of memory pool. */ + size_t new_size = (string_space_max + + (alias_len + value_len > 1024 + ? alias_len + value_len : 1024)); + char *new_pool = (char *) realloc (string_space, new_size); + if (new_pool == NULL) + return added; + + if (__builtin_expect (string_space != new_pool, 0)) + { + size_t i; + + for (i = 0; i < nmap; i++) + { + map[i].alias += new_pool - string_space; + map[i].value += new_pool - string_space; + } + } + + string_space = new_pool; + string_space_max = new_size; + } + + map[nmap].alias = memcpy (&string_space[string_space_act], + alias, alias_len); + string_space_act += alias_len; + + map[nmap].value = memcpy (&string_space[string_space_act], + value, value_len); + string_space_act += value_len; + + ++nmap; + ++added; + } + } + + /* Possibly not the whole line fits into the buffer. Ignore + the rest of the line. */ + while (strchr (buf, '\n') == NULL) + if (FGETS (buf, sizeof buf, fp) == NULL) + /* Make sure the inner loop will be left. The outer loop + will exit at the `feof' test. */ + break; + } + + /* Should we test for ferror()? I think we have to silently ignore + errors. --drepper */ + fclose (fp); + + if (added > 0) + qsort (map, nmap, sizeof (struct alias_map), + (int (*) PARAMS ((const void *, const void *))) alias_compare); + + return added; +} + + +static int +extend_alias_table () +{ + size_t new_size; + struct alias_map *new_map; + + new_size = maxmap == 0 ? 100 : 2 * maxmap; + new_map = (struct alias_map *) realloc (map, (new_size + * sizeof (struct alias_map))); + if (new_map == NULL) + /* Simply don't extend: we don't have any more core. */ + return -1; + + map = new_map; + maxmap = new_size; + return 0; +} + + +static int +alias_compare (map1, map2) + const struct alias_map *map1; + const struct alias_map *map2; +{ + return strcasecmp (map1->alias, map2->alias); +} -- 2.30.2