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 #pragma once 1 #pragma once
2 -  
3 // Distributed under the 3-Clause BSD License. See accompanying 2 // Distributed under the 3-Clause BSD License. See accompanying
4 // file LICENSE or https://github.com/CLIUtils/CLI11 for details. 3 // file LICENSE or https://github.com/CLIUtils/CLI11 for details.
5 4
@@ -9,6 +8,7 @@ @@ -9,6 +8,7 @@
9 #include <cmath> 8 #include <cmath>
10 #include <functional> 9 #include <functional>
11 #include <iostream> 10 #include <iostream>
  11 +#include <limits>
12 #include <map> 12 #include <map>
13 #include <memory> 13 #include <memory>
14 #include <string> 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,17 +512,37 @@ auto search(const T &amp;set, const V &amp;val, const std::function&lt;V(V)&gt; &amp;filter_functi
512 return {(it != std::end(setref)), it}; 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 /// Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise. 533 /// Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise.
516 template <typename T> typename std::enable_if<std::is_integral<T>::value, bool>::type checked_multiply(T &a, T b) { 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 a *= b; 536 a *= b;
519 return true; 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 return false; 540 return false;
524 } 541 }
525 - a = c; 542 + if(overflowCheck(a, b)) {
  543 + return false;
  544 + }
  545 + a *= b;
526 return true; 546 return true;
527 } 547 }
528 548
tests/HelpersTest.cpp
@@ -423,10 +423,34 @@ TEST(CheckedMultiply, Int) { @@ -423,10 +423,34 @@ TEST(CheckedMultiply, Int) {
423 ASSERT_FALSE(CLI::detail::checked_multiply(a, b)); 423 ASSERT_FALSE(CLI::detail::checked_multiply(a, b));
424 ASSERT_EQ(a, std::numeric_limits<int>::min()); 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 a = std::numeric_limits<int>::min() / 100; 431 a = std::numeric_limits<int>::min() / 100;
427 b = 99; 432 b = 99;
428 ASSERT_TRUE(CLI::detail::checked_multiply(a, b)); 433 ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
429 ASSERT_EQ(a, std::numeric_limits<int>::min() / 100 * 99); 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 TEST(CheckedMultiply, SizeT) { 456 TEST(CheckedMultiply, SizeT) {