Commit ba7aac9c8af072ee5503112531e14a2ed3b607cd

Authored by Philip Top
Committed by Henry Schreiner
1 parent eab92ed9

remove undefined bahavior (#290)

* change the checked_multiply function to not use undefined behavior to check for potential undefined behavior and wrapping.

* update the checked_multiply template to deal with mismatched sign in signed numbers and min val correctly.  This involved adding to templates to clear up warnings
include/CLI/Validators.hpp
1 1 #pragma once
2   -
3 2 // Distributed under the 3-Clause BSD License. See accompanying
4 3 // file LICENSE or https://github.com/CLIUtils/CLI11 for details.
5 4  
... ... @@ -9,6 +8,7 @@
9 8 #include <cmath>
10 9 #include <functional>
11 10 #include <iostream>
  11 +#include <limits>
12 12 #include <map>
13 13 #include <memory>
14 14 #include <string>
... ... @@ -512,17 +512,37 @@ auto search(const T &amp;set, const V &amp;val, const std::function&lt;V(V)&gt; &amp;filter_functi
512 512 return {(it != std::end(setref)), it};
513 513 }
514 514  
  515 +// the following suggestion was made by Nikita Ofitserov(@himikof)
  516 +// done in templates to prevent compiler warnings on negation of unsigned numbers
  517 +
  518 +/// Do a check for overflow on signed numbers
  519 +template <typename T>
  520 +inline typename std::enable_if<std::is_signed<T>::value, T>::type overflowCheck(const T &a, const T &b) {
  521 + if((a > 0) == (b > 0)) {
  522 + return ((std::numeric_limits<T>::max)() / (std::abs)(a) < (std::abs)(b));
  523 + } else {
  524 + return ((std::numeric_limits<T>::min)() / (std::abs)(a) > -(std::abs)(b));
  525 + }
  526 +}
  527 +/// Do a check for overflow on unsigned numbers
  528 +template <typename T>
  529 +inline typename std::enable_if<!std::is_signed<T>::value, T>::type overflowCheck(const T &a, const T &b) {
  530 + return ((std::numeric_limits<T>::max)() / a < b);
  531 +}
  532 +
515 533 /// Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise.
516 534 template <typename T> typename std::enable_if<std::is_integral<T>::value, bool>::type checked_multiply(T &a, T b) {
517   - if(a == 0 || b == 0) {
  535 + if(a == 0 || b == 0 || a == 1 || b == 1) {
518 536 a *= b;
519 537 return true;
520 538 }
521   - T c = a * b;
522   - if(c / a != b) {
  539 + if(a == (std::numeric_limits<T>::min)() || b == (std::numeric_limits<T>::min)()) {
523 540 return false;
524 541 }
525   - a = c;
  542 + if(overflowCheck(a, b)) {
  543 + return false;
  544 + }
  545 + a *= b;
526 546 return true;
527 547 }
528 548  
... ...
tests/HelpersTest.cpp
... ... @@ -423,10 +423,34 @@ TEST(CheckedMultiply, Int) {
423 423 ASSERT_FALSE(CLI::detail::checked_multiply(a, b));
424 424 ASSERT_EQ(a, std::numeric_limits<int>::min());
425 425  
  426 + b = std::numeric_limits<int>::min();
  427 + a = -1;
  428 + ASSERT_FALSE(CLI::detail::checked_multiply(a, b));
  429 + ASSERT_EQ(a, -1);
  430 +
426 431 a = std::numeric_limits<int>::min() / 100;
427 432 b = 99;
428 433 ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
429 434 ASSERT_EQ(a, std::numeric_limits<int>::min() / 100 * 99);
  435 +
  436 + a = std::numeric_limits<int>::min() / 100;
  437 + b = -101;
  438 + ASSERT_FALSE(CLI::detail::checked_multiply(a, b));
  439 + ASSERT_EQ(a, std::numeric_limits<int>::min() / 100);
  440 + a = 2;
  441 + b = std::numeric_limits<int>::min() / 2;
  442 + ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
  443 + a = std::numeric_limits<int>::min() / 2;
  444 + b = 2;
  445 + ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
  446 +
  447 + a = 4;
  448 + b = std::numeric_limits<int>::min() / 4;
  449 + ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
  450 +
  451 + a = 48;
  452 + b = std::numeric_limits<int>::min() / 48;
  453 + ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
430 454 }
431 455  
432 456 TEST(CheckedMultiply, SizeT) {
... ...