PR #2144: validate log_uniform_int_distribution stream input and harden preconditions

Imported from GitHub PR https://github.com/abseil/abseil-cpp/pull/2144

log_uniform_int_distribution's param_type constructor computes log_range_ as static_cast<int>(ceil((1/log(base)) * log(range))). A base of 0 or 1, or a negative base for a signed IntType, violates the base > 1 precondition and makes that math non-finite: 1/log(1) divides by zero, and the final cast of inf/NaN to int is undefined behavior (UBSan: "inf is outside the range of representable values of type 'int'"). It was also reachable through operator>>, which handed whatever it parsed straight to param_type.

Changes:
- Check the param_type preconditions with ABSL_HARDENING_ASSERT.
- Only take the log() branch for base_ > 2, so an out-of-contract base never reaches the floating-point math.
- Have operator>> validate max >= min and base > 1 and set failbit on bad input instead of constructing an invalid param_type, with a test covering that rejection.

Merge b15918e1696ac26b2f283a26c03671a29f9b61a6 into 8bd6e5097d96974f5c85031759c545a8e7426b4a

Merging this change closes #2144

PiperOrigin-RevId: 984157679
Change-Id: I003a3dd5536fdac5731850017095915e73ab05bb
diff --git a/absl/random/log_uniform_int_distribution.h b/absl/random/log_uniform_int_distribution.h
index cbd5e0c..99abe05 100644
--- a/absl/random/log_uniform_int_distribution.h
+++ b/absl/random/log_uniform_int_distribution.h
@@ -16,13 +16,14 @@
 #define ABSL_RANDOM_LOG_UNIFORM_INT_DISTRIBUTION_H_
 
 #include <algorithm>
-#include <cassert>
 #include <cmath>
+#include <ios>
 #include <istream>
 #include <limits>
 #include <ostream>
 
 #include "absl/base/config.h"
+#include "absl/base/macros.h"
 #include "absl/random/internal/iostream_state_saver.h"
 #include "absl/random/internal/traits.h"
 #include "absl/random/uniform_int_distribution.h"
@@ -60,15 +61,18 @@
           range_(static_cast<unsigned_type>(max_) -
                  static_cast<unsigned_type>(min_)),
           log_range_(0) {
-      assert(max_ >= min_);
-      assert(base_ > 1);
+      ABSL_HARDENING_ASSERT(max_ >= min_);
+      ABSL_HARDENING_ASSERT(base_ > 1);
 
       if (base_ == 2) {
         // Determine where the first set bit is on range(), giving a log2(range)
         // value which can be used to construct bounds.
         log_range_ = (std::min)(random_internal::BitWidth(range()),
                                 std::numeric_limits<unsigned_type>::digits);
-      } else {
+      } else if (base_ > 2) {
+        // An out-of-contract base_ (<= 1) skips this branch entirely so that
+        // no floating-point undefined behavior is reached.
+        //
         // NOTE: Computing the logN(x) introduces error from 2 sources:
         // 1. Conversion of int to double loses precision for values >=
         // 2^53, which may cause some log() computations to operate on
@@ -240,9 +244,16 @@
   auto saver = random_internal::make_istream_state_saver(is);
   is >> min >> max >> base;
   if (!is.fail()) {
-    x.param(param_type(static_cast<result_type>(min),
-                       static_cast<result_type>(max),
-                       static_cast<result_type>(base)));
+    const result_type min_val = static_cast<result_type>(min);
+    const result_type max_val = static_cast<result_type>(max);
+    const result_type base_val = static_cast<result_type>(base);
+    if (max_val < min_val || base_val <= 1) {
+      // The input violates the param_type preconditions; signal failure by
+      // setting the failbit instead of constructing an invalid param_type.
+      is.setstate(is.rdstate() | std::ios_base::failbit);
+    } else {
+      x.param(param_type(min_val, max_val, base_val));
+    }
   }
   return is;
 }
diff --git a/absl/random/log_uniform_int_distribution_test.cc b/absl/random/log_uniform_int_distribution_test.cc
index 591b5b3..4404111 100644
--- a/absl/random/log_uniform_int_distribution_test.cc
+++ b/absl/random/log_uniform_int_distribution_test.cc
@@ -116,6 +116,26 @@
   }
 }
 
+// operator>> must reject input that violates the param_type preconditions
+// (max >= min and base > 1) by setting failbit and leaving the distribution
+// unchanged, rather than constructing an out-of-contract param_type.
+TYPED_TEST(LogUniformIntDistributionTypeTest, DeserializeRejectsInvalidParams) {
+  for (const char* input : {
+           "0 100 1",  // base == 1
+           "0 100 0",  // base == 0
+           "100 0 2",  // max < min
+       }) {
+    absl::log_uniform_int_distribution<TypeParam> dist(3, 6, 17);
+    const auto before = dist.param();
+
+    std::istringstream is(input);
+    is >> dist;
+
+    EXPECT_TRUE(is.fail()) << input;
+    EXPECT_EQ(dist.param(), before) << input;
+  }
+}
+
 using log_uniform_i32 = absl::log_uniform_int_distribution<int32_t>;
 
 class LogUniformIntChiSquaredTest