Skip to content

Commit b2ccb28

Browse files
ijumaNthPortal
andcommitted
Fail early if self-referential LazyList runs out of elements
Port of scala/scala#8635. Co-authored-by: NthPortal <[email protected]>
1 parent 9fdd0f7 commit b2ccb28

File tree

2 files changed

+29
-1
lines changed

2 files changed

+29
-1
lines changed

compat/src/main/scala-2.11_2.12/scala/collection/compat/immutable/LazyList.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,16 @@ final class LazyList[+A] private (private[this] var lazyState: () => LazyList.St
231231

232232
@volatile private[this] var stateEvaluated: Boolean = false
233233
@inline private def stateDefined: Boolean = stateEvaluated
234+
private[this] var midEvaluation = false
234235

235236
private lazy val state: State[A] = {
236-
val res = lazyState()
237+
// if it's already mid-evaluation, we're stuck in an infinite
238+
// self-referential loop (also it's empty)
239+
if (midEvaluation) {
240+
throw new RuntimeException("self-referential LazyList or a derivation thereof has no more elements")
241+
}
242+
midEvaluation = true
243+
val res = try lazyState() finally midEvaluation = false
237244
// if we set it to `true` before evaluating, we may infinite loop
238245
// if something expects `state` to already be evaluated
239246
stateEvaluated = true

compat/src/test/scala/test/scala/collection/LazyListTest.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,4 +355,25 @@ class LazyListTest {
355355
assertEquals(1 to 10, build(_ ++= LazyList.from(1).take(10)))
356356
assertEquals(1 to 10, build(_ ++= Iterator.from(1).take(10)))
357357
}
358+
359+
@Test
360+
def selfReferentialFailure(): Unit = {
361+
def assertNoStackOverflow[A](lazyList: LazyList[A]): Unit = {
362+
// don't hang the test if we've made a programming error in this test
363+
val finite = lazyList.take(1000)
364+
// AssertUtil.assertThrows[RuntimeException](finite.force, _ contains "self-referential")
365+
try {
366+
finite.force
367+
fail("Expected RuntimeException to be thrown")
368+
} catch { case e: RuntimeException => assertTrue(e.getMessage.contains("self-referential")) }
369+
}
370+
assertNoStackOverflow { class L { val ll: LazyList[Nothing] = LazyList.empty #::: ll }; (new L).ll }
371+
assertNoStackOverflow { class L { val ll: LazyList[Int] = 1 #:: ll.map(_ + 1).filter(_ % 2 == 0) }; (new L).ll }
372+
class L {
373+
lazy val a: LazyList[Nothing] = LazyList.empty #::: b
374+
lazy val b: LazyList[Nothing] = LazyList.empty #::: a
375+
}
376+
assertNoStackOverflow((new L).a)
377+
assertNoStackOverflow((new L).b)
378+
}
358379
}

0 commit comments

Comments
 (0)