Skip to content

Commit ccd71b6

Browse files
committed
Fix scala-js/scala-js#3476: Specify that com messages must be valid UTF-16 strings.
* Document it in the Scaladoc of `startWithCom` * Assert that all messages are valid in the `TestKit` * Fix `ComTests.largeMessageTest` not to use an invalid message
1 parent 396acc1 commit ccd71b6

File tree

4 files changed

+46
-2
lines changed

4 files changed

+46
-2
lines changed

js-envs-test-kit/src/main/scala/org/scalajs/jsenv/test/ComTests.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,13 @@ private[test] class ComTests(config: JSEnvSuiteConfig) {
103103

104104
@Test
105105
def largeMessageTest: Unit = {
106-
// 1MB data
107-
replyTest(new String(Array.tabulate(1024 * 1024)(_.toChar)))
106+
/* 1MB data.
107+
* (i & 0x7f) limits the input to the ASCII repertoire, which will use
108+
* exactly 1 byte per Char in UTF-8. This restriction also ensures that we
109+
* do not introduce surrogate characters and therefore no invalid UTF-16
110+
* strings.
111+
*/
112+
replyTest(new String(Array.tabulate(1024 * 1024)(i => (i & 0x7f).toChar)))
108113
}
109114

110115
@Test

js-envs-test-kit/src/main/scala/org/scalajs/jsenv/test/kit/ComRun.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class ComRun private[kit] (run: JSComRun, out: IOReader, err: IOReader,
3131

3232
/** Calls [[JSComRun#send]] on the underlying run. */
3333
final def send(msg: String): this.type = {
34+
requireValidMessage(msg)
3435
run.send(msg)
3536
this
3637
}
@@ -41,12 +42,41 @@ class ComRun private[kit] (run: JSComRun, out: IOReader, err: IOReader,
4142
* @throws java.util.concurrent.TimeoutException if there is no message for too long.
4243
*/
4344
final def expectMsg(expected: String): this.type = {
45+
requireValidMessage(expected)
4446
require(!noMessages, "You may not call expectMsg after calling expectNoMsgs")
4547
val actual = msgs.waitOnMessage(timeout.fromNow)
4648
assertEquals("got bad message", expected, actual)
4749
this
4850
}
4951

52+
private def requireValidMessage(msg: String): Unit = {
53+
val len = msg.length
54+
var i = 0
55+
while (i < len) {
56+
val c = msg.charAt(i)
57+
58+
def fail(lowOrHigh: String): Nothing = {
59+
val msgDescription =
60+
if (len > 128) s"Message (of length $len)"
61+
else s"Message '$msg'"
62+
throw new IllegalArgumentException(
63+
s"$msgDescription is not a valid message because it contains an " +
64+
s"unpaired $lowOrHigh surrogate 0x${c.toInt.toHexString} at index $i")
65+
}
66+
67+
if (Character.isSurrogate(c)) {
68+
if (Character.isLowSurrogate(c))
69+
fail("low")
70+
else if (i == len - 1 || !Character.isLowSurrogate(msg.charAt(i + 1)))
71+
fail("high")
72+
else
73+
i += 2
74+
} else {
75+
i += 1
76+
}
77+
}
78+
}
79+
5080
/** Marks that no further messages are expected.
5181
*
5282
* This will make the methods [[closeRun]] / [[fails]] / [[succeeds]] fail if

js-envs/src/main/scala/org/scalajs/jsenv/JSEnv.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ trait JSEnv {
5151
* scalajsCom.send("my message");
5252
* }}}
5353
*
54+
* All messages, sent in both directions, must be valid UTF-16 strings,
55+
* i.e., they must not contain any unpaired surrogate character. The
56+
* behavior of a communication channel is unspecified if this requirement is
57+
* not met.
58+
*
5459
* We describe the expected message delivery guarantees by denoting the
5560
* transmitter as `t` and the receiver as `r`. Both the JVM and the JS end
5661
* act once as a transmitter and once as a receiver. These two

js-envs/src/main/scala/org/scalajs/jsenv/JSRuns.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ object JSRun {
6262
/** A [[JSRun]] that has a communication channel to the running JS code. */
6363
trait JSComRun extends JSRun {
6464
/** Sends a message to the JS end.
65+
*
66+
* The `msg` must be a valid UTF-16 string, i.e., it must not contain any
67+
* unpaired surrogate character. The behavior of the communication channel
68+
* is unspecified if this requirement is not met.
6569
*
6670
* Async, nothrow. See [[JSEnv#startWithCom]] for expected message delivery
6771
* guarantees.

0 commit comments

Comments
 (0)