This is an automated email from the ASF dual-hosted git repository.

pjfanning pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko.git


The following commit(s) were added to refs/heads/main by this push:
     new 42a4596780 fix: guard against int overflow in supervisor strategy and 
frequency sketch (#3022)
42a4596780 is described below

commit 42a4596780cba7bc0b1fe88c2c99bc8cdbc47af0
Author: PJ Fanning <[email protected]>
AuthorDate: Sun May 31 20:47:27 2026 +0100

    fix: guard against int overflow in supervisor strategy and frequency sketch 
(#3022)
    
    * fix: guard against int overflow in supervisor strategy and frequency 
sketch
    
    * fix: address PR #3022 review comments - width overflow in FrequencySketch 
and regression tests for large withinTimeRange (#54)
    
    * scalafmt
    
    ---------
    
    Co-authored-by: copilot-swe-agent[bot] 
<[email protected]>
---
 .../org/apache/pekko/actor/SupervisorSpec.scala    | 26 ++++++++++++++
 .../apache/pekko/util/FrequencySketchSpec.scala    | 42 ++++++++++++++++++++++
 .../org/apache/pekko/actor/FaultHandling.scala     |  6 ++--
 .../org/apache/pekko/util/FrequencySketch.scala    | 14 +++++---
 4 files changed, 82 insertions(+), 6 deletions(-)

diff --git 
a/actor-tests/src/test/scala/org/apache/pekko/actor/SupervisorSpec.scala 
b/actor-tests/src/test/scala/org/apache/pekko/actor/SupervisorSpec.scala
index e37126bed7..f75b460433 100644
--- a/actor-tests/src/test/scala/org/apache/pekko/actor/SupervisorSpec.scala
+++ b/actor-tests/src/test/scala/org/apache/pekko/actor/SupervisorSpec.scala
@@ -613,4 +613,30 @@ class SupervisorSpec
     killExpectNoRestart(pingpong)
   }
 
+  "respects maxNrOfRetries with withinTimeRange whose toMillis exceeds 
Int.MaxValue (OneForOneStrategy)" in {
+    // 30 days = 2,592,000,000 ms which silently overflowed to a negative Int 
before the fix,
+    // causing retriesInWindowOkay to always return true (unlimited restarts)
+    val supervisor = system.actorOf(Props(
+      new Supervisor(OneForOneStrategy(maxNrOfRetries = 2, withinTimeRange = 
30.days)(classOf[Exception] :: Nil))))
+
+    val pingpong = child(supervisor, Props(new PingPongActor(testActor)))
+
+    kill(pingpong)
+    kill(pingpong)
+    killExpectNoRestart(pingpong)
+  }
+
+  "respects maxNrOfRetries with withinTimeRange whose toMillis exceeds 
Int.MaxValue (AllForOneStrategy)" in {
+    // 30 days = 2,592,000,000 ms which silently overflowed to a negative Int 
before the fix,
+    // causing retriesInWindowOkay to always return true (unlimited restarts)
+    val supervisor = system.actorOf(Props(
+      new Supervisor(AllForOneStrategy(maxNrOfRetries = 2, withinTimeRange = 
30.days)(classOf[Exception] :: Nil))))
+
+    val pingpong = child(supervisor, Props(new PingPongActor(testActor)))
+
+    kill(pingpong)
+    kill(pingpong)
+    killExpectNoRestart(pingpong)
+  }
+
 }
diff --git 
a/actor-tests/src/test/scala/org/apache/pekko/util/FrequencySketchSpec.scala 
b/actor-tests/src/test/scala/org/apache/pekko/util/FrequencySketchSpec.scala
index 1e6b112884..fa036aa85a 100644
--- a/actor-tests/src/test/scala/org/apache/pekko/util/FrequencySketchSpec.scala
+++ b/actor-tests/src/test/scala/org/apache/pekko/util/FrequencySketchSpec.scala
@@ -264,6 +264,28 @@ class FrequencySketchSpec extends AnyWordSpec with 
Matchers {
       sketch.frequency("foo") shouldBe 2
       sketch.frequency("bar") shouldBe 2
     }
+
+    "reject capacity that would cause width to overflow Int" in {
+      // capacity > 536,870,912 with default widthMultiplier=4: 
ceilingPowerOfTwo gives 2^30=1073741824, times 4 overflows
+      an[IllegalArgumentException] should be thrownBy 
FrequencySketch[String](capacity = 536_870_913)
+    }
+
+    "detect width overflow that would have been missed by Int arithmetic" in {
+      // before the fix, widthMultiplier * ceilingPowerOfTwo(capacity) was 
computed in Int and could silently overflow
+      val capacity = 536_870_913
+      val widthMultiplier = 4
+      // the old Int multiplication silently overflows to a non-power-of-two 
or zero/negative value
+      val widthInt = widthMultiplier * 
FrequencySketch.Bits.ceilingPowerOfTwo(capacity)
+      widthInt should be <= 0 // negative or zero due to Int overflow (old 
broken behavior)
+      // the new Long multiplication correctly identifies the overflow
+      val widthLong = widthMultiplier.toLong * 
FrequencySketch.Bits.ceilingPowerOfTwo(capacity).toLong
+      widthLong should be > Int.MaxValue.toLong
+    }
+
+    "reject capacity that would cause width to overflow Int with custom 
widthMultiplier" in {
+      an[IllegalArgumentException] should be thrownBy
+      FrequencySketch[String](capacity = 100, widthMultiplier = Int.MaxValue)
+    }
   }
 
   "FastFrequencySketch" must {
@@ -370,5 +392,25 @@ class FrequencySketchSpec extends AnyWordSpec with 
Matchers {
       val accuracy = correct.toDouble / comparisons
       accuracy should be > 0.95 // note: depends on the hash collisions, and 
random distribution
     }
+
+    "reject capacity that would cause width to overflow Int" in {
+      // capacity > 536,870,912 with default widthMultiplier=4: 
ceilingPowerOfTwo gives 2^30=1073741824, times 4 overflows
+      an[IllegalArgumentException] should be thrownBy 
FastFrequencySketch[String](capacity = 536_870_913)
+    }
+
+    "detect width overflow that would have been missed by Int arithmetic" in {
+      // before the fix, widthMultiplier * ceilingPowerOfTwo(capacity) was 
computed in Int and could silently overflow
+      val capacity = 536_870_913
+      val widthMultiplier = 4
+      val widthInt = widthMultiplier * 
FrequencySketch.Bits.ceilingPowerOfTwo(capacity)
+      widthInt should be <= 0 // negative or zero due to Int overflow (old 
broken behavior)
+      val widthLong = widthMultiplier.toLong * 
FrequencySketch.Bits.ceilingPowerOfTwo(capacity).toLong
+      widthLong should be > Int.MaxValue.toLong
+    }
+
+    "reject capacity that would cause width to overflow Int with custom 
widthMultiplier" in {
+      an[IllegalArgumentException] should be thrownBy
+      FastFrequencySketch[String](capacity = 100, widthMultiplier = 
Int.MaxValue)
+    }
   }
 }
diff --git a/actor/src/main/scala/org/apache/pekko/actor/FaultHandling.scala 
b/actor/src/main/scala/org/apache/pekko/actor/FaultHandling.scala
index 01c5f92a2f..988b0e82c6 100644
--- a/actor/src/main/scala/org/apache/pekko/actor/FaultHandling.scala
+++ b/actor/src/main/scala/org/apache/pekko/actor/FaultHandling.scala
@@ -544,7 +544,8 @@ case class AllForOneStrategy(
    *  across actors and thus this field does not take up much space
    */
   private val retriesWindow =
-    (maxNrOfRetriesOption(maxNrOfRetries), 
withinTimeRangeOption(withinTimeRange).map(_.toMillis.toInt))
+    (maxNrOfRetriesOption(maxNrOfRetries),
+      withinTimeRangeOption(withinTimeRange).map(d => 
math.min(Int.MaxValue.toLong, d.toMillis).toInt))
 
   def handleChildTerminated(context: ActorContext, child: ActorRef, children: 
Iterable[ActorRef]): Unit = ()
 
@@ -656,7 +657,8 @@ case class OneForOneStrategy(
    */
   private val retriesWindow = (
     SupervisorStrategy.maxNrOfRetriesOption(maxNrOfRetries),
-    
SupervisorStrategy.withinTimeRangeOption(withinTimeRange).map(_.toMillis.toInt))
+    SupervisorStrategy.withinTimeRangeOption(withinTimeRange).map(d =>
+      math.min(Int.MaxValue.toLong, d.toMillis).toInt))
 
   def handleChildTerminated(context: ActorContext, child: ActorRef, children: 
Iterable[ActorRef]): Unit = ()
 
diff --git a/actor/src/main/scala/org/apache/pekko/util/FrequencySketch.scala 
b/actor/src/main/scala/org/apache/pekko/util/FrequencySketch.scala
index 90d96df312..f7c7339341 100644
--- a/actor/src/main/scala/org/apache/pekko/util/FrequencySketch.scala
+++ b/actor/src/main/scala/org/apache/pekko/util/FrequencySketch.scala
@@ -60,8 +60,11 @@ private[pekko] object FrequencySketch {
       resetMultiplier: Double = 10,
       depth: Int = 4,
       counterBits: Int = 4)(implicit hasher: Hasher[A]): FrequencySketch[A] = {
-    val width = widthMultiplier * Bits.ceilingPowerOfTwo(capacity)
-    val resetSize = (resetMultiplier * capacity).toInt
+    val widthLong = widthMultiplier.toLong * 
Bits.ceilingPowerOfTwo(capacity).toLong
+    require(widthLong <= Int.MaxValue,
+      s"computed width ($widthLong) exceeds Int.MaxValue; reduce capacity or 
widthMultiplier")
+    val width = widthLong.toInt
+    val resetSize = math.min(Int.MaxValue.toLong, (resetMultiplier * 
capacity).toLong).toInt
     new FrequencySketch(depth, width, counterBits, resetSize, hasher)
   }
 
@@ -256,8 +259,11 @@ private[pekko] object FastFrequencySketch {
    * @return a configured FastFrequencySketch
    */
   def apply[A](capacity: Int, widthMultiplier: Int = 4, resetMultiplier: 
Double = 10): FastFrequencySketch[A] = {
-    val width = widthMultiplier * 
FrequencySketch.Bits.ceilingPowerOfTwo(capacity)
-    val resetSize = (resetMultiplier * capacity).toInt
+    val widthLong = widthMultiplier.toLong * 
FrequencySketch.Bits.ceilingPowerOfTwo(capacity).toLong
+    require(widthLong <= Int.MaxValue,
+      s"computed width ($widthLong) exceeds Int.MaxValue; reduce capacity or 
widthMultiplier")
+    val width = widthLong.toInt
+    val resetSize = math.min(Int.MaxValue.toLong, (resetMultiplier * 
capacity).toLong).toInt
     new FastFrequencySketch(width, resetSize)
   }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to