[libc][NFC] make atoi undefined cases match std
authorMichael Jones <michaelrj@google.com>
Mon, 19 Dec 2022 21:35:45 +0000 (13:35 -0800)
committerMichael Jones <michaelrj@google.com>
Tue, 20 Dec 2022 18:21:25 +0000 (10:21 -0800)
The standard describes atoi as:

"equivalent to atoi: (int)strtol(nptr, (char **)NULL, 10)"

Previously, our behavior was slightly different on numbers larger than
INT_MAX, but this patch changes it to just do the cast instead. Both of
these are valid since the standard says

"If the value of the result cannot be represented, the
behavior is undefined."

But matching existing behavior makes differential fuzzing easier.

Reviewed By: sivachandra

Differential Revision: https://reviews.llvm.org/D140350

libc/src/stdlib/atoi.cpp
libc/test/src/stdlib/AtoiTest.h

index f61e365..d9bc866 100644 (file)
 namespace __llvm_libc {
 
 LLVM_LIBC_FUNCTION(int, atoi, (const char *str)) {
-  auto result = internal::strtointeger<int>(str, 10);
+  // This is done because the standard specifies that atoi is identical to
+  // (int)(strtol).
+  auto result = internal::strtointeger<long>(str, 10);
   if (result.has_error())
     errno = result.error;
 
-  return result;
+  return static_cast<int>(result);
 }
 
 } // namespace __llvm_libc
index 4013800..db48945 100644 (file)
@@ -6,10 +6,13 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "src/__support/CPP/type_traits.h"
 #include "utils/UnitTest/Test.h"
 
 #include <limits.h>
 
+using __llvm_libc::cpp::is_same_v;
+
 template <typename ReturnT> struct AtoTest : public __llvm_libc::testing::Test {
   using FunctionT = ReturnT (*)(const char *);
 
@@ -54,6 +57,26 @@ template <typename ReturnT> struct AtoTest : public __llvm_libc::testing::Test {
       const char *smallest_long_long = "-9223372036854775808";
       ASSERT_EQ(func(smallest_long_long), static_cast<ReturnT>(LLONG_MIN));
     }
+
+    // If this is atoi and the size of int is less than the size of long, then
+    // we parse as long and cast to int to match existing behavior. This only
+    // matters for cases where the result would be outside of the int range, and
+    // those cases are undefined, so we can choose whatever output value we
+    // want. In this case we have chosen to cast since that matches existing
+    // implementations and makes differential fuzzing easier, but no user should
+    // rely on this behavior.
+    if constexpr (is_same_v<ReturnT, int> && sizeof(ReturnT) < sizeof(long)) {
+
+      static_assert(sizeof(int) == 4);
+
+      const char *bigger_than_biggest_int = "2147483649";
+      ASSERT_EQ(func(bigger_than_biggest_int),
+                static_cast<ReturnT>(2147483649));
+
+      const char *smaller_than_smallest_int = "-2147483649";
+      ASSERT_EQ(func(smaller_than_smallest_int),
+                static_cast<ReturnT>(-2147483649));
+    }
   }
 
   void nonBaseTenWholeNumbers(FunctionT func) {