Background: we have in sc/source/filter/inc/ftools.hxx supposedly
"safe" casts that are (according to the Doxygen documentation)
supposed to be safer than just static_cast. As fdo#39589 uncovered,
they are actually so buggy as to be *less* safe.
Back in November, I wrote a prototype _actually_ safe versions, but
got sidetracked and forgot about them. I'm unearthing them again now,
attached.
The big remaining issue is that it is a WaE disaster on "warning:
comparison between signed and unsigned", because the compiler does not
notice that these comparisons are done under scope of if/else
conditions that make them safe. Also "warning: test will always be
true/false", since the code covers all cases and not all cases make
sense for all combinations of A,B in "cast a value of type A to type
B": the idea is for the compiler to prune away if/else branches that
will not be hit.
The "comparison between signed and unsigned" warnings cannot be easily
silenced by explicit casts, because the code makes use of the
compiler's automatic 'the smaller type is promoted to the bigger type'
rules. We could instead reimplement that logic manually, but I'm
rather loathe on making the code more complicated just to avoid
spurious warnings.
So maybe we could selectively disable the warnings on this file
instead? Someone knows how to do that with gcc and with MSVC?
As I wrote in November:
That thing is devilishly hard to get right... I think I have a
correct version now, (...)
But IMHO it needs to be unittest-ed deeply before commit.
--
Lionel
#include <X11/Xlibint.h>
#include <limits>
#include <memory>
#include <stdio.h>
// assumptions:
// \forall typename Type:
// ( (::std::numeric_limits< Type >::min() == 0) XOR ( ::std::numeric_limits< Type >::min() < 0
&& ::std::numeric_limits< Type >::is_signed ))
// && (::std::numeric_limits< Type >::max() > 0 )
// signed unbound types are unbound towards both infinities
template< typename ReturnType, typename Type >
inline ReturnType ulimit_cast( Type nValue, ReturnType nMax )
{
if ( ! ::std::numeric_limits< ReturnType >::is_signed && ::std::numeric_limits< Type >::is_signed
&& nValue < 0 )
// nValue cannot be represented in ReturnType -> return nMax
// We could have returned 0, but "ulimit_cast (Type nValue)" depends on this behaviour
return nMax;
else if ( nMax < 0 && nValue >= 0 )
return nMax;
else if ( ! ::std::numeric_limits< ReturnType >::is_bounded )
return ::std::min<ReturnType>(static_cast<ReturnType>(nValue), nMax);
// Due to implicit type conversion, the comparison in the if occurs in the type
// that has the biggest ::max()
// -> safe, because case (nMax < 0 && Type is unsigned) is already excluded
else if ( nMax > ::std::numeric_limits< Type >::max() )
// nValue <= ::std::numeric_limits< Type >::max() < nMax,
// and it is unsafe to compare nMax to nValue within Type:
// nMax would be converted with loss
return static_cast < ReturnType >(nValue);
else
// nMax fits in Type; safe to compare to nValue within Type
return static_cast< ReturnType >(::std::min<Type>(nValue, nMax));
}
/* if ( (nValue < 0) && (nValue < ::std::numeric_limits< ReturnType >::min()) )
return nMax
else
return cast(min<Type> ( nValue, nMax ))
*/
template< typename ReturnType, typename Type >
inline ReturnType ulimit_cast( Type nValue )
{
return ulimit_cast( nValue, ::std::numeric_limits< ReturnType >::max() );
}
template< typename ReturnType, typename Type >
inline ReturnType llimit_cast( Type nValue, ReturnType nMin )
{ return static_cast< ReturnType >( ::std::max< Type >( nValue, nMin ) ); }
template< typename ReturnType, typename Type >
inline ReturnType llimit_cast( Type nValue )
{ return llimit_cast( nValue, ::std::numeric_limits< ReturnType >::min() ); }
template< typename ReturnType, typename Type >
inline ReturnType limit_cast( Type nValue, ReturnType nMin, ReturnType nMax )
{
// First two cases: problematic cases in signed<->unsigned casts
if ( nMin >= 0 && nValue < 0 )
return nMin;
else if ( nValue >= 0 && nMax < 0 )
return nMax;
else if ( ! ::std::numeric_limits< ReturnType >::is_bounded )
return ::std::min<ReturnType>(::std::max(static_cast<ReturnType>(nValue), nMin), nMax);
else
{
// We first take care of nMax
/* comparison safe because (nMax < 0 && Type unsigned) already handled */
if ( nMax <= ::std::numeric_limits< Type >::max() )
{
if ( nMax < 0 &&
/* Type is necessarily signed */
nMax < ::std::numeric_limits< Type >::min() )
// nValue >= ::std::numeric_limits< Type >::min() > nMax
return nMax;
else
// safe to compare within Type
nValue = ::std::min< Type >( nValue, static_cast< Type >(nMax) );
}
/* else nValue <= ::std::numeric_limits< Type >::max() <= nMax */
// Now nValue <= nMax
// We take care of nMin
if ( nMin >= 0 )
{
// nValue >= 0 because nMax >= nMin >= 0 and case (nValue < 0 && nMin >=0) is already handled
// thus no signedness issues
if ( nMin <= ::std::numeric_limits< Type >::max() )
nValue = ::std::max< Type >( nValue, static_cast< Type >(nMin) );
else
// nValue <= ::std::numeric_limits< Type >::max() < nMin
return nMin;
}
else if ( nValue >= 0 )
// nValue >= 0 > nMin
return static_cast < ReturnType > (nValue);
// both Type and ReturnType are unsigned
else if ( nMin < ::std::numeric_limits< Type >::min() )
// nMin < ::std::numeric_limits< Type >::min() < nValue
return static_cast< ReturnType > (nValue);
else
// safe to compare
nValue = ::std::max< Type >( nValue, static_cast< Type >(nMin) );
return static_cast< ReturnType >(nValue);
}
}
template< typename ReturnType, typename Type >
inline ReturnType limit_cast( Type nValue )
{ return limit_cast( nValue, ::std::numeric_limits< ReturnType >::min(), ::std::numeric_limits<
ReturnType >::max() ); }
int main(int argc, char *argv[])
{
int foo = (1l << 31) - 1 ;
printf("foo: %d\nstatic_cast: %u\nulimit_cast: %u\nlimit_cast: %u\n", foo, static_cast<unsigned
int>(foo), ulimit_cast<unsigned int>(foo), limit_cast<unsigned int>(foo));
}
Context
- [protoPATCH] "safe" numeric casts with explicit min/max · Lionel Elie Mamane
Privacy Policy |
Impressum (Legal Info) |
Copyright information: Unless otherwise specified, all text and images
on this website are licensed under the
Creative Commons Attribution-Share Alike 3.0 License.
This does not include the source code of LibreOffice, which is
licensed under the Mozilla Public License (
MPLv2).
"LibreOffice" and "The Document Foundation" are
registered trademarks of their corresponding registered owners or are
in actual use as trademarks in one or more countries. Their respective
logos and icons are also subject to international copyright laws. Use
thereof is explained in our
trademark policy.