Fix brackets calculation (#415)

This commit is contained in:
KirillPamPam
2024-02-08 11:37:57 +04:00
committed by GitHub
parent de7207840a
commit f1c3c07cd7
2 changed files with 104 additions and 28 deletions

View File

@@ -122,14 +122,14 @@ class JsonRpcStreamParser(
for (i in bytes.indices) { for (i in bytes.indices) {
when (whatCountValue) { when (whatCountValue) {
is CountObjectBrackets -> { is CountObjectBrackets -> {
countBrackets(bytes[i], whatCountValue.count, OBJECT_OPEN_BRACKET, OBJECT_CLOSE_BRACKET) countBrackets(bytes[i], whatCountValue, OBJECT_OPEN_BRACKET, OBJECT_CLOSE_BRACKET)
} }
is CountArrayBrackets -> { is CountArrayBrackets -> {
countBrackets(bytes[i], whatCountValue.count, ARRAY_OPEN_BRACKET, ARRAY_CLOSE_BRACKET) countBrackets(bytes[i], whatCountValue, ARRAY_OPEN_BRACKET, ARRAY_CLOSE_BRACKET)
} }
is CountSlashes -> { is CountQuotesAndSlashes -> {
countQuotesAndSlashes(bytes[i], whatCountValue) countQuotesAndSlashes(bytes[i], whatCountValue)
} }
} }
@@ -168,7 +168,7 @@ class JsonRpcStreamParser(
val token = parser.nextToken() val token = parser.nextToken()
val tokenStart = parser.tokenLocation.byteOffset.toInt() val tokenStart = parser.tokenLocation.byteOffset.toInt()
return if (token.isScalarValue) { return if (token.isScalarValue) {
val count = CountSlashes(AtomicInteger(1)) val count = CountQuotesAndSlashes(AtomicInteger(1))
whatCount.set(count) whatCount.set(count)
SingleResponse( SingleResponse(
processScalarValue(parser, tokenStart, firstBytes, count, endStream), processScalarValue(parser, tokenStart, firstBytes, count, endStream),
@@ -177,13 +177,16 @@ class JsonRpcStreamParser(
} else { } else {
when (token) { when (token) {
JsonToken.START_OBJECT -> { JsonToken.START_OBJECT -> {
val count = CountObjectBrackets(AtomicInteger(1)) val count = CountObjectBrackets(
AtomicInteger(1),
CountQuotesAndSlashes(AtomicInteger(0)),
)
whatCount.set(count) whatCount.set(count)
SingleResponse( SingleResponse(
processAndCountBrackets( processAndCountBrackets(
tokenStart, tokenStart,
firstBytes, firstBytes,
count.count, count,
endStream, endStream,
OBJECT_OPEN_BRACKET, OBJECT_OPEN_BRACKET,
OBJECT_CLOSE_BRACKET, OBJECT_CLOSE_BRACKET,
@@ -193,13 +196,16 @@ class JsonRpcStreamParser(
} }
JsonToken.START_ARRAY -> { JsonToken.START_ARRAY -> {
val count = CountArrayBrackets(AtomicInteger(1)) val count = CountArrayBrackets(
AtomicInteger(1),
CountQuotesAndSlashes(AtomicInteger(0)),
)
whatCount.set(count) whatCount.set(count)
SingleResponse( SingleResponse(
processAndCountBrackets( processAndCountBrackets(
tokenStart, tokenStart,
firstBytes, firstBytes,
count.count, count,
endStream, endStream,
ARRAY_OPEN_BRACKET, ARRAY_OPEN_BRACKET,
ARRAY_CLOSE_BRACKET, ARRAY_CLOSE_BRACKET,
@@ -229,14 +235,14 @@ class JsonRpcStreamParser(
private fun processAndCountBrackets( private fun processAndCountBrackets(
tokenStart: Int, tokenStart: Int,
bytes: ByteArray, bytes: ByteArray,
brackets: AtomicInteger, countBrackets: CountBrackets,
endStream: AtomicBoolean, endStream: AtomicBoolean,
openBracket: Byte, openBracket: Byte,
closeBracket: Byte, closeBracket: Byte,
): ByteArray { ): ByteArray {
for (i in tokenStart + 1 until bytes.size) { for (i in tokenStart + 1 until bytes.size) {
countBrackets(bytes[i], brackets, openBracket, closeBracket) countBrackets(bytes[i], countBrackets, openBracket, closeBracket)
if (brackets.get() == 0) { if (countBrackets.isFinished()) {
endStream.set(true) endStream.set(true)
return Arrays.copyOfRange(bytes, tokenStart, i + 1) return Arrays.copyOfRange(bytes, tokenStart, i + 1)
} }
@@ -246,27 +252,39 @@ class JsonRpcStreamParser(
private fun countBrackets( private fun countBrackets(
byte: Byte, byte: Byte,
brackets: AtomicInteger, countBrackets: CountBrackets,
openBracket: Byte, openBracket: Byte,
closeBracket: Byte, closeBracket: Byte,
) { ) {
// we encounter the quote and the start of a string value
if (byte == QUOTE && countBrackets.isCountQuotesAndSlashesFinished()) {
countBrackets.quotesIncrement()
return
}
// we ignore all braces inside a string value until we get to the end of the value
if (!countBrackets.isCountQuotesAndSlashesFinished()) {
countQuotesAndSlashes(byte, countBrackets.countQuotesAndSlashes)
return
}
if (byte == openBracket) { if (byte == openBracket) {
brackets.incrementAndGet() countBrackets.increment()
} else if (byte == closeBracket) { } else if (byte == closeBracket) {
brackets.decrementAndGet() countBrackets.decrement()
} }
} }
private fun countQuotesAndSlashes( private fun countQuotesAndSlashes(
byte: Byte, byte: Byte,
countSlashes: CountSlashes, countQuotesAndSlashes: CountQuotesAndSlashes,
) { ) {
if (byte == BACKSLASH && !countSlashes.hasSlash()) { if (byte == BACKSLASH && !countQuotesAndSlashes.hasSlash()) {
countSlashes.count.incrementAndGet() countQuotesAndSlashes.increment()
} else if (countSlashes.hasSlash()) { } else if (countQuotesAndSlashes.hasSlash()) {
countSlashes.count.decrementAndGet() countQuotesAndSlashes.decrement()
} else if (!countSlashes.hasSlash() && byte == QUOTE) { } else if (!countQuotesAndSlashes.hasSlash() && byte == QUOTE) {
countSlashes.count.set(0) countQuotesAndSlashes.reset()
} }
} }
@@ -274,7 +292,7 @@ class JsonRpcStreamParser(
parser: JsonParser, parser: JsonParser,
tokenStart: Int, tokenStart: Int,
bytes: ByteArray, bytes: ByteArray,
countSlashes: CountSlashes, countQuotesAndSlashes: CountQuotesAndSlashes,
endStream: AtomicBoolean, endStream: AtomicBoolean,
): ByteArray { ): ByteArray {
when (parser.currentToken) { when (parser.currentToken) {
@@ -284,8 +302,8 @@ class JsonRpcStreamParser(
} }
JsonToken.VALUE_STRING -> { JsonToken.VALUE_STRING -> {
for (i in tokenStart + 1 until bytes.size) { for (i in tokenStart + 1 until bytes.size) {
countQuotesAndSlashes(bytes[i], countSlashes) countQuotesAndSlashes(bytes[i], countQuotesAndSlashes)
if (countSlashes.isFinished()) { if (countQuotesAndSlashes.isFinished()) {
endStream.set(true) endStream.set(true)
return Arrays.copyOfRange(bytes, tokenStart, i + 1) return Arrays.copyOfRange(bytes, tokenStart, i + 1)
} }
@@ -300,20 +318,45 @@ class JsonRpcStreamParser(
} }
private abstract class Count( private abstract class Count(
val count: AtomicInteger, protected val count: AtomicInteger,
) { ) {
open fun isFinished(): Boolean = count.get() == 0 open fun isFinished(): Boolean = count.get() == 0
fun increment() {
count.incrementAndGet()
}
fun decrement() {
count.decrementAndGet()
}
fun reset() {
count.set(0)
}
}
private open class CountBrackets(
countBrackets: AtomicInteger,
val countQuotesAndSlashes: CountQuotesAndSlashes,
) : Count(countBrackets) {
fun quotesIncrement() {
countQuotesAndSlashes.increment()
}
fun isCountQuotesAndSlashesFinished() = countQuotesAndSlashes.isFinished()
} }
private class CountArrayBrackets( private class CountArrayBrackets(
countBrackets: AtomicInteger, countBrackets: AtomicInteger,
) : Count(countBrackets) countQuotesAndSlashes: CountQuotesAndSlashes,
) : CountBrackets(countBrackets, countQuotesAndSlashes)
private class CountObjectBrackets( private class CountObjectBrackets(
countBrackets: AtomicInteger, countBrackets: AtomicInteger,
) : Count(countBrackets) countQuotesAndSlashes: CountQuotesAndSlashes,
) : CountBrackets(countBrackets, countQuotesAndSlashes)
private class CountSlashes( private class CountQuotesAndSlashes(
countSlashes: AtomicInteger, countSlashes: AtomicInteger,
) : Count(countSlashes) { ) : Count(countSlashes) {

View File

@@ -153,6 +153,39 @@ class JsonRpcStreamParserTest {
Chunk("}]".toByteArray(), true), Chunk("}]".toByteArray(), true),
), ),
), ),
Arguments.of(
listOf(
"{\"id\": 2,\"result\": [{\"na]me\": ".toByteArray(),
"\"bigName]".toByteArray(),
"super]NameGoin[]gOn]".toByteArray(),
"thi]sIsEnd\"".toByteArray(),
"}],".toByteArray(),
"\"field\": \"value\"}".toByteArray(),
),
listOf(
Chunk("[{\"na]me\": ".toByteArray(), false),
Chunk("\"bigName]".toByteArray(), false),
Chunk("super]NameGoin[]gOn]".toByteArray(), false),
Chunk("thi]sIsEnd\"".toByteArray(), false),
Chunk("}]".toByteArray(), true),
),
),
Arguments.of(
listOf(
"{\"id\": 2,\"result\": {\"name\": ".toByteArray(),
"\"bigName}".toByteArray(),
"so}me}{thi{}}ng\",".toByteArray(),
"\"ne}\\w}F}{}ield\" : \"that's}\\\" All\"".toByteArray(),
"}".toByteArray(),
),
listOf(
Chunk("{\"name\": ".toByteArray(), false),
Chunk("\"bigName}".toByteArray(), false),
Chunk("so}me}{thi{}}ng\",".toByteArray(), false),
Chunk("\"ne}\\w}F}{}ield\" : \"that's}\\\" All\"".toByteArray(), false),
Chunk("}".toByteArray(), true),
),
),
) )
} }
} }