Skip to content

Commit 9feee5c

Browse files
authored
Merge pull request #4159 from counter2015/patch/array-size-check
Check target array length before allocating.
2 parents dece9e3 + 3f00eed commit 9feee5c

File tree

4 files changed

+96
-4
lines changed

4 files changed

+96
-4
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2020-2024 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package cats.effect.benchmarks
18+
19+
import cats.effect.ArrayStack
20+
21+
import org.openjdk.jmh.annotations._
22+
23+
import java.util.concurrent.TimeUnit
24+
25+
/**
26+
* To do comparative benchmarks between versions:
27+
*
28+
* benchmarks/run-benchmark ArrayStackBenchmark
29+
*
30+
* This will generate results in `benchmarks/results`.
31+
*
32+
* Or to run the benchmark from within sbt:
33+
*
34+
* Jmh / run -i 10 -wi 10 -f 2 -t 1 cats.effect.benchmarks.ArrayStackBenchmark
35+
*
36+
* Which means "10 iterations", "10 warm-up iterations", "2 forks", "1 thread". Please note that
37+
* benchmarks should be usually executed at least in 10 iterations (as a rule of thumb), but
38+
* more is better.
39+
*/
40+
@State(Scope.Thread)
41+
@BenchmarkMode(Array(Mode.Throughput))
42+
@OutputTimeUnit(TimeUnit.SECONDS)
43+
class ArrayStackBenchmark {
44+
45+
@Param(Array("1000000"))
46+
var size: Int = _
47+
48+
@Benchmark
49+
def push(): Unit = {
50+
val stack: ArrayStack[Integer] = ArrayStack[Integer](size)
51+
52+
var i = 0
53+
while (i < size) {
54+
stack.push(i)
55+
i += 1
56+
}
57+
}
58+
}

core/jvm-native/src/main/scala/cats/effect/ArrayStack.scala

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616

1717
package cats.effect
1818

19+
import PlatformStatics.VM_MaxArraySize
1920
import Platform.static
2021

21-
private final class ArrayStack[A <: AnyRef](
22+
private[effect] final class ArrayStack[A <: AnyRef](
2223
private[this] var buffer: Array[AnyRef],
2324
private[this] var index: Int) {
2425

@@ -72,7 +73,17 @@ private final class ArrayStack[A <: AnyRef](
7273
private[this] def checkAndGrow(): Unit =
7374
if (index >= buffer.length) {
7475
val len = buffer.length
75-
val buffer2 = new Array[AnyRef](len * 2)
76+
val targetLen = len * 2
77+
78+
val resizeLen =
79+
if (targetLen < 0)
80+
throw new Exception(s"Overflow while resizing array. Request length: $targetLen")
81+
else if (len > VM_MaxArraySize / 2)
82+
VM_MaxArraySize
83+
else
84+
targetLen
85+
86+
val buffer2 = new Array[AnyRef](resizeLen)
7687
System.arraycopy(buffer, 0, buffer2, 0, len)
7788
buffer = buffer2
7889
}

core/jvm-native/src/main/scala/cats/effect/CallbackStack.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ import scala.annotation.tailrec
2020

2121
import java.util.concurrent.atomic.{AtomicBoolean, AtomicReference}
2222

23-
import CallbackStack.Handle
24-
import CallbackStack.Node
23+
import CallbackStack.{Handle, Node}
2524
import Platform.static
2625

2726
private final class CallbackStack[A](private[this] var callback: A => Unit)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2020-2024 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package cats.effect
18+
19+
// copy from scala.runtime.PStatics
20+
private[effect] object PlatformStatics {
21+
// `Int.MaxValue - 8` traditional soft limit to maximize compatibility with diverse JVMs
22+
// See https://stackoverflow.com/a/8381338 for example
23+
final val VM_MaxArraySize = 2147483639
24+
}

0 commit comments

Comments
 (0)