Skip to content
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

Correctly handle buffer boundaries while decoding escape sequences from json stream #1706

Merged
merged 2 commits into from Nov 10, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -318,6 +318,9 @@ internal abstract class AbstractJsonLexer {
if (char == STRING_ESC) {
usedAppend = true
currentPosition = appendEscape(lastPosition, currentPosition)
currentPosition = definitelyNotEof(currentPosition)
sandwwraith marked this conversation as resolved.
Show resolved Hide resolved
if (currentPosition == -1)
fail("EOF", currentPosition)
qwwdfsad marked this conversation as resolved.
Show resolved Hide resolved
lastPosition = currentPosition
} else if (++currentPosition >= source.length) {
usedAppend = true
Expand Down Expand Up @@ -435,7 +438,13 @@ internal abstract class AbstractJsonLexer {
}

private fun appendHex(source: CharSequence, startPos: Int): Int {
if (startPos + 4 >= source.length) fail("Unexpected EOF during unicode escape")
if (startPos + 4 >= source.length) {
currentPosition = startPos
ensureHaveChars()
if (currentPosition + 4 >= source.length)
fail("Unexpected EOF during unicode escape")
return appendHex(source, currentPosition)
}
escapedString.append(
((fromHexChar(source, startPos) shl 12) +
(fromHexChar(source, startPos + 1) shl 8) +
Expand Down
@@ -1,8 +1,11 @@
/*
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.serialization.json

import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.json.internal.*
import kotlinx.serialization.test.*
import kotlin.random.*
import kotlin.test.*
Expand Down Expand Up @@ -59,7 +62,7 @@ class JsonUnicodeTest : JsonTestBase() {
@Test
fun testRandomEscapeSequences() = noJs { // Too slow on JS
repeat(10_000) {
val s = generateRandomString()
val s = generateRandomUnicodeString(Random.nextInt(1, 2047))
try {
assertSerializedAndRestored(s, String.serializer())
} catch (e: Throwable) {
Expand All @@ -68,21 +71,4 @@ class JsonUnicodeTest : JsonTestBase() {
}
}
}

private fun generateRandomString(): String {
val size = Random.nextInt(1, 2047)
return buildString(size) {
repeat(size) {
val pickEscape = Random.nextBoolean()
if (pickEscape) {
// Definitely escape symbol
// null can be appended as well, completely okay
append(ESCAPE_STRINGS.random())
} else {
// Any symbol, including escaping one
append(Char(Random.nextInt(Char.MIN_VALUE.code..Char.MAX_VALUE.code)))
}
}
}
}
}
@@ -1,10 +1,13 @@
/*
* Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.serialization.test

import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.json.internal.ESCAPE_STRINGS
import kotlin.random.Random
import kotlin.random.nextInt
import kotlin.test.*

fun SerialDescriptor.assertDescriptorEqualsTo(other: SerialDescriptor) {
Expand Down Expand Up @@ -40,3 +43,18 @@ inline fun assertFailsWithMissingField(block: () -> Unit) {
val e = assertFailsWith<SerializationException>(block = block)
assertTrue(e.message?.contains("but it was missing") ?: false)
}

fun generateRandomUnicodeString(size: Int): String {
return buildString(size) {
repeat(size) {
val pickEscape = Random.nextBoolean()
if (pickEscape) {
// Definitely escape symbol
sandwwraith marked this conversation as resolved.
Show resolved Hide resolved
append(ESCAPE_STRINGS.random().takeIf { it != null } ?: 'N')
} else {
// Any symbol, including escaping one
append(Char(Random.nextInt(Char.MIN_VALUE.code..Char.MAX_VALUE.code)).takeIf { it.isDefined() && !it.isSurrogate()} ?: 'U')
}
}
}
}
Expand Up @@ -4,13 +4,15 @@

package kotlinx.serialization.features

import kotlinx.serialization.*
import kotlinx.serialization.SerializationException
import kotlinx.serialization.StringData
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.*
import kotlinx.serialization.json.internal.BATCH_SIZE
import kotlinx.serialization.test.decodeViaStream
import kotlinx.serialization.test.encodeViaStream
import kotlinx.serialization.test.*
import org.junit.Test
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

Expand Down Expand Up @@ -65,4 +67,22 @@ class JsonJvmStreamsTest {
Json.decodeViaStream(String.serializer(), "\"")
}
}

@Test
fun testRandomEscapeSequences() {
repeat(10_000) {
qwwdfsad marked this conversation as resolved.
Show resolved Hide resolved
val s = generateRandomUnicodeString(strLen)
try {
val serializer = String.serializer()
val b = ByteArrayOutputStream()
Json.encodeToStream(serializer, s, b)
val restored = Json.decodeFromStream(serializer, ByteArrayInputStream(b.toByteArray()))
assertEquals(s, restored)
} catch (e: Throwable) {
// Not assertion error to preserve cause
throw IllegalStateException("Unexpectedly failed test, cause string: $s", e)
}
}
}

}