gnu-findutils

Find's cost-based optimiser breaks short-circuit evaluation


Quoting the man page of find (GNU findutils 4.7.0, emphasis mine):

GNU find searches the directory tree rooted at each given starting-point by evaluating the given expression from left to right, according to the rules of precedence (see section PERATORS), until the outcome is known (the left hand side is false for and operations, true for or), at which point find moves on to the next file name.

Therefore when find evaluates <expr1> -and <expr2> I would expect that <expr2> is not evaluated unless <expr1> is true and I rely on that to avoid some error messages, specifically, I do not want find to test whether a non readable directory is empty. Here is a SCCCE:

mkdir some_dir
chmod 333 some_dir
find * -readable ! -empty -printf "yes" -or -printf "no" -prune  

which yields

find: ‘some_dir’: Permission denied
no

Adding, otherwise implicit, -and and parentheses, the expression evaluated by find should be equivalent to

( ( -readable -and (! -empty ) ) -and -printf "yes" ) -or ( -printf "no" -and -prune )

Hence, after realising that some_directory is not readable, find should forgo the emptiness test and the evaluation of -printf "yes". Instead, it should jump to the evaluation of -printf "no" and finally -prune. The "Permission denied" in the output suggests it's evaluating -empty anyway. (Removing ! -empty from the original expression makes the error go away.)

Using -D tree to inspect the evaluation tree, I see that the optimised form (edited here for the sake of brevity and clarity) is:

(  (  ( ! -empty ) -and -readable ) -and -printf "yes" ) -or ( -printf "no" -and -prune ) 

according to which -empty is indeed evaluated and, worse, prior to -readable which completely screws up the intended logic. I reckon this is a bug. Am I right?

Update: (26-May-2020) A bug report has been submitted and it has been confirmed as a bug by the developers.


Solution

  • In my opinion, this is a bug in findutils' "arm-swapping" optimization, because it fails to consider that -empty and -xtype may have the side effect of causing find to report an error and exit with a non-zero status. I've reported the same issue about -xtype, which the findutils devs agreed was a bug. It's hard to work around this bug too, because findutils doesn't have a way to turn off this optimization. -O0 is equivalent to -O1 which already applies it.

    If you need a workaround, I wrote a drop-in replacement for find called bfs: https://github.com/tavianator/bfs. It's fully compatible with all of GNU find's options, and doesn't have this bug.