(How) do you handle possible integer overflows in C ++ code?

From time to time, especially when you make 64-bit assemblies of some code base, I notice that there are many cases when integer overflows are possible. The most common case is that I am doing something like this:

// Creates a QPixmap out of some block of data; this function comes from library A QPixmap createFromData( const char *data, unsigned int len ); const std::vector<char> buf = createScreenShot(); return createFromData( &buf[0], buf.size() ); // <-- warning here in 64bit builds 

The fact is that std::vector::size() beautifully returns size_t (which is 8 bytes in 64-bit assemblies), but the function accepts an unsigned int (which still remains only 4 bytes in 64-bit assemblies). Therefore, the compiler warns correctly.

If possible, I am trying to fix signatures in order to use the correct types in the first place. However, I often encounter this problem when combining functions from different libraries that I cannot change. Unfortunately, I often resort to some reasoning like “Okay, no one will ever take a screenshot generating more than 4 GB of data, so why bother” and just change the code to make

 return createFromData( &buf[0], static_cast<unsigned int>( buf.size() ) ); 

So, the compiler closes. However, this seems really evil. Thus, I am considering the possibility of a kind of runtime statement that at least gives a good error in the debug assembly, as in:

 assert( buf.size() < std::numeric_limits<unsigned int>::maximum() ); 

This is already a bit nicer, but I wonder: how do you deal with such a problem, that is: whole overflows that are "almost" impossible (in practice). I assume that this means that they do not occur for you, they do not occur for QA - but they explode in the face of the client.

+6
source share
5 answers

If you cannot fix types (because you cannot break library compatibility) and you are “sure” that the size will never be so large, you can use boost::numeric_cast instead of static_cast . This will throw an exception if the value is too large.

Of course, the surrounding code should then do something vaguely reasonable with the exception that since this condition is “not expected to happen,” it could mean a clean shutdown. Still better than continuing with the wrong size.

+4
source

The decision depends on the context. In some cases, you know where the data comes from and can eliminate overflow: int , which initializes to 0 and increases once per second, for example, does not overflow at any time during the whole life of the machine. In such cases, you simply convert (or allow the implicit conversion to do your own thing) and don’t worry about it.

Another type of case is quite similar: in your case, for example, it is probably not reasonable for the screen to have more data that can be represented by int , so the conversion is also safe. Provided the data really comes from the screen; in such cases, the usual procedure is to check the input data, ensure that your restrictions are met downstream, and then do not check again.

Finally, if you do not have real control over where the data comes from and cannot be checked at the entrance (at least not for your restrictions downstream), you are stuck with using some kind of conversion check, confirming immediately at the conversion point .

+2
source

If you push a 64-bit overflow number to a 32-bit library, you open the pandora - undefined window.

Throw an exception. Since exceptions can generally be spring anywhere, you must have the appropriate code to catch it. Given this, you can use it.

The error messages are unpleasant, but they are better than the undefined behavior.

+1
source

Such scenarios can be carried out in one of four ways or using a combination of them:

  • use the right types
  • use static statements
  • use runtime statements
  • ignore until damaged

It's usually best to use the right types right until your code gets ugly, and then roll in static statements. Static statements are much better than runtime statements for this very purpose.

Finally, when static statements will not work (for example, in your example), you use runtime statements - yes, they fall into the faces of clients, but at least your program behaves predictably. Yes, customers don’t like statements - they start to panic (“we have a mistake!” In all the caches), but without approval, the program is most likely mistaken and in no way can easily diagnose the problem.

0
source

One thing only occurred to me: since I need some kind of runtime check (regardless of whether the value, for example buf.size() exceeds the unsigned int range, only at runtime), but I don't want to have a million assert() calls around the world, I could do something like

 template <typename T, typename U> T integer_cast( U v ) { assert( v < std::numeric_limits<T>::maximum() ); return static_cast<T>( v ); } 

So, I would at least make this statement centralized, and

 return createFromData( &buf[0], integer_cast<unsigned int>( buf.size() ) ); 

A little better. Maybe I would rather prefer an exception (this is really unusual!) Instead of assert 'ing, to give the caller the opportunity to handle the situation gracefully, discarding the previous work and issuing a diagnostic output or the like.

0
source

Source: https://habr.com/ru/post/893563/


All Articles