On Tue, Feb 21, 2017 at 07:00:34AM +0100, Otto Moerbeek wrote:

> On Tue, Feb 21, 2017 at 04:08:57AM +0100, Martijn Dekker wrote:
> 
> > Upon encountering a parsing error, bc(1) passes an error message on to
> > dc(1), which writes the error message to standard output along with the
> > normal output.
> > 
> > That is a bug. Error messages should go to standard error instead, as
> > POSIX specifies:
> > http://pubs.opengroup.org/onlinepubs/9699919799/utilities/bc.html#tag_20_09_10
> > 
> > GNU 'bc' and Solaris 'bc' act like POSIX says and write error messages
> > to standard error.
> > 
> > Bizarrely, the exit status of bc(1) is left unspecified:
> > http://pubs.opengroup.org/onlinepubs/9699919799/utilities/bc.html#tag_20_09_18
> > And indeed, all versions of 'bc' exit with status 0 if there is an input
> > error such as a parsing error, so the exit status cannot be used to
> > catch it. That leaves examining standard error as the only method for a
> > program calling bc(1), such as a shell script, to distinguish between an
> > error state and normal operation. That is, with this bug, there is no
> > way at all.
> > 
> > The following example shell function transparently hardens bc(1) by
> > intercepting standard error and exiting the program or subshell if an
> > error was produced.
> > 
> > bc() {
> >     _bc_err=$(command -p bc "$@" 1>&3 2>&1)
> >     [ -z "${_bc_err}" ] && return
> >     printf '%s\n' "$0: bc(1) caught errors:" "${_bc_err}" 1>&2
> >     exit 125
> > } 3>&1
> > 
> > The patch below fixes bc(1) so error messages are written directly to
> > standard error and the above shell function works as expected. As a side
> > effect, yyerror() is simplified.
> > 
> > Another side effect is that bc(1) error messages are no longer neatly
> > included in the generated dc(1) source when debugging it using 'bc -c'.
> > But I don't think that is actually a problem; they are just printed to
> > standard error instead. In fact, the patch makes 'bc -c' act like
> > Solaris. If others find this problematic, the patch could be extended to
> > restore the old behaviour only if '-c' is given.
> > 
> > The manual page does not document error message behaviour one way or
> > another. Since the patch implements standard behaviour, no change seems
> > necessary there.
> > 
> > Thanks,
> > 
> > - M.
> > 
> 
> Thanks for the diff. I am now wondering why I wrote it this way....
> Likely beacuse the original bc had a similar approach.
> Anyway, I'll try to look at this the coming days,

Indeed, 4.4BSD bc does this:

yyerror( s ) char *s; {
        if(ifile > sargc)ss="teletype";
        printf("c[%s on line %d, %s]pc\n", s ,ln+1,ss);
        fflush(stdout);
        cp = cary;
        crs = rcrs;
        bindx = 0;
        lev = 0;
        b_sp_nxt = &b_space[0];
}

My original goal was to make a bc that produced the same dc commands
as the reference implementation I used.

You can now see that your diff skips the 'c' commands an in that
changes behaviour. Pondering if introducing a way to write to
stderr in dc(1) would be better...

        -Otto

> 
> > Index: bc.y
> > ===================================================================
> > RCS file: /cvs/src/usr.bin/bc/bc.y,v
> > retrieving revision 1.49
> > diff -u -p -r1.49 bc.y
> > --- bc.y        23 Nov 2015 09:58:55 -0000      1.49
> > +++ bc.y        21 Feb 2017 02:09:08 -0000
> > @@ -942,35 +942,25 @@ add_local(ssize_t n)
> >  void
> >  yyerror(char *s)
> >  {
> > -       char    *str, *p;
> >         int     n;
> > 
> >         if (yyin != NULL && feof(yyin))
> > -               n = asprintf(&str, "%s: %s:%d: %s: unexpected EOF",
> > +               n = fprintf(stderr, "%s: %s:%d: %s: unexpected EOF\n",
> >                     __progname, filename, lineno, s);
> >         else if (yytext[0] == '\n')
> > -               n = asprintf(&str,
> > -                   "%s: %s:%d: %s: newline unexpected",
> > +               n = fprintf(stderr,
> > +                   "%s: %s:%d: %s: newline unexpected\n",
> >                     __progname, filename, lineno, s);
> >         else if (isspace((unsigned char)yytext[0]) ||
> >             !isprint((unsigned char)yytext[0]))
> > -               n = asprintf(&str,
> > -                   "%s: %s:%d: %s: ascii char 0x%02x unexpected",
> > +               n = fprintf(stderr,
> > +                   "%s: %s:%d: %s: ascii char 0x%02x unexpected\n",
> >                     __progname, filename, lineno, s, yytext[0] & 0xff);
> >         else
> > -               n = asprintf(&str, "%s: %s:%d: %s: %s unexpected",
> > +               n = fprintf(stderr, "%s: %s:%d: %s: %s unexpected\n",
> >                     __progname, filename, lineno, s, yytext);
> >         if (n == -1)
> >                 err(1, NULL);
> > -
> > -       fputs("c[", stdout);
> > -       for (p = str; *p != '\0'; p++) {
> > -               if (*p == '[' || *p == ']' || *p =='\\')
> > -                       putchar('\\');
> > -               putchar(*p);
> > -       }
> > -       fputs("]pc\n", stdout);
> > -       free(str);
> >  }
> > 
> >  void

Reply via email to