Hi tech --

I've shared this with a few developers, and have been advised to
share now with a wider audience.

This diff adds -E flag functionality to m4(1). I wrote this diff
after noticing a patch in ports/devel/scons by jasper@ with the
comment:
XXX: OpenBSD's m4(1) lacks the -E option (needs to be implemented though).

The -E flag causes warnings to become fatal. It appears to be a
GNU extension. Unfortunately, the GNU people have multiple
definitions of what fatal is, so I will outline the situation and
my approach to this diff.

The -E flag was first introduced to GNU m4 in 1994, version 1.2.
The flag, when set, caused m4 to exit with an exit status > 0
immediately upon issuing its first warning.

In GNU m4 1.4.9, released in 2007, this behavior was changed to
do the following:
1. If a single -E flag is given, still change the exit status of
   m4 to be > 0, but otherwise continue as normal.
2. If 2 or more -E flags are given, do the old -E behavior, that
   is, exit with an exit status > 0 immediately upon issuing the
   first warning.
This is the current behavior of all later GNU m4 releases.

I have chosen the 1.4.9 and later behavior for our m4, as it has
been now 10 years since the new behavior was introduced.

I also looked to see if there is an upstream for m4. There is not
as far as I can tell. So we are our own upstream. In fact, we are
upstream for other projects as well.

Here's that situation:
1. FreeBSD (and DragonFly) sync their m4(1) to ours. They do not
   have an -E flag because we don't.
2. NetBSD added -E flag support in January 2016. However, NetBSD
   opted to implement the old pre-GNU m4 1.4.9 -E flag behavior.
   Additionally, NetBSD's implementation does not error out for
   all warnings: only warnings for functions contained within
   gm4.c got the -E flag treatment. Therefore, it is possible to
   run NetBSD's m4(1) with the -E flag, receive a warning, and
   not error out (and the -E flag does absolutely nothing in the
   NetBSD implementation if the -g flag is not also given).
   My implementation was written entirely independently from the
   NetBSD implementation, as I did not even know they had added
   -E flag support until this diff was written and sent off for
   early review.
3. Solaris and AIX implementations of m4(1) don't have -E flag
   support.
4. Mac OS X (tested on 10.12.5) has GNU m4 1.4.6 as the version
   that comes with the base system, meaning that anyone who
   uses that version will get the old pre-1.4.9 behavior.

This diff comes with man page additions explaining the new flag,
as well as a regress test for -E -E behavior. I could not figure
out how to write a regress test that checks for exit status, so
there is no test for single -E behavior.

Comments appreciated.

~Brian

Index: usr.bin/m4/eval.c
===================================================================
RCS file: /cvs/src/usr.bin/m4/eval.c,v
retrieving revision 1.74
diff -u -p -u -p -r1.74 eval.c
--- usr.bin/m4/eval.c   5 Feb 2015 12:59:57 -0000       1.74
+++ usr.bin/m4/eval.c   11 Jun 2017 22:52:08 -0000
@@ -269,6 +269,12 @@ expand_builtin(const char *argv[], int a
                                        warn("%s at line %lu: include(%s)",
                                            CURRENT_NAME, CURRENT_LINE, 
argv[2]);
                                        exit_code = 1;
+                                       /* exit immediately if multiple -E flags
+                                        */
+                                       if (fatal_warns == 2) {
+                                               killdiv();
+                                               exit(exit_code);
+                                       }
                                } else
                                        err(1, "%s at line %lu: include(%s)",
                                            CURRENT_NAME, CURRENT_LINE, 
argv[2]);
Index: usr.bin/m4/extern.h
===================================================================
RCS file: /cvs/src/usr.bin/m4/extern.h,v
retrieving revision 1.54
diff -u -p -u -p -r1.54 extern.h
--- usr.bin/m4/extern.h 12 May 2014 19:11:19 -0000      1.54
+++ usr.bin/m4/extern.h 11 Jun 2017 22:52:08 -0000
@@ -58,6 +58,8 @@ extern void doesyscmd(const char *);
 extern void getdivfile(const char *);
 extern void doformat(const char *[], int);
 
+extern void check_fatal_warns(void);
+
 /* look.c */
 
 #define FLAG_UNTRACED 0
@@ -175,4 +177,5 @@ extern int  synch_lines;    /* line synchro
 
 extern int mimic_gnu;          /* behaves like gnu-m4 */
 extern int prefix_builtins;    /* prefix builtin macros with m4_ */
+extern int fatal_warns;                /* make warnings fatal */
 
Index: usr.bin/m4/gnum4.c
===================================================================
RCS file: /cvs/src/usr.bin/m4/gnum4.c,v
retrieving revision 1.50
diff -u -p -u -p -r1.50 gnum4.c
--- usr.bin/m4/gnum4.c  29 Apr 2015 00:13:26 -0000      1.50
+++ usr.bin/m4/gnum4.c  11 Jun 2017 22:52:08 -0000
@@ -234,7 +234,7 @@ addchar(int c)
 }
 
 static char *
-getstring()
+getstring(void)
 {
        addchar('\0');
        current = 0;
@@ -255,11 +255,29 @@ exit_regerror(int er, regex_t *re, const
        m4errx(1, "regular expression error in %s: %s.", source, errbuf);
 }
 
+void
+check_fatal_warns(void)
+{
+
+       /* Do nothing if no -E flags, set exit_code > 0 but keep going
+        * if one -E flag, exit immediately with exit status > 0 if
+        * two or more -E flags.
+        */
+       if (fatal_warns == 0)
+               return;
+       else if (fatal_warns == 1)
+               exit_code = 1;
+       else
+               exit(1);
+}
+
 static void
 add_sub(int n, const char *string, regex_t *re, regmatch_t *pm)
 {
-       if (n > re->re_nsub)
+       if (n > re->re_nsub) {
                warnx("No subexpression %d", n);
+               check_fatal_warns();
+       }
        /* Subexpressions that did not match are
         * not an error.  */
        else if (pm[n].rm_so != -1 &&
@@ -443,6 +461,7 @@ dopatsubst(const char *argv[], int argc)
 {
        if (argc <= 3) {
                warnx("Too few arguments to patsubst");
+               check_fatal_warns();
                return;
        }
        /* special case: empty regexp */
@@ -495,6 +514,7 @@ doregexp(const char *argv[], int argc)
 
        if (argc <= 3) {
                warnx("Too few arguments to regexp");
+               check_fatal_warns();
                return;
        }
        /* special gnu case */
Index: usr.bin/m4/m4.1
===================================================================
RCS file: /cvs/src/usr.bin/m4/m4.1,v
retrieving revision 1.63
diff -u -p -u -p -r1.63 m4.1
--- usr.bin/m4/m4.1     14 Sep 2015 20:06:58 -0000      1.63
+++ usr.bin/m4/m4.1     11 Jun 2017 22:52:08 -0000
@@ -38,7 +38,7 @@
 .Nd macro language processor
 .Sh SYNOPSIS
 .Nm
-.Op Fl gPs
+.Op Fl EgPs
 .Oo
 .Sm off
 .Fl D Ar name Op No = Ar value
@@ -127,6 +127,19 @@ turn on all options.
 .Pp
 By default, trace is set to
 .Qq eq .
+.It Fl E
+Set warnings to be fatal.
+When a single
+.Fl E
+flag is specified, if warnings are issued, execution continues but
+.Nm
+will exit with a non-zero exit status.
+When multiple
+.Fl E
+flags are specified, execution will halt upon issuing the first warning and
+.Nm
+will exit with a non-zero exit status.
+This behaviour matches GNU-m4 1.4.9 and later.
 .It Fl g
 Activate GNU-m4 compatibility mode.
 In this mode, translit handles simple character
@@ -434,7 +447,9 @@ Returns the current file's name.
 .Pp
 But note that the
 .Ic m4exit
-macro can modify the exit status.
+macro can modify the exit status, as can the
+.Fl E
+flag.
 .Sh STANDARDS
 The
 .Nm
@@ -443,7 +458,7 @@ utility is compliant with the
 specification.
 .Pp
 The flags
-.Op Fl dgIPot
+.Op Fl dEgIPot
 and the macros
 .Ic builtin ,
 .Ic esyscmd ,
Index: usr.bin/m4/main.c
===================================================================
RCS file: /cvs/src/usr.bin/m4/main.c,v
retrieving revision 1.86
diff -u -p -u -p -r1.86 main.c
--- usr.bin/m4/main.c   3 Nov 2015 16:21:47 -0000       1.86
+++ usr.bin/m4/main.c   11 Jun 2017 22:52:08 -0000
@@ -77,6 +77,7 @@ char scommt[MAXCCHARS+1] = {SCOMMT};  /* 
 char ecommt[MAXCCHARS+1] = {ECOMMT};   /* end character for comment   */
 int  synch_lines = 0;          /* line synchronisation for C preprocessor */
 int  prefix_builtins = 0;      /* -P option to prefix builtin keywords */
+int  fatal_warns = 0;          /* -E option to make warnings fatal */
 
 struct keyblk {
         char    *knam;          /* keyword name */
@@ -185,7 +186,7 @@ main(int argc, char *argv[])
        outfile = NULL;
        resizedivs(MAXOUT);
 
-       while ((c = getopt(argc, argv, "gst:d:D:U:o:I:P")) != -1)
+       while ((c = getopt(argc, argv, "gst:d:D:EU:o:I:P")) != -1)
                switch(c) {
 
                case 'D':               /* define something..*/
@@ -195,6 +196,10 @@ main(int argc, char *argv[])
                        if (*p)
                                *p++ = EOS;
                        dodefine(optarg, p);
+                       break;
+               case 'E':               /* like GNU m4 1.4.9+ */
+                       if (fatal_warns < 2)
+                               fatal_warns++;
                        break;
                case 'I':
                        addtoincludepath(optarg);
Index: usr.bin/m4/misc.c
===================================================================
RCS file: /cvs/src/usr.bin/m4/misc.c,v
retrieving revision 1.46
diff -u -p -u -p -r1.46 misc.c
--- usr.bin/m4/misc.c   7 Dec 2015 14:12:46 -0000       1.46
+++ usr.bin/m4/misc.c   11 Jun 2017 22:52:08 -0000
@@ -379,9 +379,9 @@ xstrdup(const char *s)
 }
 
 void
-usage()
+usage(void)
 {
-       fprintf(stderr, "usage: m4 [-gPs] [-Dname[=value]] [-d flags] "
+       fprintf(stderr, "usage: m4 [-EgPs] [-Dname[=value]] [-d flags] "
                        "[-I dirname] [-o filename]\n"
                        "\t[-t macro] [-Uname] [file ...]\n");
        exit(1);
Index: usr.bin/m4/tokenizer.l
===================================================================
RCS file: /cvs/src/usr.bin/m4/tokenizer.l,v
retrieving revision 1.8
diff -u -p -u -p -r1.8 tokenizer.l
--- usr.bin/m4/tokenizer.l      12 Apr 2012 17:00:11 -0000      1.8
+++ usr.bin/m4/tokenizer.l      11 Jun 2017 22:52:08 -0000
@@ -22,6 +22,8 @@
 #include <stdint.h>
 #include <limits.h>
 
+extern void check_fatal_warns(void);
+extern int fatal_warns;
 extern int mimic_gnu;
 extern int32_t yylval;
 
@@ -67,6 +69,7 @@ number()
        if (((l == LONG_MAX || l == LONG_MIN) && errno == ERANGE) ||
            l > INT32_MAX || l < INT32_MIN) {
                fprintf(stderr, "m4: numeric overflow in expr: %s\n", yytext);
+               check_fatal_warns();
        }
        return l;
 }
@@ -83,6 +86,7 @@ parse_radix()
        base = strtol(yytext+2, &next, 0);
        if (base > 36 || next == NULL) {
                fprintf(stderr, "m4: error in number %s\n", yytext);
+               check_fatal_warns();
        } else {
                next++;
                while (*next != 0) {
@@ -97,6 +101,7 @@ parse_radix()
                        if (d >= base) {
                                fprintf(stderr, 
                                    "m4: error in number %s\n", yytext);
+                               check_fatal_warns();
                                return 0;
                        }
                        l = base * l + d;
Index: regress/usr.bin/m4/Makefile
===================================================================
RCS file: /cvs/src/regress/usr.bin/m4/Makefile,v
retrieving revision 1.32
diff -u -p -u -p -r1.32 Makefile
--- regress/usr.bin/m4/Makefile 1 Nov 2016 00:35:34 -0000       1.32
+++ regress/usr.bin/m4/Makefile 11 Jun 2017 22:52:08 -0000
@@ -13,7 +13,7 @@ REGRESS_TARGETS= test-ff_after_dnl test-
     test-gnupatterns2 test-comments test-synch1 test-synch1bis \
     test-gnuformat test-includes test-dumpdef test-gnuprefix \
     test-translit test-translit2 test-gnutranslit2 \
-    test-gnueval test-gnusofterror
+    test-gnueval test-gnusofterror test-fatalwarnings
 
 test-ff_after_dnl: ff_after_dnl.m4
        ${M4} ff_after_dnl.m4 | diff - ${.CURDIR}/ff_after_dnl.out
@@ -119,6 +119,9 @@ test-gnueval:
 test-gnusofterror:
        ${M4} -g ${.CURDIR}/gnusofterror.m4 2>/dev/null| diff -u - 
${.CURDIR}/gnusofterror.out
        ! ${M4} -g ${.CURDIR}/gnusofterror.m4 2>/dev/null >/dev/null 
+
+test-fatalwarnings:
+       ${M4} -E -E -g ${.CURDIR}/fatalwarnings.m4 2>&1 | diff -u - 
${.CURDIR}/fatalwarnings.out
 
 .PHONY:        ${REGRESS_TARGETS}
 
Index: regress/usr.bin/m4/fatalwarnings.m4
===================================================================
RCS file: regress/usr.bin/m4/fatalwarnings.m4
diff -N regress/usr.bin/m4/fatalwarnings.m4
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ regress/usr.bin/m4/fatalwarnings.m4 11 Jun 2017 22:52:08 -0000
@@ -0,0 +1,3 @@
+patsubst(`a')
+patsubst(`b')
+patsubst(`c')
Index: regress/usr.bin/m4/fatalwarnings.out
===================================================================
RCS file: regress/usr.bin/m4/fatalwarnings.out
diff -N regress/usr.bin/m4/fatalwarnings.out
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ regress/usr.bin/m4/fatalwarnings.out        11 Jun 2017 22:52:08 -0000
@@ -0,0 +1 @@
+m4: Too few arguments to patsubst

Reply via email to