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