diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index da914ca9cb67..6b68b4ddfc42 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -212,6 +212,17 @@ object Decorators { /** Union on lists seen as sets */ def setUnion (ys: List[T]): List[T] = xs ::: ys.filterNot(xs contains _) + /** Reduce left with `op` as long as list `xs` is not longer than `seqLimit`. + * Otherwise, split list in two half, reduce each, and combine with `op`. + */ + def reduceBalanced(op: (T, T) => T, seqLimit: Int = 100): T = + val len = xs.length + if len > seqLimit then + val (leading, trailing) = xs.splitAt(len / 2) + op(leading.reduceBalanced(op, seqLimit), trailing.reduceBalanced(op, seqLimit)) + else + xs.reduceLeft(op) + extension [T, U](xss: List[List[T]]) def nestedMap(f: T => U): List[List[U]] = xss match case xs :: xss1 => xs.map(f) :: xss1.nestedMap(f) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index b1885d3e9fba..f46bb779bf55 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -96,21 +96,23 @@ end RecursionOverflow */ // Beware: Since this object is only used when handling a StackOverflow, this code // cannot consume significant amounts of stack. -object handleRecursive { +object handleRecursive: + inline def underlyingStackOverflowOrNull(exc: Throwable): Throwable | Null = + var e: Throwable | Null = exc + while e != null && !e.isInstanceOf[StackOverflowError] do e = e.getCause + e + def apply(op: String, details: => String, exc: Throwable, weight: Int = 1)(using Context): Nothing = - if (ctx.settings.YnoDecodeStacktraces.value) + if ctx.settings.YnoDecodeStacktraces.value then throw exc - else - exc match { - case _: RecursionOverflow => - throw new RecursionOverflow(op, details, exc, weight) - case _ => - var e: Throwable | Null = exc - while (e != null && !e.isInstanceOf[StackOverflowError]) e = e.getCause - if (e != null) throw new RecursionOverflow(op, details, e, weight) - else throw exc - } -} + else exc match + case _: RecursionOverflow => + throw new RecursionOverflow(op, details, exc, weight) + case _ => + val so = underlyingStackOverflowOrNull(exc) + if so != null then throw new RecursionOverflow(op, details, so, weight) + else throw exc +end handleRecursive /** * This TypeError signals that completing denot encountered a cycle: it asked for denot.info (or similar), diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 86e5ba228cff..f5c0c678a6ec 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -20,6 +20,8 @@ import collection.mutable import reporting.{Profile, NoProfile} import dotty.tools.tasty.TastyFormat.ASTsSection +object TreePickler: + class StackSizeExceeded(val mdef: tpd.MemberDef) extends Exception class TreePickler(pickler: TastyPickler) { val buf: TreeBuffer = new TreeBuffer @@ -27,6 +29,7 @@ class TreePickler(pickler: TastyPickler) { import buf._ import pickler.nameBuffer.nameIndex import tpd._ + import TreePickler.* private val symRefs = Symbols.MutableSymbolMap[Addr](256) private val forwardSymRefs = Symbols.MutableSymbolMap[List[Addr]]() @@ -53,7 +56,7 @@ class TreePickler(pickler: TastyPickler) { def docString(tree: untpd.MemberDef): Option[Comment] = Option(docStrings.lookup(tree)) - private def withLength(op: => Unit) = { + private inline def withLength(inline op: Unit) = { val lengthAddr = reserveRef(relative = true) op fillRef(lengthAddr, currentAddr, relative = true) @@ -328,16 +331,24 @@ class TreePickler(pickler: TastyPickler) { registerDef(sym) writeByte(tag) val addr = currentAddr - withLength { - pickleName(sym.name) - pickleParams - tpt match { - case _: Template | _: Hole => pickleTree(tpt) - case _ if tpt.isType => pickleTpt(tpt) + try + withLength { + pickleName(sym.name) + pickleParams + tpt match { + case _: Template | _: Hole => pickleTree(tpt) + case _ if tpt.isType => pickleTpt(tpt) + } + pickleTreeUnlessEmpty(rhs) + pickleModifiers(sym, mdef) } - pickleTreeUnlessEmpty(rhs) - pickleModifiers(sym, mdef) - } + catch + case ex: Throwable => + if !ctx.settings.YnoDecodeStacktraces.value + && handleRecursive.underlyingStackOverflowOrNull(ex) != null then + throw StackSizeExceeded(mdef) + else + throw ex if sym.is(Method) && sym.owner.isClass then profile.recordMethodSize(sym, currentAddr.index - addr.index, mdef.span) for @@ -784,7 +795,17 @@ class TreePickler(pickler: TastyPickler) { def pickle(trees: List[Tree])(using Context): Unit = { profile = Profile.current - trees.foreach(tree => if (!tree.isEmpty) pickleTree(tree)) + for tree <- trees do + try + if !tree.isEmpty then pickleTree(tree) + catch case ex: StackSizeExceeded => + report.error( + em"""Recursion limit exceeded while pickling ${ex.mdef} + |in ${ex.mdef.symbol.showLocated}. + |You could try to increase the stacksize using the -Xss JVM option. + |For the unprocessed stack trace, compile with -Yno-decode-stacktraces.""", + ex.mdef.srcPos) + def missing = forwardSymRefs.keysIterator .map(sym => i"${sym.showLocated} (line ${sym.srcPos.line}) #${sym.id}") .toList diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index 0a9a7a83948c..c33e82d47443 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -266,7 +266,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) { val comparisons = sortedAccessors.map { accessor => This(clazz).withSpan(ctx.owner.span.focus).select(accessor).equal(ref(thatAsClazz).select(accessor)) } var rhs = // this.x == this$0.x && this.y == x$0.y && that.canEqual(this) - if comparisons.isEmpty then Literal(Constant(true)) else comparisons.reduceLeft(_ and _) + if comparisons.isEmpty then Literal(Constant(true)) else comparisons.reduceBalanced(_ and _) val canEqualMeth = existingDef(defn.Product_canEqual, clazz) if !clazz.is(Final) || canEqualMeth.exists && !canEqualMeth.is(Synthetic) then rhs = rhs.and( diff --git a/tests/pos/i16500.scala b/tests/pos/i16500.scala new file mode 100644 index 000000000000..9f279e044959 --- /dev/null +++ b/tests/pos/i16500.scala @@ -0,0 +1,54 @@ +type I = Int +case class BigClass254( + udef00:I, udef01:I, udef02:I, udef03:I, udef04:I, udef05:I, udef06:I, udef07:I, + udef08:I, udef09:I, udef0a:I, udef0b:I, udef0c:I, udef0d:I, udef0e:I, udef0f:I, + udef10:I, udef11:I, udef12:I, udef13:I, udef14:I, udef15:I, udef16:I, udef17:I, + udef18:I, udef19:I, udef1a:I, udef1b:I, udef1c:I, udef1d:I, udef1e:I, udef1f:I, + udef20:I, udef21:I, udef22:I, udef23:I, udef24:I, udef25:I, udef26:I, udef27:I, + udef28:I, udef29:I, udef2a:I, udef2b:I, udef2c:I, udef2d:I, udef2e:I, udef2f:I, + udef30:I, udef31:I, udef32:I, udef33:I, udef34:I, udef35:I, udef36:I, udef37:I, + udef38:I, udef39:I, udef3a:I, udef3b:I, udef3c:I, udef3d:I, udef3e:I, udef3f:I, + udef40:I, udef41:I, udef42:I, udef43:I, udef44:I, udef45:I, udef46:I, udef47:I, + udef48:I, udef49:I, udef4a:I, udef4b:I, udef4c:I, udef4d:I, udef4e:I, udef4f:I, + udef50:I, udef51:I, udef52:I, udef53:I, udef54:I, udef55:I, udef56:I, udef57:I, + udef58:I, udef59:I, udef5a:I, udef5b:I, udef5c:I, udef5d:I, udef5e:I, udef5f:I, + udef60:I, udef61:I, udef62:I, udef63:I, udef64:I, udef65:I, udef66:I, udef67:I, + udef68:I, udef69:I, udef6a:I, udef6b:I, udef6c:I, udef6d:I, udef6e:I, udef6f:I, + udef70:I, udef71:I, udef72:I, udef73:I, udef74:I, udef75:I, udef76:I, udef77:I, + udef78:I, udef79:I, udef7a:I, udef7b:I, udef7c:I, udef7d:I, udef7e:I, udef7f:I, + udef80:I, udef81:I, udef82:I, udef83:I, udef84:I, udef85:I, udef86:I, udef87:I, + udef88:I, udef89:I, udef8a:I, udef8b:I, udef8c:I, udef8d:I, udef8e:I, udef8f:I, + udef90:I, udef91:I, udef92:I, udef93:I, udef94:I, udef95:I, udef96:I, udef97:I, + udef98:I, udef99:I, udef9a:I, udef9b:I, udef9c:I, udef9d:I, udef9e:I, udef9f:I, + udefa0:I, udefa1:I, udefa2:I, udefa3:I, udefa4:I, udefa5:I, udefa6:I, udefa7:I, + udefa8:I, udefa9:I, udefaa:I, udefab:I, udefac:I, udefad:I, udefae:I, udefaf:I, + udefb0:I, udefb1:I, udefb2:I, udefb3:I, udefb4:I, udefb5:I, udefb6:I, udefb7:I, + udefb8:I, udefb9:I, udefba:I, udefbb:I, udefbc:I, udefbd:I, udefbe:I, udefbf:I, + udefc0:I, udefc1:I, udefc2:I, udefc3:I, udefc4:I, udefc5:I, udefc6:I, udefc7:I, + udefc8:I, udefc9:I, udefca:I, udefcb:I, udefcc:I, udefcd:I, udefce:I, udefcf:I, + udefd0:I, udefd1:I, udefd2:I, udefd3:I, udefd4:I, udefd5:I, udefd6:I, udefd7:I, + udefd8:I, udefd9:I, udefda:I, udefdb:I, udefdc:I, udefdd:I, udefde:I, udefdf:I, + udefe0:I, udefe1:I, udefe2:I, udefe3:I, udefe4:I, udefe5:I, udefe6:I, udefe7:I, + udefe8:I, udefe9:I, udefea:I, udefeb:I, udefec:I, udefed:I, udefee:I, udefef:I, + udeff0:I, udeff1:I, udeff2:I, udeff3:I, udeff4:I, udeff5:I, udeff6:I, udeff7:I, + udeff8:I, udeff9:I, udeffa:I, udeffb:I, udeffc:I, udeffd:I, +) +@main def Test = + BigClass254( + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ) \ No newline at end of file