]> git.draconx.ca Git - cdecl99.git/commitdiff
Avoid gnulib getline module.
authorNick Bowler <nbowler@draconx.ca>
Fri, 5 Jan 2024 05:17:24 +0000 (00:17 -0500)
committerNick Bowler <nbowler@draconx.ca>
Fri, 5 Jan 2024 07:13:01 +0000 (02:13 -0500)
Outside of the test suite, we only use getline in one location.  Instead
of pulling in gnulib's replacement implementation and its dependencies,
adjust the do_getline funciton to directly incorporate a pure standard
C fallback.

Since there is also some getline use in the test suite, rearrange the
code a bit so that we can also include do_getline in the tests.

The gnulib configure tests for getline seem to be complete overkill.
According to the gnulib documentation, the portability problems which
gnulib solves are:

 (a) The function is missing on some platforms.
 (b) On FreeBSD 8, getline crashes if passed a null pointer and a
     nonzero size.
 (c) On AIX 7.1, there is no declaration in <stdio.h>.
 (d) Some unspecified system has an unrelated getline function
     which may erroneously satisfy link tests.

It is unclear to me that gnulib actually solves (c) in a reasonable way.
On AIX 7.2, getline is properly declared in <stdio.h> but gnulib rejects
it anyway and substitutes its own reimplementation as if it didn't exist
at all.  Presumably the exact same thing happens on AIX 7.1.

So I don't see any reason for the annoying runtime tests just to check
if we have issue (d) and linked an irrelevant getline.  Although it's
hard to know for sure since I don't know what system has this problem,
surely it is good enough to just check that getline is properly declared
in <stdio.h> and that we can successfully link against it.  This check
can be implemented with just one single AC_LINK_IFELSE invocation.

I feel that issue (b) is much more sensible to work around in the source
code (by initializing the size to zero), that way we don't need to probe
for it at all or reject this implementation.

Funnily enough there is an unmentioned bug in the AIX 7.2 getline which
is not the reason it gets rejected by gnulib: passing a buffer size of
exactly one byte causes it to return 0 immediately instead of actually
reading input and enlarging the buffer.  Work around that problem too
in the rendertest application (the only place that can hit it).

12 files changed:
Makefile.am
configure.ac
m4/gnulib-cache.m4
src/cdecl99.c
src/getline.h [new file with mode: 0644]
src/version.h
t/crossparse.c
t/normalize.c
t/rendertest.c
t/test.h
t/testlib.c
tests/general.at

index 7b60f4b5f4aee82884266413cf5cb96eea2c996d..c32eb926fdc3b6003d6b1f1e9cac9c1c2a1d1132 100644 (file)
@@ -73,7 +73,7 @@ EXTRA_libgnu_a_DEPENDENCIES = $(static_gl_objects)
 dummy $(static_gl_objects): $(gnulib_headers)
 
 bin_PROGRAMS = cdecl99
-cdecl99_SOURCES = common/src/help.c src/commands.c src/cdecl99.h
+cdecl99_SOURCES = common/src/help.c src/commands.c src/cdecl99.h src/getline.h
 EXTRA_cdecl99_DEPENDENCIES = $(libmain_a_OBJECTS) $(libexec_a_OBJECTS)
 cdecl99_LDADD = $(EXTRA_cdecl99_DEPENDENCIES) libcdecl.la libgnu.a \
                 $(LTLIBINTL) $(LTLIBREADLINE)
index 645e871f4a2db4ab7b98913dca00cb70d5902fcd..435eaf9b004482c3e232ac081642ac7ae1859d24 100644 (file)
@@ -1,4 +1,4 @@
-dnl Copyright © 2011-2013, 2020-2023 Nick Bowler
+dnl Copyright © 2011-2013, 2020-2024 Nick Bowler
 dnl
 dnl License WTFPL2: Do What The Fuck You Want To Public License, version 2.
 dnl This is free software: you are free to do what the fuck you want to.
@@ -110,5 +110,16 @@ AH_BOTTOM([#include <conf_post.h>])
 AC_CONFIG_TESTDIR([.], [t:.])
 DX_PROG_AUTOTEST_AM
 
+AC_CACHE_CHECK([for getline], [dx_cv_have_getline],
+  [AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <stdio.h>
+], [ssize_t (*x)() = getline;
+char *p = 0;
+size_t n = 0;
+return getline(&p, &n, stdin);
+])], [dx_cv_have_getline=yes], [dx_cv_have_getline=no])])
+AS_CASE([$dx_cv_have_getline], [yes],
+  [AC_DEFINE([HAVE_GETLINE], [1],
+    [Define to 1 if the getline function is available.])])
+
 AC_CONFIG_FILES([Makefile])
 AC_OUTPUT
index 870a647eefac1ee821871cab6abd9ada69b2943d..13e0fa21a9f329354d3bc91d587530176684aa61 100644 (file)
@@ -42,7 +42,6 @@
 #  --avoid=gperf \
 #  --avoid=std-gnu11 \
 #  dx-nls \
-#  getline \
 #  getopt-gnu \
 #  gitlog-to-changelog \
 #  inttypes-incomplete \
@@ -55,7 +54,6 @@
 gl_LOCAL_DIR([lib/local])
 gl_MODULES([
   dx-nls
-  getline
   getopt-gnu
   gitlog-to-changelog
   inttypes-incomplete
index 574d36e5c6f7e2334fdf5e3854979c7827c1b668..3eb7cc1b4c99ef2815eb11d3360c8a119b0166a9 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Command line utility for making sense of C declarations.
- * Copyright © 2011-2012, 2020-2023 Nick Bowler
+ * Copyright © 2011-2012, 2020-2024 Nick Bowler
  *
  * 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
@@ -37,6 +37,7 @@
 #include "xtra.h"
 #include "options.h"
 #include "version.h"
+#include "getline.h"
 
 #if HAVE_READLINE_READLINE_H
 #  include <readline/readline.h>
@@ -115,21 +116,6 @@ static void print_help(const struct option *lopts)
        printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
 }
 
-static int do_getline(char **linebuf, size_t *n)
-{
-       ssize_t rc;
-
-       if ((rc = getline(linebuf, n, stdin)) < 0) {
-               if (ferror(stdin))
-                       print_error("%s", strerror(errno));
-               return 0;
-       }
-
-       if (rc-- && (*linebuf)[rc] == '\n')
-               (*linebuf)[rc] = '\0';
-       return 1;
-}
-
 static int do_readline(char **linebuf, size_t *n, bool batch)
 {
 #if !HAVE_READLINE
@@ -156,7 +142,7 @@ static int repl(bool batch)
 {
        char *line = NULL;
        bool fail = 0;
-       size_t n;
+       size_t n = 0;
 
        while (do_readline(&line, &n, batch)) {
                int rc = run_command(line, batch);
diff --git a/src/getline.h b/src/getline.h
new file mode 100644 (file)
index 0000000..f4116a0
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Copyright © 2024 Nick Bowler
+ *
+ * getline-like function which removes trailing newline (if any), and
+ * returns nonzero if and only if a line was successfully read.
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+
+#ifdef CDECL_TEST_H_
+/*
+ * Use the minimum initial alloc size in the test programs to ensure we at
+ * least are exercising the realloc path on a reasonably regular basis.
+ */
+#  define CDECL99_GETLINE_INITIAL_ALLOC 1
+#else
+#  define CDECL99_GETLINE_INITIAL_ALLOC 75
+#endif
+
+static inline int do_getline(char **linebuf, size_t *n)
+{
+       extern void print_error(const char *fmt, ...);
+       const char *errmsg;
+
+#if HAVE_GETLINE
+       ssize_t rc;
+
+       if ((rc = getline(linebuf, n, stdin)) < 0) {
+               if (ferror(stdin))
+                       goto input_error;
+               return 0;
+       }
+
+       if (rc-- && (*linebuf)[rc] == '\n')
+               (*linebuf)[rc] = '\0';
+       return 1;
+#else
+       char *work = *linebuf;
+       size_t pos = 0;
+       size_t sz;
+
+       if (!work) {
+               sz = CDECL99_GETLINE_INITIAL_ALLOC;
+               goto initial_alloc;
+       }
+
+       for (sz = *n;;) {
+               if (!fgets(&work[pos], sz - pos, stdin)) {
+                       if (ferror(stdin))
+                               goto input_error;
+
+                       return !!pos;
+               }
+
+               pos += strlen(&work[pos]);
+               if (work[pos-1] == '\n') {
+                       work[pos-1] = '\0';
+                       return 1;
+               }
+
+               if (sz > INT_MAX/2 || sz > ((size_t)-1)/4)
+                       break;
+
+               sz = ((sz*4) + 2) / 3;
+initial_alloc:
+               work = realloc(work, sz);
+               if (!work)
+                       break;
+               *linebuf = work;
+               *n = sz;
+       }
+
+       errmsg = _("failed to allocate memory");
+#endif
+       if (0) input_error: errmsg = strerror(errno);
+       print_error("%s", errmsg);
+       return 0;
+}
+
index 9d41d110cc1557369138218ad2d2ed436fd19c31..347a4163f801d3d7934cfd9663cc26595d3c4286 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2023 Nick Bowler
+ * Copyright © 2023-2024 Nick Bowler
  *
  * 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
@@ -20,7 +20,7 @@
 
 #define version_blurb(s, copyright) \
        s "\n" \
-       copyright "2023 Nick Bowler\n" \
+       copyright "2024 Nick Bowler\n" \
        "License GPLv3+: GNU GPL version 3 or any later version.\n" \
        "This is free software: you are free to change and redistribute it.\n" \
        "There is NO WARRANTY, to the extent permitted by law." \
index e77a1cc9f21b846d6f46866affd5181d82e717b8..5f7eddc1324d732cda1634039b20465b24b1f725 100644 (file)
@@ -28,6 +28,7 @@
 
 #define PROGNAME "crossparse"
 #include "test.h"
+#include "getline.h"
 
 static const char sopts[] = "f:ECVH";
 static const struct option lopts[] = {
@@ -139,7 +140,6 @@ int main(int argc, char **argv)
        int i;
 
        const char *filename = NULL;
-       FILE *infile = NULL;
 
        if (argc > 0)
                progname = argv[0];
@@ -153,7 +153,6 @@ int main(int argc, char **argv)
                        mode = MODE_ENGLISH;
                        break;
                case 'f':
-                       infile = stdin;
                        filename = optarg;
                        break;
                case 'V':
@@ -168,29 +167,19 @@ int main(int argc, char **argv)
                }
        }
 
-       if (infile) {
+       if (filename && !freopen(filename, "r", stdin)) {
+               print_error("%s: %s", filename, strerror(errno));
+               return EXIT_FAILURE;
+       } else if (filename) {
                char *line = NULL;
-               size_t n;
-
-               if (filename) {
-                       infile = fopen(filename, "r");
-                       if (!infile) {
-                               print_error("%s: %s", filename,
-                                           strerror(errno));
-                               return EXIT_FAILURE;
-                       }
-               }
+               size_t n = 0;
 
-               while (getline(&line, &n, infile) >= 0) {
-                       char *c = strchr(line, '\n');
-                       if (c)
-                               *c = '\0';
+               while (do_getline(&line, &n)) {
                        if (!test_crossparse(line, mode))
                                ret = EXIT_FAILURE;
                }
 
                free(line);
-               fclose(infile);
        } else if (argv[optind]) {
                for (i = optind; i < argc; i++) {
                        if (!test_crossparse(argv[i], mode))
index e11e6484f94ba0ca6479678b8a02dd3249d488ec..e309d70ec08b9be3be1cb3c8dc423ba1a51ca336 100644 (file)
@@ -27,6 +27,7 @@
 
 #define PROGNAME "normalize"
 #include "test.h"
+#include "getline.h"
 
 static const char sopts[] = "f:VH";
 static const struct option lopts[] = {
@@ -191,17 +192,12 @@ int main(int argc, char **argv)
                }
        }
 
-       if (filename) {
-               infile = fopen(filename, "r");
-               if (!infile) {
-                       perror(filename);
-                       return EXIT_FAILURE;
-               }
+       if (filename && !freopen(filename, "r", stdin)) {
+               print_error("%s: %s", filename, strerror(errno));
+               return EXIT_FAILURE;
        }
 
-       while ((rc = getline(&line, &n, infile)) >= 0) {
-               if (rc > 0 && line[rc-1] == '\n')
-                       line[rc-1] = 0;
+       while (do_getline(&line, &n)) {
                if (do_normalize(line, n) < 0)
                        ret = EXIT_FAILURE;
        }
index cd142298e6b43fef345913453b8311f56c9cbd12..00e0c59f0a7981e4d5f2b3cdc32280b4961cfc25 100644 (file)
@@ -27,6 +27,7 @@
 
 #define PROGNAME "rendertest"
 #include "test.h"
+#include "getline.h"
 
 static const char sopts[] = "n:ECVH";
 static const struct option lopts[] = {
@@ -125,12 +126,12 @@ int main(int argc, char **argv)
                }
        }
 
-       line = malloc_nofail((sz = n+1));
-       while (getline(&line, &sz, stdin) >= 0) {
-               char *c = strchr(line, '\n');
-               if (c)
-                       *c = '\0';
-
+       /*
+        * Ensure the preallocated buffer is more than one byte, otherwise we
+        * will hit a bug in AIX 7.2 getline and fall into an infinite loop.
+        */
+       line = malloc_nofail((sz = MAX(n+1, 10)));
+       while (do_getline(&line, &sz)) {
                if (do_test(line, n, mode) < 0)
                        ret = EXIT_FAILURE;
        }
index 9e8ddee252fa46c79b0149c218d78e818cd39010..2e7882de5bf9cbdd6c74929c3bbdb8034745a285 100644 (file)
--- a/t/test.h
+++ b/t/test.h
@@ -28,6 +28,7 @@
 #endif
 
 #define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
 
 struct cdecl_declspec;
 struct option;
index 10b27ad81c3ccef7a7a3bbebd83ba58fdfecc3cd..dd9e0736a06e31687894cb121a1f2cf1f46072b9 100644 (file)
@@ -20,6 +20,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <stdarg.h>
 #include <errno.h>
 #include <getopt.h>
 
index f73f78114f99068b31092332499889005c03907f..c40d6b511e56b13008220ff92165eb18362d5232 100644 (file)
@@ -230,3 +230,12 @@ AT_CHECK([echo >>stdout; sed -f check.sed stdout], [0],
 ])
 
 AT_CLEANUP
+
+AT_SETUP([cdecl99 EOF in batch mode])
+
+AT_CHECK([AS_ECHO_N(["explain int"]) | cdecl99 --batch | head], [0],
+[[type int
+]])
+AT_CHECK([cdecl99 --batch </dev/null])
+
+AT_CLEANUP