Problem: API violations are treated as recoverable errors

The example is applications passing invalid arguments to a socket option
and then failing to check the return code. The results can be very hard
to diagnose. Here are some threads that show the pain this causes:

* https://github.com/zeromq/zyre/issues/179
* http://lists.zeromq.org/pipermail/zeromq-dev/2014-June/026388.html

One common argument is that a library should never assert, and should
pass errors back to the calling application. The counter argument is
that when an application is broken enough to pass garbage to libzmq,
it cannot be trusted to handle the resulting errors properly. Empirical
evidence from CZMQ, where we systematically assert on bad arguments, is
that this militant approach makes applications more, not less, robust.

I don't see any valid use cases for returning errors on bad arguments,
with one exception: zmq_setsockopt can be used to probe whether libzmq
was e.g. built with CURVE security. I'd argue that it's nasty to use a
side effect like this. If apps need to probe how libzmq was built, this
should be done explicitly, and for ALL build options, not just CURVE.

There are/were no libzmq test cases that check the return code for an
invalid option.

For now I've enabled militant assertions using --with-militant at
configure time. However I'd like to make this the default setting.
This commit is contained in:
Pieter Hintjens 2014-06-17 15:49:52 +02:00
parent 1d236d81c8
commit b4ed3f5506
2 changed files with 26 additions and 4 deletions

View File

@ -144,6 +144,17 @@ else
libzmq_pedantic="yes"
fi
AC_ARG_WITH([militant],
[AS_HELP_STRING([--with-militant],
[Enable militant API assertions])],
[zmq_militant="yes"],
[])
if test "x$zmq_militant" = "xyes"; then
AC_DEFINE(ZMQ_ACT_MILITANT, 1, [Enable militant API assertions])
fi
# By default compiling with -Werror except OSX.
libzmq_werror="yes"

View File

@ -65,6 +65,7 @@ int zmq::options_t::setsockopt (int option_, const void *optval_,
{
bool is_int = (optvallen_ == sizeof (int));
int value = is_int? *((int *) optval_): 0;
bool malformed = true; // Did caller pass a bad option value?
switch (option_) {
case ZMQ_SNDHWM:
@ -440,10 +441,21 @@ int zmq::options_t::setsockopt (int option_, const void *optval_,
}
break;
default:
// There are valid scenarios for probing with unknown socket option
// values, e.g. to check if security is enabled or not. This will not
// provoke a militant assert. However, passing bad values to a valid
// socket option will, if ZMQ_ACT_MILITANT is defined.
malformed = false;
break;
}
#if defined (ZMQ_ACT_MILITANT)
// There is no valid use case for passing an error back to the application
// when it sent malformed arguments to a socket option. Use ./configure
// --with-militant to enable this checking.
if (malformed)
zmq_assert (false);
#endif
errno = EINVAL;
return -1;
}
@ -517,6 +529,7 @@ int zmq::options_t::getsockopt (int option_, void *optval_, size_t *optvallen_)
return 0;
}
break;
case ZMQ_TYPE:
if (is_int) {
*value = type;
@ -757,9 +770,7 @@ int zmq::options_t::getsockopt (int option_, void *optval_, size_t *optvallen_)
return 0;
}
break;
}
}
errno = EINVAL;
return -1;
}