SafeInt information can be found here. Basically, it is an open source library authored by security expert David LeBlanc of Microsoft. It is basically a "drop-in" replacement for integer types, and will throw an exception whenever integer overflows occur. I believe that getting this added to the standard would be a boon to C++ and secure code. It should be a relatively low effort addition considering the "proof-of-concept" is already widely used within Microsoft.
SafeInt information can be found here. ...
I would most definitely like to have a standardized interface to handle numerical overflow.
I do however have some reservations on doing it by means of an integer wrapper.
First, I think we need such "safe numeric operations" to handle floating point values just as well. While the mechanisms are different, the requirements are exactly the same (and floating-point does overflow no matter how big their range is). In fact, I think we need it for arbitrary numeric types, including rationals, complex, user-defined extended precision, interval, etc... (and consistently defined for even unlimited precision numeric types, even if the overflow test always yields false)
Second, I wonder if a wrapper is the best interface. Since this is a "security" utility, it needs to be consistently applied *all over* the relevant source code, otherwise it is not really effective
A lot, if not most, critical and complex numeric code (where this is mostly needed) depends on third-party libraries, and in the numerical community there is a huge base of reusable code that is (yet) not sufficiently generic to let you get away with simple using a wrapper type at the highest level.
In other words, you might use SafeInt in your own code but it is not easy to have third-party code use it as well.
Since the overflow is always a side effect of operations, and operations are expressed in C++ via operators (and functions), I wonder if the library shouldn't be in a form of "safe operators and functions" as opposed to "safe numeric type".
IIUC, the current operator overloading mechanism should allow something like the following:
#include <safe_numeric_operations>
// This "activates" the overflow detection on any supported type, including builtin types
using std::safe_numeric_operations ;
void test()
{
try
{
int a = std::numeric_limits<int>::max();
// std::safe_numeric_operations::operator + (int,int) being called here
int r = a + a ;
}
catch ( std::bad_numeric_conversion ) { }
}
I would most definitely like to have a standardized interface to handle numerical overflow.
I do however have some reservations on doing it by means of an integer wrapper.
First, I think we need such "safe numeric operations" to handle floating point values just as well. While the mechanisms are different, the requirements are exactly the same (and floating-point does overflow no matter how big their range is). In fact, I think we need it for arbitrary numeric types, including rationals, complex, user-defined extended precision, interval, etc... (and consistently defined for even unlimited precision numeric types, even if the overflow test always yields false)
Second, I wonder if a wrapper is the best interface. Since this is a "security" utility, it needs to be consistently applied *all over* the relevant source code, otherwise it is not really effective
A lot, if not most, critical and complex numeric code (where this is mostly needed) depends on third-party libraries, and in the numerical community there is a huge base of reusable code that is (yet) not sufficiently generic to let you get away with simple using a wrapper type at the highest level.
In other words, you might use SafeInt in your own code but it is not easy to have third-party code use it as well.
IIUC, the current operator overloading mechanism should allow something like the following:
#include <safe_numeric_operations>
// This "activates" the overflow detection on any supported type, including builtin types
using std::safe_numeric_operations ;
void test()
{
try
{
int a = std::numeric_limits<int>::max();
// std::safe_numeric_operations::operator + (int,int) being called here
int r = a + a ;
}
catch ( std::bad_numeric_conversion ) { }
}
template<int64_t MIN, int64_t MAX> //minimum and maximum runtime value that can be stored
OK, I agree with your observations about silently overloading operators.
OTOH, ....
On Thu, Nov 22, 2012 at 5:57 PM, <robertmac...@gmail.com> wrote:I didn't intend to suggest that a library should do less or more than proposed here. I suggested that in order to prepare and evaluate such a proposal, the bigger picture should be taken into account.
On Thursday, November 22, 2012 9:02:42 AM UTC-8, Fernando Cacciola wrote:OK, I agree with your observations about silently overloading operators.OTOH, ....
Well one can make this very elaborate. When I was doing this I experimented with keep track of number of bits a compile time so that operations which could never overthrow would generate no runtime overhead. There are lots of things one could do along this line - especially now that we have something like auto.
But in the end, I decided to keep it as simple as I could. The whole library can be summarized in one sentence.
"any operation involving a safe numeric type will produce the expected mathematical result or throw an exception"
Making it do more or less than this would severely hamper it's utility.
For instance, both SafeInt's and your code *requires* a primitive operation that returns the result in a larger type such that it is known not to overflow.
The overflow detection is just an extra step, and it's only required because (or rather IFF) the result is needed to be converted into the same type as the operands.
That means that both libraries under consideration are really the composition of two layers: the bottom layer that performs the operation in a way that it's result is known to be correct, and the layer that performs the conversion to a given receiving type but doing a proper range check first.
On Thursday, November 22, 2012 1:33:38 PM UTC-8, Fernando Cacciola wrote:On Thu, Nov 22, 2012 at 5:57 PM, <robertmac...@gmail.com> wrote:I didn't intend to suggest that a library should do less or more than proposed here. I suggested that in order to prepare and evaluate such a proposal, the bigger picture should be taken into account.
On Thursday, November 22, 2012 9:02:42 AM UTC-8, Fernando Cacciola wrote:OK, I agree with your observations about silently overloading operators.OTOH, ....
Well one can make this very elaborate. When I was doing this I experimented with keep track of number of bits a compile time so that operations which could never overthrow would generate no runtime overhead. There are lots of things one could do along this line - especially now that we have something like auto.
But in the end, I decided to keep it as simple as I could. The whole library can be summarized in one sentence.
"any operation involving a safe numeric type will produce the expected mathematical result or throw an exception"
Making it do more or less than this would severely hamper it's utility.
For instance, both SafeInt's and your code *requires* a primitive operation that returns the result in a larger type such that it is known not to overflow.
I don't think that's true. That's not part of the interface or type requirements. It's merely an implementation feature of the library because it's the fastest way to do this. It still checks for overflows on int64 operations even though there is not int 128 type - it has to use a much slower method.
The overflow detection is just an extra step, and it's only required because (or rather IFF) the result is needed to be converted into the same type as the operands.
The result is calculated to whatever precision is required to avoid overflow. When the result is assigned somewhere - then the overflow is caught. So
safe<int16> a, b;
safe<int16> x = a + b; // could trap on overflow - but
safe<int32> x = a + b; // would never trap
An interesting side issue of this is that there is no run time overhead on the second
statement because template meta programming is used to determine that
there can never, ever be an overflow. Finally
auto x = a + b; will result in x being of type safe<int32> and will never, ever trap( actually I have to double check this).
So to my mind this is exactly what is desired.
That means that both libraries under consideration are really the composition of two layers: the bottom layer that performs the operation in a way that it's result is known to be correct, and the layer that performs the conversion to a given receiving type but doing a proper range check first.
In my implementation, the two layers are there - but can't be separated. The "lower" layer happens on the + operator while the "upper" layer happens on the = operator. There is only one library though.
I'm guessing that anyone wanting to do this could just overload the = operator. or use safe<?> as a constructor argument to his own special type.
Then I'm saying that, since the bottom layer is a requirement for these libraries, and it's also useful--and used--in several other numerical techniques, it should be standardized as such (and not hidden as an implementation detail as it proposed)
as a practical matter, I don't see how you can "throw upfront" until you actually do an assignment or explicit cast. Take the following assignment.
But that's not all.
I'm also saying that the bigger picture could critically affect design decision even within the scope proposed. I gave the example of certified arithmetic because that would suggest that a given safe_int should not necessarily just throw right up front, but it could instead flag the overflow and throw only when necessary, such as when casting to int. This is important because if you are going to compare the overflowed result, you can just return "maybe" from the comparison without throwing an exception. This not only allows the client code to be *elegantly* explicit about the way overflowed results affect the computation results, but it also allows the library to be used in contexts where exceptions are to be avoided when possible.
safe<int16> a;
safe<int32> b;
auto x = a + b; // what is the type of x?
auto x = b + a; // is the type of y the same as the type of x?
Questions like these made me decide to divide the task as you suggested
a) do the calculation resulting in a type which can hold the true result
b) on assignment or cast (safe_cast ...) trap the attempt to save a value which loses information.
Well one can make this very elaborate. When I was doing this I experimented with keep track of number of bits a compile time so that operations which could never overthrow would generate no runtime overhead. There are lots of things one could do along this line - especially now that we have something like auto.
On Thursday, November 22, 2012 6:10:24 PM UTC-8, Ben Craig wrote:
I believe that a multi-precision library has recently been submitted to boost by john maddock. It would be my hope that save<T> would work with any type which has std::limits<T> implemented and the member limits has the right definitions. I haven't spent any time considering whether this is possible though. I did define and implement a "Numeric" concept which includes all the intergers and the safe versions of the same in order to support such an idea. But I don't know whether this will really pan out or not. My main motivation was to make a "boostified" version of SafeInt.
Off topic - but related note. In the course of doing this, I spent time investigating Boost conversion which I believe you wrote. I really couldn't understand how to use it from the documentation and filed a track issue to that effect. To my knowledge it's still a pending issue. I think some more effort in this area would be very helpful.
On Fri, Nov 23, 2012 at 3:45 AM, <robertmac...@gmail.com> wrote:
On Thursday, November 22, 2012 6:10:24 PM UTC-8, Ben Craig wrote:
I would rather say that a fixed-point library, as well as a rationals library, would be alongside (or maybe one level above) safe-int, as opposed to a generalization of it.
A few facts I wanted to share:...
Many overflow check could be removed if the integer type knows its exact minimum and maximum.
I also believe that safe-numerics is very good. I think the problem of integers that grows at runtime has to be done in a different library. Also, there is the independent problem of creating efficient accumulators.
safe_int<int64_t> i;
i.accumulate(uint32_t* first, uint32_t* last);
The number of overflow check could be greatly reduced in the above equation.
SafeInt information can be found here. Basically, it is an open source library authored by security expert David LeBlanc of Microsoft. It is basically a "drop-in" replacement for integer types, and will throw an exception whenever integer overflows occur. I believe that getting this added to the standard would be a boon to C++ and secure code. It should be a relatively low effort addition considering the "proof-of-concept" is already widely used within Microsoft.
--Hi,
Le 08/11/12 03:59, Ben Craig a écrit :
Hi,
have you take a look at "C++ Binary Fixed-Point Arithmetic" http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3352.html?
It seems to respond to most of the problems (if not all) you try to solve.
Could you comment features you want that this proposal doesn't covers?
cardinal
and integral
for integer arithmetic,
and nonnegative
and negatable
for fractional arithmetic."
-- Vicente
On Sunday, November 25, 2012 1:39:58 PM UTC-8, viboes wrote:Le 08/11/12 03:59, Ben Craig a écrit :
Hi,
have you take a look at "C++ Binary Fixed-Point Arithmetic" http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3352.html?
It seems to respond to most of the problems (if not all) you try to solve.
Could you comment features you want that this proposal doesn't covers?
Very interesting. i don't know whether or not I saw this when I made my version of safe<T>.
In general, I see it as very similar. But there are some notable differences.
"The fixed-point library contains four class templates. They arecardinal
andintegral
for integer arithmetic, andnonnegative
andnegatable
for fractional arithmetic."
a) I use the integer traits defined by std::limits<T> to determined whether the type is signed/unsigned, the max/min, etc. So instead of having a variety of templates, there is only one using TMP to determine the traits.
b) I don't address fractions or rounding at all.
c) I considered a policy parameter of some sort to indicate how to handle overflows - they use an enum to select. But I rejected the idea for two reasons
i) more complexity with very little value.
ii) I couldn't see how to do it with something like
safe<int32> x;
safe<int16> y;
safe<int32> z;
z = x + y; // where would the policy parameter go?
In the end, I re-focused on a drop-in replacement for all integer types. "all integer types" means all types - instrinsic and user defined types for which std::limit<T>::is_integer() return true.
To summarize, SafeInt and safe<T> are different than the proposal in the paper above. But I believe they are better.
With the Lawrence proposal you don't have overflow as far as you don't loss range. So the natural place is to use it only with assignment.c) I considered a policy parameter of some sort to indicate how to handle overflows - they use an enum to select. But I rejected the idea for two reasons
i) more complexity with very little value.
ii) I couldn't see how to do it with something like
safe<int32> x;
safe<int16> y;
safe<int32> z;
z = x + y; // where would the policy parameter go?
integral<31,OV1> x;
integral<15,OV2> y;
integral<31,OV3> z;
z = x + y;
Check for overflow only when doing the assignment using the overflow OV3 policy.
The user will need to use a number_cast conversion like in
The fact to add the overflow policy on the type makes the code less weight.
We can have also a base class for integral that has no overflow policy and forbids assignment that can loss information
basic_integral<31> x;
basic_integral<15> y;
basic_integral<31> z;
z = x + y; // compile file
z = number_cast<OV3>(x + y);
But do the PO proposal or your Safe library allow to saturate on overflow, assert, ignore, ...?
Best,
Vicente
I wanted to add. safe<T> could be defined as a alias of the fixed point class.
-- Vicente
Some large use cases for SafeInt are computing sizes for buffers and array indices. In these situations, you probably want to throw an exception, or terminate the program. Saturation, assertions in debug mode, and ignoring aren't very good options from a security standpoint.
If you stick to "drop-in" replacements, then I'm not sure if there's any benefit to an "ignore" policy. Ignore is equivalent to the raw type.
Fixed-point libraries could make use of it though. Debug assertions aren't any better from a security standpoint, but they could help discover bugs in non-security critical code.
On Friday, November 23, 2012 4:26:08 AM UTC-8, Fernando Cacciola wrote:
On Fri, Nov 23, 2012 at 3:45 AM, <robertmac...@gmail.com> wrote:
On Thursday, November 22, 2012 6:10:24 PM UTC-8, Ben Craig wrote:
I would rather say that a fixed-point library, as well as a rationals library, would be alongside (or maybe one level above) safe-int, as opposed to a generalization of it.
I'm not sure I followed your suggestion, but somehow I'm thinking I might be in agreement. My view would be the "safe" idea would be orthogonal to the "numeric" idea. So
int, rational<T>, multiprecision<N>, .... would have std limits<T> defined. std::limits includes members to define min/max values and a lot of other features of numeric types.
This would make them fullfill the "Numeric" concept (type requirements).
safe<T> would be defined for any type T fullfilling the "Numeric" concept.
and safe<T> would also have std::limits implemented so it would fullfill the "Numeric" concept as well.
This would separate the "safe" idea from the "numeric" idea and permit users to use the "safe" version if and only if actually desired. It would also clarify what "safe" does as opposed to what "numeric" does. My proposal implements this idea for current integer types.
It hasn't really been investigated whether this extends well to all integer types for which std::limits is implemented. Anyway, this is far into the future.
Le 23/11/12 18:10, robertmac...@gmail.com a écrit :safe and integers could be orthogonal, but I don't see how a safe<T> class could provide safety to rational as overflow is avoided by using gcd while doing the addition of two rationals and many other tricks.
Robert Ramey
Le 08/11/12 03:59, Ben Craig a écrit :
Hi,SafeInt information can be found here. Basically, it is an open source library authored by security expert David LeBlanc of Microsoft. It is basically a "drop-in" replacement for integer types, and will throw an exception whenever integer overflows occur. I believe that getting this added to the standard would be a boon to C++ and secure code. It should be a relatively low effort addition considering the "proof-of-concept" is already widely used within Microsoft.
--
have you take a look at "C++ Binary Fixed-Point Arithmetic" http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3352.html?
Some large use cases for SafeInt are computing sizes for buffers and array indices.
In these situations, you probably want to throw an exception, or terminate the program.
Saturation, assertions in debug mode, and ignoring aren't very good options from a security standpoint.
On Sunday, November 25, 2012 11:17:04 PM UTC-8, viboes wrote:
With the Lawrence proposal you don't have overflow as far as you don't loss range. So the natural place is to use it only with assignment.c) I considered a policy parameter of some sort to indicate how to handle overflows - they use an enum to select. But I rejected the idea for two reasons
i) more complexity with very little value.
ii) I couldn't see how to do it with something like
safe<int32> x;
safe<int16> y;
safe<int32> z;
z = x + y; // where would the policy parameter go?
It's exactly the same with safe<T>. EXCEPT the case where safe<int64> + safe<int64> where there is not large type to hold the intermediate result. That is, the overflow can occur before the assignment. In this case, it throws immediately. I struggled on how to avoid this - but solutions seemed more and more heavy weight.
integral<31,OV1> x;
integral<15,OV2> y;
integral<31,OV3> z;
z = x + y;
Check for overflow only when doing the assignment using the overflow OV3 policy.
I suppose this could be made to work - except for the note above.The user will need to use a number_cast conversion like in
The fact to add the overflow policy on the type makes the code less weight.
We can have also a base class for integral that has no overflow policy and forbids assignment that can loss information
basic_integral<31> x;
basic_integral<15> y;
basic_integral<31> z;
z = x + y; // compile file
z = number_cast<OV3>(x + y);
I wanted to avoid this so as to keep the "drop-in" replacement functionality. I want people take a program with bugs in it, change all their "int" to safe<int>, etc. run the program and trap errors.
But do the PO proposal or your Safe library allow to saturate on overflow, assert, ignore, ...?
The safe<T> throws an exception. Users would have to trap the exception if they want to handle it in some specific way. I considered alternatives, but I felt that if something traps it's almost always an unrecoverable error - ie a programming error
On 11/23/12, Fernando Cacciola <fernando...@gmail.com> wrote:
> On Nov 23, 2012 <robertmac...@gmail.com> wrote:> > > <http://en.wikipedia.org/wiki/Fixed-point_arithmetic> library.
> > On Thursday, November 22, 2012 6:10:24 PM UTC-8, Ben Craig wrote:
> > > That would likely turn into a fixed-point arithmetic
>The committee has a Study Group 6 addressing issues of number
> I would rather say that a fixed-point library, as well as a
> rationals library, would be alongside (or maybe one level above)
> safe-int, as opposed to a generalization of it.
representation. One of the tasks is figuring out the logical
relationships and implementation relationships between various
number types.
We welcome contributions.
> Both fixed_point<T> and rational<T> are useful as long as theThe study group has reached a tentative conclusion that a rational
> values do not overflow. However, and this is particularly true in
> the case of rationals, there are several application domains where
> computations can easily, and often, overflow. In these cases, one
> must use a big_int (unlimited precision integer) as T (whether for
> a fixed_point or rational) But that is pessimistically inefficient,
> so one would consider a non-template "exact" numeric type which
> would have a dynamic internal representation of rational<T>
> but which would automatically switch from T to 2T (meaning a two
> times bigger integer), until it reaches big_int.
should be based either on a bigint, or do rounding when the
representation is not sufficient for the true result. The former
would be useful in computational geometry. The latter would be
useful in music.
Fixed point is useful even in the presence of overflow, but the
method for dealing with the overflow may vary depending on the
application. In these cases, I think it generally best to give
program the tools they need, rather than pick a single solution.
(Plain unsigned ints picked one solution, and it is inappropriate
in a number of cases.)
On Mon, Nov 26, 2012 at 1:59 PM, Ben Craig <ben....@gmail.com> wrote:
Some large use cases for SafeInt are computing sizes for buffers and array indices.
Hmmm.
Can you elaborate how is SafeInt safe when it comes to an array index in the case of an array of runtime size? Which I believe is, by far, the most common type of array (buffer, container, etc)
For *that* purpose I think the proper tool is an integer with runtime boundaries (a number of these have been implemented and proposed to, for instance, Boost)
In these situations, you probably want to throw an exception, or terminate the program.
This s off-topic but IMO you never ever want to terminate a program when you use a language that supports exceptions, like C++
Saturation, assertions in debug mode, and ignoring aren't very good options from a security standpoint.Maybe. It depends on how you define security.
But for sure those are good options in several application domains, and a standard C++ library facility should consider them all (as much as possible)
Without a facility like SafeInt, a developer would likely write (buggy) code like this:size_t index = user_controlled_x+user_controlled_y;
if(index > array_size)
throw std::range_error("");
With SafeInt, you get something like this:
SafeInt<size_t> index = SafeInt<uint32_t>(user_controlled_x)+SafeInt<uint32_t>(user_controlled_y);
if(index > array_size)
throw std::range_error("");
On Monday, November 26, 2012 3:32:09 PM UTC-8, viboes wrote:Le 23/11/12 18:10, robertmac...@gmail.com a écrit :safe and integers could be orthogonal, but I don't see how a safe<T> class could provide safety to rational as overflow is avoided by using gcd while doing the addition of two rationals and many other tricks.
then safe<rational> wouldn't throw on overflow - because it can't happen.
This s off-topic but IMO you never ever want to terminate a program when you use a language that supports exceptions, like C++
On Tue, Nov 27, 2012 at 7:32 AM, Fernando Cacciola <fernando...@gmail.com> wrote:
On Mon, Nov 26, 2012 at 1:59 PM, Ben Craig <ben....@gmail.com> wrote:
Some large use cases for SafeInt are computing sizes for buffers and array indices.
Hmmm.
Can you elaborate how is SafeInt safe when it comes to an array index in the case of an array of runtime size? Which I believe is, by far, the most common type of array (buffer, container, etc)
For *that* purpose I think the proper tool is an integer with runtime boundaries (a number of these have been implemented and proposed to, for instance, Boost)
Without a facility like SafeInt, a developer would likely write (buggy) code like this:
size_t index = user_controlled_x+user_controlled_y;
if(index > array_size)
throw std::range_error("");
With SafeInt, you get something like this:
SafeInt<size_t> index = SafeInt<uint32_t>(user_controlled_x)+SafeInt<uint32_t>(user_controlled_y);
if(index > array_size)
throw std::range_error("");
SafeInt doesn't do everything for you, but it gets the difficult checks out of the way, and lets the programmer handle the easy, program specific checks. A more fully range checked class might be appropriate, but there isn't as much field experience with that.
In these situations, you probably want to throw an exception, or terminate the program.
This s off-topic but IMO you never ever want to terminate a program when you use a language that supports exceptions, like C++
If you hit undefined behavior, you should probably terminate the program as quickly as possible. This is the approach that "stack canary" implementations take. However, since we are talking about C++ spec work here, we should probably only talk about things with defined behavior. For those, I agree with you that exceptions are the better approach.
Saturation, assertions in debug mode, and ignoring aren't very good options from a security standpoint.Maybe. It depends on how you define security.
But for sure those are good options in several application domains, and a standard C++ library facility should consider them all (as much as possible)
Let's take it from a code review standpoint. If SafeInt (or some alternative) is used, then any place where C++ "a+b" is not the same as arithmetic "a+b", you should get an exception, or there should be something explicit in the code indicating that a different policy is required at that point. Maybe the "drop-in" stuff only allows exceptions, but saturation and modulo arithmetic get free functions? "short myVal = saturation_cast<short>(x, 0, 100);"
On Monday, November 26, 2012 3:32:09 PM UTC-8, viboes wrote:Le 23/11/12 18:10, robertmac...@gmail.com a écrit :safe and integers could be orthogonal, but I don't see how a safe<T> class could provide safety to rational as overflow is avoided by using gcd while doing the addition of two rationals and many other tricks.
then safe<rational> wouldn't throw on overflow - because it can't happen.
Same with save<multi-precision>. But currently safe<T> throws on at attempt to divide by zero. So the concept is easily extended to other numeric types. I can easily imagine a safe<float> and safe<double> which could overflow, underflow and device by zero. Basically safe<T> would throw anytime and operation on T doesn't yield the expected mathematical result.
On Nov 27, 2012, at 9:33 AM, Olaf van der Spek <olafv...@gmail.com> wrote:On Tuesday, November 27, 2012 2:33:03 PM UTC+1, Fernando Cacciola wrote:This s off-topic but IMO you never ever want to terminate a program when you use a language that supports exceptions, like C++Why not? The difference between aborting and throwing is basically whether you can catch and continue (safely).For the majority of apps aborting is fine for certain errors and exceptions aren't without cost and can't be used everywhere.The key is "for the majority of apps" is the key.
This is a decision that needs to be made by the app developer, not the library developer.
On Mon, Nov 26, 2012 at 2:45 PM, <robertmac...@gmail.com> wrote:
On Sunday, November 25, 2012 11:17:04 PM UTC-8, viboes wrote:
With the Lawrence proposal you don't have overflow as far as you don't loss range. So the natural place is to use it only with assignment.c) I considered a policy parameter of some sort to indicate how to handle overflows - they use an enum to select. But I rejected the idea for two reasons
i) more complexity with very little value.
ii) I couldn't see how to do it with something like
safe<int32> x;
safe<int16> y;
safe<int32> z;
z = x + y; // where would the policy parameter go?
It's exactly the same with safe<T>. EXCEPT the case where safe<int64> + safe<int64> where there is not large type to hold the intermediate result. That is, the overflow can occur before the assignment. In this case, it throws immediately. I struggled on how to avoid this - but solutions seemed more and more heavy weight.
Hmm, how isn't is as simple as using a software-based int128 number type?
Imagine you have a program and you get a division by zero exception . What do you do
You change the program so that, instead of attempting a computation that cannot be performed, it determines the case and handle the "exceptional case" manually in a different way.
You could just let the exception abort the current execution block but that's NOT the way division by zero is handled.
By analogy to the div-by-zero case, and IME (and I do have experience writing algorithms that need handle overflow, both integer and floating-point), you also want to detect the case before-hand so you can do something else.
Simply changing a program so that instead of UB it throws is a step ahead, but it's a too general solution that surely works for general application code.
But if you are writing a numerical algorithm OTOH you need, for example, to have a clean and simple way to handle the overflow and do something else *without* aborting the computation.
But do the PO proposal or your Safe library allow to saturate on overflow, assert, ignore, ...?
The safe<T> throws an exception. Users would have to trap the exception if they want to handle it in some specific way. I considered alternatives, but I felt that if something traps it's almost always an unrecoverable error - ie a programming error
Wait.
Numerical overflow is *rarely* a programming error. It's the consequence of finite precision when crunching numbers.
On Tue, Nov 27, 2012 at 7:32 AM, Fernando Cacciola <fernando...@gmail.com> wrote:On Mon, Nov 26, 2012 at 1:59 PM, Ben Craig <ben....@gmail.com> wrote:
Some large use cases for SafeInt are computing sizes for buffers and array indices.
Let's take it from a code review standpoint. If SafeInt (or some alternative) is used, then any place where C++ "a+b" is not the same as arithmetic "a+b", you should get an exception,
Le 27/11/12 00:28, Lawrence Crowl a �crit :
> On 11/26/12, robertmac...@gmail.com
I don't know if your SafeInt/safe<T> prevents from assignments from
signed values to unsigned types, but this could also be useful.
-- Vicente
I would most definitely like to have a standardized interface to handle numerical overflow.
I do however have some reservations on doing it by means of an integer wrapper.
First, I think we need such "safe numeric operations" to handle floating point values just as well. While the mechanisms are different, the requirements are exactly the same (and floating-point does overflow no matter how big their range is). In fact, I think we need it for arbitrary numeric types, including rationals, complex, user-defined extended precision, interval, etc... (and consistently defined for even unlimited precision numeric types, even if the overflow test always yields false)
Second, I wonder if a wrapper is the best interface. Since this is a "security" utility, it needs to be consistently applied *all over* the relevant source code, otherwise it is not really effective
A lot, if not most, critical and complex numeric code (where this is mostly needed) depends on third-party libraries, and in the numerical community there is a huge base of reusable code that is (yet) not sufficiently generic to let you get away with simple using a wrapper type at the highest level.
In other words, you might use SafeInt in your own code but it is not easy to have third-party code use it as well.
Since the overflow is always a side effect of operations, and operations are expressed in C++ via operators (and functions), I wonder if the library shouldn't be in a form of "safe operators and functions" as opposed to "safe numeric type".
IIUC, the current operator overloading mechanism should allow something like the following:
#include <safe_numeric_operations>
// This "activates" the overflow detection on any supported type, including builtin types
using std::safe_numeric_operations ;
void test()
{
try
{
int a = std::numeric_limits<int>::max();
// std::safe_numeric_operations::operator + (int,int) being called here
int r = a + a ;
}
catch ( std::bad_numeric_conversion ) { }
}On Wed, Nov 21, 2012 at 11:25 PM, <robertmac...@gmail.com> wrote:
On Wednesday, November 7, 2012 6:59:50 PM UTC-8, Ben Craig wrote:SafeInt information can be found here. ...--
I also found myself interested in this code. When I got around to looking at it I felt it could be improved. Being a "booster" I wanted to leverage on Boost stuff to make a simpler implementation. The result of my efforts can be found at http://rrsd.com/blincubator.com/bi_library/safe-numerics/
Main differences are:
a) follows Boost/C++ library conventions as to naming, formating etc.
b) Is many many lines shorter due to the usage of more meta-programming technique in the implementation.
c) Includes an exhaustive set of tests including all combinations of corner cases - generated with boost pre-processor library.
d) defines concepts for "numeric" types and includes concept checking via the boost concept checking library.
e) includes documentation more in line with currently recommended "formal" style.
Robert Ramey
Don't forget the essential C++ maxim: pay only for what you use.
There is a lot of code that's just fine with non-secure integers. They no need to have overflow checks and such every time time they loop from 0 to 240. They aren't going to overflow their loop counter. There are many such occurrences in a code-base where the structure and nature of the code means that overflow is just not a realistic possibility.
This means that whatever we adopt has to be an opt-in mechanism, not an opt-out.
OK, I agree with your observations about silently overloading operators.
OTOH, I still think there is something to be considered in the fact that it is the operations that overflow (and other unwanted side effects), not the numbers themselves, so I would propose a layer of overloaded functions, that can be applied on any type, including built-in types, and on top of that a higher-level wrapper (or wrappers like in Robert's library)
The advantage of doing so, following your own observations, is that in most numeric code, you don't really need to check for all operations, so a performance critical algorithm might not be able to just use the wrapper as a drop-in replacement for int, yet it could be made secure by adding checked operations where specifically necessary.
Having said all that, I would like to at least mention the fact that there is a largest picture to consider here, where integer overflow is just one case. I'm referring to the domain of numerical robustness in general, and while a comprehensive work on that is way out of the scope of a relatively simple proposal like this one, the big picture should still be constantly present and considered.
From the more general point of view of numerical robustness, throwing an exception when a numerical computation could not be performed (such as in the case of integer overflow) is the most basic building block, but it is still too low-level. There is a lot of work in the research community about how to truly handle numerical limitations, as opposed to not handling in order (which is what a safe-int does). Unfortunately, most of that work is specific to an application domain and a given numeric type or types, so it's hard to extrapolate general idioms and utilities. However, there are two basic elements that are usually found in most of them:
First there is the general mechanism of using a proper type to perform an operation. For intance, an int64 to operate two int32. This is the mechanism used by SafeInt and Robert's code, but it's actually used in a lot of places dealing with integer numerics. So, we should standardized that, such that it is generally available to any programmer to do something like:
auto r = (secure)a + b;
here (secure) would bootstrap an operator+ which selects the proper return type to guarantee the addition does not overflow (or you could just have secure_add(a,b) or whatever)
Second, there is the general requirement to test whether the result of operations are reliable, beyond just overflow.
One idiom that has emerged in recent years and which I found intuitive enough to be general is the concept of "certified arithmetic" (usually implemented with interval floating-point numbers). The idea is that, on the one hand, the result of operations carry a certificate (explicit or not) to indicate if it succeeded or not, and on the other hand, triboolean logic is used along, made general and easy. In terms of safe_int this would mean that instead of throwing, the result is just flaged as overflow, much like a floating-point saturates to +- INF, but, when you try to compare, the answer is "maybe" (and if you want to convert it to plain int it throws)
The general form of the idiom would be something like this:
auto e = ( a + b == c + d ) ;
if ( certainy(e) )
{
}
else if ( certainly_not(e ) )
{
}
else
{
// can't tell. It not possible to compare the numbers
}
Here, if a,b,c,d are integers numbers, and either of the additions overflow, the code flows to the last else, and without even any exception being thrown.
The trick is that operator == returns a tribool (or it's generalization uncertain<bool>)
Best
On Thu, Nov 22, 2012 at 1:58 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Thursday, November 22, 2012 5:45:42 AM UTC-8, Fernando Cacciola wrote:I would most definitely like to have a standardized interface to handle numerical overflow.
I do however have some reservations on doing it by means of an integer wrapper.
First, I think we need such "safe numeric operations" to handle floating point values just as well. While the mechanisms are different, the requirements are exactly the same (and floating-point does overflow no matter how big their range is). In fact, I think we need it for arbitrary numeric types, including rationals, complex, user-defined extended precision, interval, etc... (and consistently defined for even unlimited precision numeric types, even if the overflow test always yields false)
Second, I wonder if a wrapper is the best interface. Since this is a "security" utility, it needs to be consistently applied *all over* the relevant source code, otherwise it is not really effective
Don't forget the essential C++ maxim: pay only for what you use.
There is a lot of code that's just fine with non-secure integers. They no need to have overflow checks and such every time time they loop from 0 to 240. They aren't going to overflow their loop counter. There are many such occurrences in a code-base where the structure and nature of the code means that overflow is just not a realistic possibility.
This means that whatever we adopt has to be an opt-in mechanism, not an opt-out.
A lot, if not most, critical and complex numeric code (where this is mostly needed) depends on third-party libraries, and in the numerical community there is a huge base of reusable code that is (yet) not sufficiently generic to let you get away with simple using a wrapper type at the highest level.
In other words, you might use SafeInt in your own code but it is not easy to have third-party code use it as well.
Since the overflow is always a side effect of operations, and operations are expressed in C++ via operators (and functions), I wonder if the library shouldn't be in a form of "safe operators and functions" as opposed to "safe numeric type".
IIUC, the current operator overloading mechanism should allow something like the following:
#include <safe_numeric_operations>
// This "activates" the overflow detection on any supported type, including builtin types
using std::safe_numeric_operations ;
void test()
{
try
{
int a = std::numeric_limits<int>::max();
// std::safe_numeric_operations::operator + (int,int) being called here
int r = a + a ;
}
catch ( std::bad_numeric_conversion ) { }
}
Ignoring the fact that a recompile would suddenly cause massive amounts of code to potentially throw std::bad_numeric_conversion without the knowledge of that code's owners (ie: we can't do that), there's also the practical issue that this won't magically affect any C code that people often use.
But even more importantly, I'm pretty sure you can't overload operator+(int, int). Besides being practically infeasible by creating the possibility of exceptions in potentially exception-unsafe code, it's simply not possible.
On Wed, Nov 21, 2012 at 11:25 PM, <robertmac...@gmail.com> wrote:
On Wednesday, November 7, 2012 6:59:50 PM UTC-8, Ben Craig wrote:SafeInt information can be found here. ...
I also found myself interested in this code. When I got around to looking at it I felt it could be improved. Being a "booster" I wanted to leverage on Boost stuff to make a simpler implementation. The result of my efforts can be found at http://rrsd.com/blincubator.com/bi_library/safe-numerics/
Main differences are:
a) follows Boost/C++ library conventions as to naming, formating etc.
b) Is many many lines shorter due to the usage of more meta-programming technique in the implementation.
c) Includes an exhaustive set of tests including all combinations of corner cases - generated with boost pre-processor library.
d) defines concepts for "numeric" types and includes concept checking via the boost concept checking library.
e) includes documentation more in line with currently recommended "formal" style.
Robert Ramey
--
--
On Thu, Nov 22, 2012 at 5:57 PM, <robertmac...@gmail.com> wrote:
For instance, both SafeInt's and your code *requires* a primitive operation that returns the result in a larger type such that it is known not to overflow.
Then I'm saying that, since the bottom layer is a requirement for these libraries, and it's also useful--and used--in several other numerical techniques, it should be standardized as such (and not hidden as an implementation detail as it proposed)
I'm also saying that the bigger picture could critically affect design decision even within the scope proposed. I gave the example of certified arithmetic because that would suggest that a given safe_int should not necessarily just throw right up front, but it could instead flag the overflow and throw only when necessary, such as when casting to int. This is important because if you are going to compare the overflowed result, you can just return "maybe" from the comparison without throwing an exception. This not only allows the client code to be *elegantly* explicit about the way overflowed results affect the computation results, but it also allows the library to be used in contexts where exceptions are to be avoided when possible.
I haven't checked your code in this corner case, but SafeInt uses a form of 128 bits type (when configured as such)
So what's the signature of operator+ ( safe<int64>, safe<int64> ) ?
I still prefer something more general that would allow me to do:
int16 a,b ;
int32 x = safe_add(a,b);
and entirely bypass the wrapper though, specially as a std utility (which is what we are discussing here)
safe<int16> a;
safe<int32> b;
auto x = a + b; // what is the type of x?
should be safe<int64>
auto x = b + a; // is the type of y the same as the type of x?
yes
Questions like these made me decide to divide the task as you suggested
a) do the calculation resulting in a type which can hold the true result
b) on assignment or cast (safe_cast ...) trap the attempt to save a value which loses information.
So we are almost on the same page.
We differ in how to propose two two layers to the std
I also believe that safe-numerics is very good. I think the problem of integers that grows at runtime has to be done in a different library. Also, there is the independent problem of creating efficient accumulators.
On Thursday, November 22, 2012 1:33:38 PM UTC-8, Fernando Cacciola wrote:On Thu, Nov 22, 2012 at 5:57 PM, <robertmac...@gmail.com> wrote:
For instance, both SafeInt's and your code *requires* a primitive operation that returns the result in a larger type such that it is known not to overflow.
No, not true - for type A, B, operator op:SafeInt<A> op B returns a SafeInt<A>
So it is still the same size. Which has a difficulty associated with it, namely:unsigned short a = 2;unsigned short b = 3;int c = -3;(a + b + c) using standard behavior emits an int with a value of 2 as will:(a + (b + SafeInt<int>(c)))But:(a + (SafeInt<unsigned short>(b) + c))Will throw. But this is actually an unusual case, so in practice you will not see it much, and you can just document the behavior. I have explored just increasing the size of the type, but it got to be a very horrible problem and threatened to destroy perf.
Then I'm saying that, since the bottom layer is a requirement for these libraries, and it's also useful--and used--in several other numerical techniques, it should be standardized as such (and not hidden as an implementation detail as it proposed)
To some extent, I agree. Though there is support for this in SafeInt - there are templatized functions that will answer the general question of whether an addition (for example) between two arbitrary types is going to have a numerically valid result.
I'm also saying that the bigger picture could critically affect design decision even within the scope proposed. I gave the example of certified arithmetic because that would suggest that a given safe_int should not necessarily just throw right up front, but it could instead flag the overflow and throw only when necessary, such as when casting to int. This is important because if you are going to compare the overflowed result, you can just return "maybe" from the comparison without throwing an exception. This not only allows the client code to be *elegantly* explicit about the way overflowed results affect the computation results, but it also allows the library to be used in contexts where exceptions are to be avoided when possible.
We tried this, and it did not go well. Great idea, and we even implemented it and put it into a fair bit of code, but we've since abandoned the approach and removed uses of it. The best solution we've found so far is the templatized, non-throwing functions.
On Mon, Nov 26, 2012 at 1:59 PM, Ben Craig <ben....@gmail.com> wrote:Some large use cases for SafeInt are computing sizes for buffers and array indices.
Hmmm.
Can you elaborate how is SafeInt safe when it comes to an array index in the case of an array of runtime size? Which I believe is, by far, the most common type of array (buffer, container, etc)
For *that* purpose I think the proper tool is an integer with runtime boundaries (a number of these have been implemented and proposed to, for instance, Boost)
In these situations, you probably want to throw an exception, or terminate the program.
This s off-topic but IMO you never ever want to terminate a program when you use a language that supports exceptions, like C++
I haven't checked your code in this corner case, but SafeInt uses a form of 128 bits type (when configured as such)No, it does not support 128-bit integers.
Once there is a compiler that will give you those, then it could be extended. But the template programming accounts for all 64 possible combination of signed and size that we currently have. If you introduce a fifth, then we have a 100 node table, and some of the current edge cases get easier, to be replaced by edge cases because now we don't have a 256 bit type.So what's the signature of operator+ ( safe<int64>, safe<int64> ) ?SafeInt<int64> operator+( SafeInt<int64>, SafeInt<64>)
I still prefer something more general that would allow me to do:
int16 a,b ;
int32 x = safe_add(a,b);
and entirely bypass the wrapper though, specially as a std utility (which is what we are discussing here)
Yes, that's included in the class, because there are times you want to test things and not have to deal with exceptions at all. Though it has to be done (in general) as:int16 tmp;if(SafeAdd(a, b, tmp) && SafeCast(tmp, x))
Because you have two operations that can possibly make trouble, though in this case, the second will compile away, because it is always safe.safe<int16> a;
safe<int32> b;
auto x = a + b; // what is the type of x?
should be safe<int64>No, it is defined, and depends on operator precedence. This would invoke:SafeInt<int16>::operator+(SafeInt<int32>)which returns a SafeInt<int16>
If you wanted a SafeInt<int64> out of it, then you would write:auto x = SafeInt<int64>(a) + b;Else, we quickly get to all operations working on the largest possible type, and perf would suffer badly.
auto x = b + a; // is the type of y the same as the type of x?
yesActually, no.
Questions like these made me decide to divide the task as you suggested
a) do the calculation resulting in a type which can hold the true result
b) on assignment or cast (safe_cast ...) trap the attempt to save a value which loses information.
So we are almost on the same page.
We differ in how to propose two two layers to the stdThat approach will end up being infeasible due to perf issues.
On Tuesday, November 27, 2012 5:33:03 AM UTC-8, Fernando Cacciola wrote:On Mon, Nov 26, 2012 at 1:59 PM, Ben Craig <ben....@gmail.com> wrote:Some large use cases for SafeInt are computing sizes for buffers and array indices.
Hmmm.
Can you elaborate how is SafeInt safe when it comes to an array index in the case of an array of runtime size? Which I believe is, by far, the most common type of array (buffer, container, etc)
We can ensure that the array index is not the result of a wrapping operation. If you want to ensure that the index is within the bounds of the array, then I would construct the array from std::vector, and then rely on either at() or [] to know what is a suitable range. Works better if someone comes along and resizes the array while your other variable is not updated.
For *that* purpose I think the proper tool is an integer with runtime boundaries (a number of these have been implemented and proposed to, for instance, Boost)Disagree - then you have to keep the boundaries in sync with the buffer size, which could grow or shrink, and the code may not think to notify the bounds. Especially given that the most efficient way to implement the bounds would be at compile time, and then you are stuck.
In these situations, you probably want to throw an exception, or terminate the program.
This s off-topic but IMO you never ever want to terminate a program when you use a language that supports exceptions, like C++
Also disagree. You may be in a state where you cannot cleanly unwind. Clearly, if you have ideal C++ code, then you can, but I have not seen much ideal code in the wild. You may want to just consider it the same sort of problem as a bad pointer dereference where you just take the app down.
This is why the exception policy for SafeInt is user-defined, and you could have your own custom exceptions, standard exceptions, fail fast to exit, or OS-specific error handlers.
Hmm, how isn't is as simple as using a software-based int128 number type?
I'm tempted to respond, but I think this thread is not the correct place. (and I'm not sure where would be)
Anyway, let me just rephrase that: IMO, the *end user* never ever wants you the programmer to structure the program in such a way that the *entire application* must abort as opposed to the current "task" (task from the end user POV )
I was referring to the implementation.
In the code (I did actually look), you do perform "the primitive operation in a type that has twice the precision" of the operands.
To some extent, I agree. Though there is support for this in SafeInt - there are templatized functions that will answer the general question of whether an addition (for example) between two arbitrary types is going to have a numerically valid result.
I know. I saw them in the code.
And I'm saying that such functions (or whatever, it doesn't really matter what it is exactly, to the effect of my point) are a bottom layer of SafeInt but they would be equally useful as a bottom layer for several other integer related utility classes.
Then, I propose to standarize *them* as well.
We tried this, and it did not go well. Great idea, and we even implemented it and put it into a fair bit of code, but we've since abandoned the approach and removed uses of it. The best solution we've found so far is the templatized, non-throwing functions.
Fair enough.
OTOH, I believe it is reasonable to ask for a rationale on why it did not work, and to allow us to have some discussion about it, which would not ignore your experience but simply try to consider different contexts which might have not been taken into account when you first consider it.
For example, you said "Turned out to be unusable, since a simple check like if (SafeCheckedInt<int>(x) < 0) might throw"
Why would that throw? I don't get it
On Tuesday, November 27, 2012 9:43:28 AM UTC-8, Fernando Cacciola wrote:
I'm tempted to respond, but I think this thread is not the correct place. (and I'm not sure where would be)
Anyway, let me just rephrase that: IMO, the *end user* never ever wants you the programmer to structure the program in such a way that the *entire application* must abort as opposed to the current "task" (task from the end user POV )
Somewhat off-topic, and very much platform-centric, but actually we do want that under a number of conditions. On Windows, if the app aborts unexpectedly, and you have opted in to the crash reporting feature, we then get a bug with a stack trace and possibly a dump. If we see many of them, we'll fix it. If you just catch the exception, then maybe we find out, maybe we don't, and the bug is more likely to stay there. Also some applications, Excel being one example, feel like the worst thing we can ever do is to corrupt the user's data. Crashing is bad, but not the worst thing we can possibly do. So if Excel gets off in the weeds, it would rather fall over than corrupt your data.
I did once have the X Window library do that to me (call exit()), and it was seriously annoying. So what you want is for the developer to decide what happens, which is what SafeInt does.
Not on the interface.
But the code uses _imul128 (or some such) for a certain operation over certain argument and given certain configuration options.
So the implementation is using an operation on 128 integers.
But that question was addressed to Robert. His library *sifnificantly* differs from yours in this regard:
In Robert's library, safe<T> * safe<T> -> safe<2T>
And I believe *that* is the correct choice (SafeInt<> does the opposite so I believe that's not the correct choice)
Given that pattern, I ask what's the case when you already reached int64.
Else, we quickly get to all operations working on the largest possible type, and perf would suffer badly.OK.
Then that would be your argument for NOT automatically promoting to the next bigger type to avoid overflow.
Right off the top of my head I disagree with your conclusion (I agree with the perf observation but not the conclusion you drew from that), but this is one thing that should be discussed as its own topic.
Maybe one of us is misreading the question.
I read the question as: Should ( a + b ) and ( b + a) return the same type?
I say yes.
That approach will end up being infeasible due to perf issues.I would need you to elaborate on that.
I don't know if your SafeInt/safe<T> prevents from assignments from
signed values to unsigned types, but this could also be useful.
If you are going to derive something from my work, please follow the license. It doesn't ask much, just that you keep attribution and the license. If you are going to completely rewrite something based on my work, then you may not need a license (I Am Not A Lawyer, one should be asked), but attribution would be nice.
On Tuesday, November 27, 2012 6:32:27 PM UTC-8, Fernando Cacciola wrote:But that question was addressed to Robert. His library *sifnificantly* differs from yours in this regard:
In Robert's library, safe<T> * safe<T> -> safe<2T>
And I believe *that* is the correct choice (SafeInt<> does the opposite so I believe that's not the correct choice)
Given that pattern, I ask what's the case when you already reached int64.Ah - from the standpoint of keeping things from overflowing, I would tend to agree. From the standpoint of being able to use it in production code because I have not caused a perf problem, I disagree.
I would note that there could be a middle ground, though it may be tricky to get to compile. You could say that for SafeInt of types smaller than int, then the return would be SafeInt<int>
What is undefined is the case of SafeInt<T> op SafeInt<U>. I don't know what type you want back, and compilers tend to just throw up their hands and refuse to compile. You could have a large table of all the types, but that's ugly. So I then ask you to make up your mind and do for example (U)SafeInt<T> op SafeInt<U>.
On Tuesday, November 27, 2012 6:32:27 PM UTC-8, Fernando Cacciola wrote:Not on the interface.
But the code uses _imul128 (or some such) for a certain operation over certain argument and given certain configuration options.
So the implementation is using an operation on 128 integers.
That's a Microsoft-specific intrinsic (AFAIK). If it turns out to be portable, I'd use it for other compilers, too. FWIW, it is not available on 32-bit Microsoft compilers.But that question was addressed to Robert. His library *sifnificantly* differs from yours in this regard:
In Robert's library, safe<T> * safe<T> -> safe<2T>
And I believe *that* is the correct choice (SafeInt<> does the opposite so I believe that's not the correct choice)
Given that pattern, I ask what's the case when you already reached int64.Ah - from the standpoint of keeping things from overflowing, I would tend to agree. From the standpoint of being able to use it in production code because I have not caused a perf problem, I disagree.I would note that there could be a middle ground, though it may be tricky to get to compile. You could say that for SafeInt of types smaller than int, then the return would be SafeInt<int>
>> And I would use a totally different *interface* but that's minor. What matters IMO is the existence of these utilities, which I believe should not be left hidden in the implementation.Sure, complete agreement, and there are aspects of the existing interface I am not completely happy with.
Else, we quickly get to all operations working on the largest possible type, and perf would suffer badly.OK.
Then that would be your argument for NOT automatically promoting to the next bigger type to avoid overflow.
Right off the top of my head I disagree with your conclusion (I agree with the perf observation but not the conclusion you drew from that), but this is one thing that should be discussed as its own topic.OK.Maybe one of us is misreading the question.
I read the question as: Should ( a + b ) and ( b + a) return the same type?
I say yes.This gets into some level of implementation detail in terms of simplifying assumptions. The class was significantly hard to develop, and evolved over some years. The bulk of it was done in about 6 months. A simplifying assumption is that some_int op SafeInt<other_int> always returns the SafeInt<other_int>. This is true no matter what the order - we can do a + safe_b or safe_b + a. All works and meets your requirement.What is undefined is the case of SafeInt<T> op SafeInt<U>. I don't know what type you want back, and compilers tend to just throw up their hands and refuse to compile. You could have a large table of all the types, but that's ugly. So I then ask you to make up your mind and do for example (U)SafeInt<T> op SafeInt<U>.
This is very interesting. When I started on this, I started with "save_range<min, max>" from which safe<int> is derived. This left open the possibility of using safe_range<0, 4> * safe_range<0,8> to return a safe_range<0, 12> which was very cool to me. But the TMP got just to complex and I scaled back my so that safe_range<0,4> is implemented as a safe<unsigned char> etc.
On Tuesday, November 27, 2012 6:19:32 PM UTC-8, Fernando Cacciola wrote:
I was referring to the implementation.
In the code (I did actually look), you do perform "the primitive operation in a type that has twice the precision" of the operands.Oh - OK. This is not always true, though. Addition can be checked without larger types, and sometimes I just leverage normal operator casting behavior - char op char does not go to short, but instead int. For multiplication, this is the case for 32-bit types.To some extent, I agree. Though there is support for this in SafeInt - there are templatized functions that will answer the general question of whether an addition (for example) between two arbitrary types is going to have a numerically valid result.
I know. I saw them in the code.
And I'm saying that such functions (or whatever, it doesn't really matter what it is exactly, to the effect of my point) are a bottom layer of SafeInt but they would be equally useful as a bottom layer for several other integer related utility classes.
Then, I propose to standarize *them* as well.Thanks, then yes, we agree on that. However, you would need both throwing and non-throwing semantics, as you see in the code. The throwing semantics can sometimes have a cleaner failure path.
We tried this, and it did not go well. Great idea, and we even implemented it and put it into a fair bit of code, but we've since abandoned the approach and removed uses of it. The best solution we've found so far is the templatized, non-throwing functions.
Fair enough.
OTOH, I believe it is reasonable to ask for a rationale on why it did not work, and to allow us to have some discussion about it, which would not ignore your experience but simply try to consider different contexts which might have not been taken into account when you first consider it.
For example, you said "Turned out to be unusable, since a simple check like if (SafeCheckedInt<int>(x) < 0) might throw"
Why would that throw? I don't get itIf the checked int had overflowed
, then we cannot answer the question of whether it is < 0, because it is undefined.
Only thing we can do is throw at that point.
This tends to cause serious problems when trying to use it in existing code.
I'm certainly willing to discuss the idea - one possibility is that our implementation or design was bad.
Some other approach might work. We originally liked the idea because a float can tell us if it has become an undefined value, seemed like this concept should transfer. Part of it was confusion because a SafeInt can never become invalid, and a comparison operation can never throw.
So introducing something that acts somewhat the same, but throws in places the other cannot was a mess.
On Wed, Nov 28, 2012 at 4:37 AM, <robertmac...@gmail.com> wrote:
This is very interesting. When I started on this, I started with "save_range<min, max>" from which safe<int> is derived. This left open the possibility of using safe_range<0, 4> * safe_range<0,8> to return a safe_range<0, 12> which was very cool to me. But the TMP got just to complex and I scaled back my so that safe_range<0,4> is implemented as a safe<unsigned char> etc.
OTOH, in our context: a std proposal, we can just leave that to the implementations (knowing that it could be complex, but *can* be done). There are far more difficult things in the new language definition, so much that haven't even been implemented yet.
SafeInt information can be found here. Basically, it is an open source library authored by security expert David LeBlanc of Microsoft. It is basically a "drop-in" replacement for integer types, and will throw an exception whenever integer overflows occur. I believe that getting this added to the standard would be a boon to C++ and secure code. It should be a relatively low effort addition considering the "proof-of-concept" is already widely used within Microsoft.
--
Ah - from the standpoint of keeping things from overflowing, I would tend to agree. From the standpoint of being able to use it in production code because I have not caused a perf problem, I disagree.
I'm not convinced that promoting the types to 64 bits is more expensive timewise than the alternatives. I'd have to see some real data on that.
I would note that there could be a middle ground, though it may be tricky to get to compile. You could say that for SafeInt of types smaller than int, then the return would be SafeInt<int>
This is very interesting. When I started on this, I started with "save_range<min, max>" from which safe<int> is derived. This left open the possibility of using safe_range<0, 4> * safe_range<0,8> to return a safe_range<0, 12> which was very cool to me. But the TMP got just to complex and I scaled back my so that safe_range<0,4> is implemented as a safe<unsigned char> etc.
On Wed, Nov 28, 2012 at 12:22 AM, <dvd_l...@yahoo.com> wrote:I agree that there are two separate goals. One is to avoid overflow by promoting to the next integer This would reach bigint in the end. The other is to turn overflow into well defined and *useful* behavior, but without any need to avoid it, just to deal with it.
From your use cases I can see that SafeInt is aimed at the later. The uses I have in mind are closer to the former.
So, IMO a proposal should contemplate both, and possibly, provide utilities to both problems.
What is undefined is the case of SafeInt<T> op SafeInt<U>. I don't know what type you want back, and compilers tend to just throw up their hands and refuse to compile. You could have a large table of all the types, but that's ugly. So I then ask you to make up your mind and do for example (U)SafeInt<T> op SafeInt<U>.
Integer types are ranked based on their ranges. That's used in integer promotion. IMO, it's perfectly valid and I would say simple to define similar promotion rules for SafeInt<T> op SafeInt<U> based on the operation.
On Wed, Nov 28, 2012 at 12:01 AM, <dvd_l...@yahoo.com> wrote:
Thanks, then yes, we agree on that. However, you would need both throwing and non-throwing semantics, as you see in the code. The throwing semantics can sometimes have a cleaner failure path.
IIUC, this is because you have failure policies.
If, OTOH, the library just consistently throw as the sole overflow handling mechanism, you won't need both.
I would (and probably will) argue that, a *standard library utility* should just consistently throw. Or else, something at a wider lever, across libraries, should be proposed.
I'm certainly willing to discuss the idea - one possibility is that our implementation or design was bad.
IME, most thoughtful implementations and designs are not bad, but can be improved as new use cases or updated requirements are discovered.
I use my own safeint extensively, and find it to be very useful in protecting from various kinds of overflow.
There is (in my opinion) one major design flaw in safeint, namely the operators like:
'operator int() const', which allow the safeint to be turned back into an int, for function calls. I prefer having a member function 'x.raw()', or new function 'checked_cast<int>(x)'. This does make code using safeint much more verbose, but means you cannot accidentally invoke unsafe behaviour, by turning back into non-safe types.
I feel if someone is going to the effort of using safeint, then they should care both when values are turned into safeint, and turned back. However, this goes get particularly annoying when doing things like indexing vectors, where you must keep writing 'v[x.raw()]' all the time.
On Tuesday, November 27, 2012 11:37:34 PM UTC-8, robertmac...@gmail.com wrote:
Ah - from the standpoint of keeping things from overflowing, I would tend to agree. From the standpoint of being able to use it in production code because I have not caused a perf problem, I disagree.
I'm not convinced that promoting the types to 64 bits is more expensive timewise than the alternatives. I'd have to see some real data on that.OK, so looking at the case of
OTOH, if we are operating on T then an implementation might very well not use an expensive 2T to test for overflow, in which case your analysis applies and it would more efficient to test for the overflow in a different way.
So, it seems to me that automatically promoting up to a certain implementation-defined type is both useful and efficient. Beyond that type, different application domains would benefit from one or other solution, thus like I said I would like a proposal to provide both, or at least leave a sensible path for a separate proposal to complete the offering.