From aa0cb95c4863cf2694e2691064c2412ece7b6fe6 Mon Sep 17 00:00:00 2001 From: Merlin Hughes Date: Sat, 27 Jun 2026 00:15:43 -0400 Subject: [PATCH] [2.x] fix: Constrain job id parser to signed longs (#9353) Job ids are long. The job id parser currently uses NotSpace to parse the ids, which fails with a NumberFormatException for short non-numeric values and OOMs SBT for long non-numeric values. --- CONTRIBUTING.md | 4 ++-- .../scala/sbt/internal/util/complete/Parsers.scala | 11 ++++++++--- main/src/main/scala/sbt/BackgroundJobService.scala | 8 ++++---- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9f8e79049..645f65854 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -112,8 +112,8 @@ See also [Development environment][02] and [Coding style and best practices][03] sbt features a testing infrastructure encompassing multiple testing methodologies designed to ensure reliability and functionality across different integrations. The testing framework includes: - [Unit tests][04] -- [scripted tests][05] -- [Manual tests][06], using the locally baked sbt +- [Scripted tests][05] +- [Manual tests][06], which details how to run the locally baked sbt in your own project ### Tech stack diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parsers.scala b/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parsers.scala index 83c0fd5a8..4efcc6e26 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parsers.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parsers.scala @@ -224,13 +224,18 @@ trait Parsers { lazy val Port = token(IntBasic, "") /** Parses a signed integer. */ - lazy val IntBasic = mapOrFail('-'.? ~ Digit.+)(Function.tupled(toInt)) + lazy val IntBasic = parseSigned(_.toInt) + + /** Parses a signed long. */ + lazy val LongBasic = parseSigned(_.toLong) /** Parses an unsigned integer. */ lazy val NatBasic = mapOrFail(Digit.+)(_.mkString.toInt) - private def toInt(neg: Option[Char], digits: Seq[Char]): Int = - (neg.toSeq ++ digits).mkString.toInt + private def parseSigned[A](f: String => A) = + mapOrFail('-'.? ~ Digit.+) { case (neg, digits) => + f((neg.toSeq ++ digits).mkString) + } /** Parses the lower-case values `true` and `false` into their corresponding Boolean values. */ lazy val Bool = ("true" ^^^ true) | ("false" ^^^ false) diff --git a/main/src/main/scala/sbt/BackgroundJobService.scala b/main/src/main/scala/sbt/BackgroundJobService.scala index a6f34c5d5..efaa43222 100644 --- a/main/src/main/scala/sbt/BackgroundJobService.scala +++ b/main/src/main/scala/sbt/BackgroundJobService.scala @@ -96,12 +96,12 @@ object BackgroundJobService { private[sbt] def jobIdParser: (State, Seq[JobHandle]) => Parser[Seq[JobHandle]] = { import DefaultParsers.* (state, handles) => { - val stringIdParser: Parser[Seq[String]] = Space ~> token( - NotSpace.examples(handles.map(_.id.toString).toSet), + val idParser: Parser[Seq[Long]] = Space ~> token( + LongBasic.examples(handles.map(_.id.toString).toSet), description = "" ).+ - stringIdParser.map { strings => - strings.map(Integer.parseInt(_)).flatMap(id => handles.find(_.id == id)) + idParser.map { ids => + ids.flatMap(id => handles.find(_.id == id)) } } }