Skip to content

REPL attempts infinite iterable toString #11004

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
som-snytt opened this issue Jan 6, 2021 · 5 comments
Closed

REPL attempts infinite iterable toString #11004

som-snytt opened this issue Jan 6, 2021 · 5 comments

Comments

@som-snytt
Copy link
Contributor

Minimized code

scala> val vs = new Iterable[Int] { def iterator = Iterator.continually(42) }

Output

java.lang.OutOfMemoryError: Java heap space
  at java.base/java.util.Arrays.copyOf(Arrays.java:3536)
  at java.base/java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:228)
  at java.base/java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:582)
  at java.base/java.lang.StringBuilder.append(StringBuilder.java:173)
  at java.base/java.lang.StringBuilder.append(StringBuilder.java:167)
  at scala.collection.IterableOnceOps.addString(IterableOnce.scala:1187)
  at scala.collection.IterableOnceOps.addString$(IterableOnce.scala:1179)
  at rs$line$1$$anon$1.addString(rs$line$1:1)
  at scala.collection.IterableOnceOps.mkString(IterableOnce.scala:1129)
  at scala.collection.IterableOnceOps.mkString$(IterableOnce.scala:1127)
  at rs$line$1$$anon$1.mkString(rs$line$1:1)
  at scala.collection.Iterable.toString(Iterable.scala:76)
  at scala.collection.Iterable.toString$(Iterable.scala:76)
  at rs$line$1$$anon$1.toString(rs$line$1:1)
  at scala.runtime.ScalaRunTime$.inner$1(ScalaRunTime.scala:255)
  at scala.runtime.ScalaRunTime$.stringOf(ScalaRunTime.scala:266)
  at scala.runtime.ScalaRunTime$.replStringOf(ScalaRunTime.scala:274)
  at scala.runtime.ScalaRunTime.replStringOf(ScalaRunTime.scala)
  at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
  at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.base/java.lang.reflect.Method.invoke(Method.java:564)
  at dotty.tools.repl.Rendering.classLoader$$anonfun$1(Rendering.scala:62)
  at dotty.tools.repl.Rendering$$Lambda$1329/0x00000008010a22f8.apply(Unknown Source)
  at dotty.tools.repl.Rendering.replStringOf(Rendering.scala:71)
  at dotty.tools.repl.Rendering.$anonfun$4(Rendering.scala:85)
  at dotty.tools.repl.Rendering$$Lambda$1332/0x00000008010a3630.apply(Unknown Source)
  at scala.Option.map(Option.scala:242)
  at dotty.tools.repl.Rendering.valueOf(Rendering.scala:85)
  at dotty.tools.repl.Rendering.renderVal(Rendering.scala:121)
  at dotty.tools.repl.ReplDriver.$anonfun$13(ReplDriver.scala:308)
  at dotty.tools.repl.ReplDriver$$Lambda$1308/0x0000000801095328.apply(Unknown Source)

Expectation

I might want to use that iterable.

This is a transfer of scala/bug#11785 which was not previously resolved.

@griggt
Copy link
Contributor

griggt commented Feb 10, 2021

It seems that this is because the call to useOwnToString returns true in:

    def inner(arg: Any): String = arg match {
      case null                         => "null"
      case ""                           => "\"\""
      case x: String                    => if (x.head.isWhitespace || x.last.isWhitespace) "\"" + x + "\"" else x
      case x if useOwnToString(x)       => x.toString     // *** HERE this case matches, we never get to Iterable[_] below
      case x: AnyRef if isArray(x)      => arrayToString(x)
      case x: scala.collection.Map[_, _] => x.iterator.take(maxElements).map(mapInner).mkString(x.collectionClassName + "(", ", ", ")")
      case x: Iterable[_]               => x.iterator.take(maxElements).map(inner).mkString(x.collectionClassName + "(", ", ", ")")
      case x: Product1[_] if isTuple(x) => "(" + inner(x._1) + ",)" 
      case x: Product if isTuple(x)     => x.productIterator.map(inner).mkString("(", ",", ")")
      case x                            => x.toString
    }

https://github.com/scala/scala/blob/51790d7a5b0b1c12a16e14a1bd65c1f0941dda47/src/library/scala/runtime/ScalaRunTime.scala#L251-L262

@som-snytt
Copy link
Contributor Author

Thanks, I totally forgot this ticket. Probably the events of Jan 6 erased my memory.

@griggt
Copy link
Contributor

griggt commented Feb 11, 2021

Ref: scala/bug#3710
scala/scala@11ae7ea

It seems that the logic is intended, in part, to prevent infinite traversal, and here is having the opposite effect.

@som-snytt
Copy link
Contributor Author

Thanks for tracking that down. The best part is

    val maxElements = 10000

So the real bug is that a zero was lost at some point.

@som-snytt
Copy link
Contributor Author

I returned to this because the Scala 3 REPL currently (recently) asks for both a bounded and unbounded string result from stringOf in the runtime, in order to detect whether the output was truncated. It occurred to me that an unbounded result must invite infinite strings.

In this case, indeed, custom Iterable is expected to supply a custom toString.

Recently, I wondered if there is a use case for isTraversableAgain: maybe it implies finitely traversable.

I will close this and bounce it back to the Scala 2 ticket.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants