Skip to content

Commit c32ee8f

Browse files
committed
Fix: known stack overflow bug in Stream#bind
This fixes a known[1][2] bug which often manifests in stack overflow errors in production usage of the FJ library's Stream class. [1] http://code.google.com/p/functionaljava/issues/detail?id=16 [2] http://stackoverflow.com/questions/10915279/functionaljava-app-throws-stackoverflowerror-with-stream-in-stack-trace
1 parent 8f0e035 commit c32ee8f

2 files changed

Lines changed: 24 additions & 2 deletions

File tree

core/src/main/java/fj/data/Stream.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,16 @@ public final <B, C> F<B, Stream<C>> mapM(final F<A, F<B, C>> f) {
401401
* @return A new stream after performing the map, then final join.
402402
*/
403403
public final <B> Stream<B> bind(final F<A, Stream<B>> f) {
404-
return join(map(f));
404+
return map(f).foldLeft(new F2<Stream<B>, Stream<B>, Stream<B>>() {
405+
@Override
406+
public Stream<B> f(Stream<B> accumulator, Stream<B> element) {
407+
Stream<B> result = accumulator;
408+
for (B single : element) {
409+
result = result.cons(single);
410+
}
411+
return result;
412+
}
413+
}, Stream.<B>nil()).reverse();
405414
}
406415

407416
/**

core/src/test/fj/data/CheckStream.scala

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import Equal.{streamEqual, stringEqual}
1010
import Unit.unit
1111
import Stream.{nil, single, join, iterableStream}
1212
import Ord.stringOrd
13-
import org.scalacheck.Properties
13+
import org.scalacheck.{Gen, Properties}
14+
import collection.JavaConversions
1415

1516
object CheckStream extends Properties("Stream") {
1617
property("isEmpty") = forAll((a: Stream[Int]) =>
@@ -47,6 +48,18 @@ object CheckStream extends Properties("Stream") {
4748
def g(s: String) = s.toUpperCase
4849
streamEqual(stringEqual).eq(a.map((x: String) => f(g(x))), a.map((x: String) => g(x)).map((x: String) => f(x)))})
4950

51+
val length = Gen.choose(0, 3000)
52+
53+
property("bindStackOverflow") = forAll(length)(size => {
54+
val stream = iterableStream(JavaConversions.asJavaIterable((1 to size)))
55+
val bound: Stream[Int] = stream.bind(new F[Int, Stream[Int]] {
56+
def f(a: Int) = single(a)
57+
})
58+
stream.zip(bound).forall(new F[P2[Int, Int], java.lang.Boolean] {
59+
def f(a: P2[Int, Int]) = a._1() == a._2()
60+
})
61+
})
62+
5063
property("foreach") = forAll((a: Stream[Int]) => {
5164
var i = 0
5265
a.foreach({

0 commit comments

Comments
 (0)