Subject: Re: bug#23222: test: incorrect operator-precedence

From: Mattias Andrée <[email-masked]>
Date: Tue, 5 Apr 2016 20:36:07 +0200

On Tue, 5 Apr 2016 09:09:40 -0600
Eric Blake <[email-masked]> wrote:

> tag 23222 confirmed
> thanks
> 
> On 04/05/2016 05:57 AM, Mattias Andrée wrote:
> > Failing test-case #1:
> > 
> > ./test -n -a -n
> > 
> > fails and outputs
> > 
> >   ./test: extra argument ‘-n’
> > 
> > Expected behaviour is silent success, as seen in
> > Bash's implementation.  
> 
> Thanks for the report.  POSIX indeed says that -a is a
> binary primary, and also says that
> "3 arguments:
>         If $2 is a binary primary, perform the binary
> test of $1 and
> $3." (http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html)
> 
> So by that reading, coreutils MUST parse your expression
> as:
> 
> test \( -n \) -a \( -n \)
> 
> whereas it appears that coreutils has instead
> accidentally parsed it as:
> 
> test \( -n -a \) -n
> 
> Not everyday someone finds a real bug in 'test'!
> 
> That said, POSIX _also_ says that portable scripts MUST
> NOT use -a or -o; it is inherently ambiguous.
> 
> You are better off fixing your script to use:
> 
> test -n && test -n
> 
> whether or not someone is able to quickly patch
> coreutils' bug.

I'm not actually using -a, -o, (, ), and !. I just
noticed the bug by running some test-cases.

> 
> 
> > 
> > Explanation: -a has higher precedence than -n.  
> 
> Not quite true.  The POSIX rules state that -a is
> optional (non-XSI systems don't even have to have it),
> and that precedence has no bearing (rather, it is whether
> you have exactly three arguments that determines whether
> -a must be used in its role as a binary operator).
> 
> 
> > 
> > Failing test-case #2:
> > 
> > ./test ! '' -a ''
> > 
> > shall fail (exit value 1), but in this
> > implementation it succeeds.  
> 
> Sorry, but here, you're wrong.  POSIX states:
> 
> "4 arguments:
>         If $1 is '!', negate the three-argument test of
> $2, $3, and $4."
> 
> which means this parses as:
> 
> test ! \( '' -a '' \)
> 
> or as:
> 
> test ! \( \( '' \) -a \( '' \) \)
> 
> The negation of 'false and false' is true, so the exit
> status is correctly 0.
> 
> 
> > 
> > Explanation: ! has higher precedence than -a,  
> 
> Wrong.  As mentioned above, the rules are based not on
> precedence, but on how many operands are present.
> 

XSI-conformant systems shall use the following precedence
rules (highest to lowest), as documented (not quite as clearly)
in the POSIX specifications:

* parentheses

(1)

* string binary primaries

* unary non-string primaries

* algebraic binary primaries

(2)

* unary string primary

with

* (3)

* !

* -a

* -o

* (4)

in that order but not necessarily directly
followed by each other somewhere between
(1) and (2), and with other binary primitives
somewhere between (3) and (5).

The POSIX specification does indeed that that
the number of arguments shall determine the
precedence for 1 to 4 arguments. And the given
rules do conflict with XSI. But if we run

test \( \( ... \) \) instead of test it seems
only reasonable to use the XSI rules. Is it
too much to ask that test ... gives the same
result? Your implementation appear to be unaffected
if it is given extra parentheses in this way.
But it does evaluate test ! \( '' \) -a \( '' \)
as expected. It is very weird that
test ! \( '' \) -a \( '' \) and
test ! '' -a '' do not give the same result.

I believe that POSIX rule for 4 arguments shall
be disregarded. It is clearly only intended for
other binary operators than logical operators.