From 50f649c3c9a77b9aed414ae482f4502474a7aab2 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Wed, 21 Oct 2020 13:38:49 -0700 Subject: [PATCH 1/3] Fix SimpleInputStream reads into array When the read methods of InputStream that take an Array[Byte] as input are called, they are supposed to return -1 if there are no bytes available due to an EOF. Previously it was incorrectly writing the -1 as a byte in the array and returning 1. Now it correctly returns -1. The condition for closing the WriteableInputStream was also incorrect. We should only close the input stream if it returns -1 in raw mode. Fixes #5999 --- .../src/main/scala/sbt/internal/util/Terminal.scala | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/Terminal.scala b/internal/util-logging/src/main/scala/sbt/internal/util/Terminal.scala index 4c6d48a5d..0c336fa2d 100644 --- a/internal/util-logging/src/main/scala/sbt/internal/util/Terminal.scala +++ b/internal/util-logging/src/main/scala/sbt/internal/util/Terminal.scala @@ -486,7 +486,7 @@ object Terminal { val _ = readQueue.take val b = in.read buffer.put(b) - if (Thread.interrupted() || (b != -1 && !isRaw.get)) closed.set(true) + if (Thread.interrupted() || (b == -1 && isRaw.get)) closed.set(true) else impl() } try impl() @@ -596,9 +596,12 @@ object Terminal { private[sbt] trait SimpleInputStream extends InputStream { override def read(b: Array[Byte]): Int = read(b, 0, b.length) override def read(b: Array[Byte], off: Int, len: Int): Int = { - val byte = read() - b(off) = byte.toByte - 1 + read() match { + case -1 => -1 + case byte => + b(off) = byte.toByte + 1 + } } } private[this] object proxyInputStream extends SimpleInputStream { From a0db985c36f371ddee6ffd8f488e977ac5108cb2 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Wed, 21 Oct 2020 13:58:38 -0700 Subject: [PATCH 2/3] Fix canonical input for network client It is valid for the thin client input stream to return -1 as an EOF when the user inputs ctrl+d in canonical mode. --- .../scala/sbt/internal/client/NetworkClient.scala | 9 +++------ .../scala/sbt/internal/server/NetworkChannel.scala | 12 ++++-------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala index aa3718717..2b76b1205 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -934,12 +934,9 @@ class NetworkClient( val stopped = new AtomicBoolean(false) override final def run(): Unit = { def read(): Unit = { - inputStream.read match { - case -1 => - case b => - inLock.synchronized(stdinBytes.offer(b)) - if (attached.get()) drain() - } + val b = inputStream.read + inLock.synchronized(stdinBytes.offer(b)) + if (attached.get()) drain() } try read() catch { case _: InterruptedException | NonFatal(_) => stopped.set(true) } finally { diff --git a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala index 2e4cec4c0..5168b55bf 100644 --- a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -11,7 +11,6 @@ package server import java.io.{ IOException, InputStream, OutputStream } import java.net.{ Socket, SocketTimeoutException } -import java.nio.channels.ClosedChannelException import java.util.concurrent.{ ConcurrentHashMap, Executors, @@ -85,7 +84,7 @@ final class NetworkChannel( private var initialized = false private val pendingRequests: mutable.Map[String, JsonRpcRequestMessage] = mutable.Map() - private[this] val inputBuffer = new LinkedBlockingQueue[Byte]() + private[this] val inputBuffer = new LinkedBlockingQueue[Int]() private[this] val pendingWrites = new LinkedBlockingQueue[(Array[Byte], Boolean)]() private[this] val attached = new AtomicBoolean(false) private[this] val alive = new AtomicBoolean(true) @@ -107,7 +106,7 @@ final class NetworkChannel( interactive.set(true) jsonRpcNotify(promptChannel, "") } - private[sbt] def write(byte: Byte) = inputBuffer.add(byte) + private[sbt] def write(byte: Byte) = inputBuffer.add(byte.toInt) private[this] val terminalHolder = new AtomicReference[Terminal](Terminal.NullTerminal) override private[sbt] def terminal: Terminal = terminalHolder.get @@ -634,15 +633,12 @@ final class NetworkChannel( ) } - private[this] lazy val inputStream: InputStream = new InputStream { + private[this] lazy val inputStream: InputStream = new Terminal.SimpleInputStream { override def read(): Int = { import sjsonnew.BasicJsonProtocol._ try { jsonRpcNotify(readSystemIn, "") - inputBuffer.take & 0xFF match { - case -1 => throw new ClosedChannelException() - case b => b - } + inputBuffer.take } catch { case e: IOException => try jsonRpcNotify(cancelReadSystemIn, "") From 7894938c7d116487efa4b64dd6fa6cab46dd0548 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Wed, 21 Oct 2020 14:03:36 -0700 Subject: [PATCH 3/3] Revert "Don't ever use jline 3 dumb terminal" This reverts commit 6f63b2ccfadf3e3429000ae51bac58579cae852c. It turns out the DumbTerminal does work in some cases such as when the TERM environment variable is set to DUMB. --- .../src/main/scala/sbt/internal/util/JLine3.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/JLine3.scala b/internal/util-logging/src/main/scala/sbt/internal/util/JLine3.scala index 27e4a8ddb..a80d81410 100644 --- a/internal/util-logging/src/main/scala/sbt/internal/util/JLine3.scala +++ b/internal/util-logging/src/main/scala/sbt/internal/util/JLine3.scala @@ -16,7 +16,7 @@ import org.jline.utils.{ ClosedException, NonBlockingReader } import org.jline.terminal.{ Attributes, Size, Terminal => JTerminal } import org.jline.terminal.Attributes.{ InputFlag, LocalFlag } import org.jline.terminal.Terminal.SignalHandler -import org.jline.terminal.impl.AbstractTerminal +import org.jline.terminal.impl.{ AbstractTerminal, DumbTerminal } import org.jline.terminal.impl.jansi.JansiSupportImpl import org.jline.terminal.impl.jansi.win.JansiWinSysTerminal import org.jline.utils.OSUtils @@ -73,6 +73,11 @@ private[sbt] object JLine3 { term } private[sbt] def apply(term: Terminal): JTerminal = { + if (System.getProperty("jline.terminal", "") == "none") + new DumbTerminal(term.inputStream, term.outputStream) + else wrapTerminal(term) + } + private[this] def wrapTerminal(term: Terminal): JTerminal = { new AbstractTerminal( term.name, "nocapabilities",