Fix exhaustive when in JsonUtf8Reader (#1473)

* Fix exhaustive when in JsonUtf8Reader

This accidentally snuck into master because it predates the Kotlin 1.6 upgrade

* Fix nextInt too

* Clean up a few little warnings opportunistically

* Fix comment

* Use knownNotNull
This commit is contained in:
Zac Sweers
2021-12-26 17:07:52 -06:00
committed by GitHub
parent c49daf907b
commit f0b886f129
2 changed files with 63 additions and 27 deletions

View File

@@ -52,10 +52,12 @@ tasks.withType<Test>().configureEach {
tasks.withType<KotlinCompile>() tasks.withType<KotlinCompile>()
.configureEach { .configureEach {
kotlinOptions { kotlinOptions {
val args = mutableListOf("-Xopt-in=kotlin.contracts.ExperimentalContracts")
if (name.contains("test", true)) { if (name.contains("test", true)) {
@Suppress("SuspiciousCollectionReassignment") // It's not suspicious args += "-Xopt-in=kotlin.ExperimentalStdlibApi"
freeCompilerArgs += listOf("-Xopt-in=kotlin.ExperimentalStdlibApi")
} }
@Suppress("SuspiciousCollectionReassignment") // It's not suspicious
freeCompilerArgs += args
} }
} }

View File

@@ -23,6 +23,7 @@ import okio.buffer
import java.io.EOFException import java.io.EOFException
import java.io.IOException import java.io.IOException
import java.math.BigDecimal import java.math.BigDecimal
import kotlin.contracts.contract
internal class JsonUtf8Reader : JsonReader { internal class JsonUtf8Reader : JsonReader {
/** The input JSON. */ /** The input JSON. */
@@ -237,13 +238,13 @@ internal class JsonUtf8Reader : JsonReader {
// "fallthrough" from previous `when` // "fallthrough" from previous `when`
when (nextNonWhitespace(true).toChar()) { when (nextNonWhitespace(true).toChar()) {
']' -> { ']' -> {
if (peekStack == JsonScope.EMPTY_ARRAY) {
buffer.readByte() // Consume ']'.
return setPeeked(PEEKED_END_ARRAY)
}
// In lenient mode, a 0-length literal in an array means 'null'.
return when (peekStack) { return when (peekStack) {
JsonScope.EMPTY_ARRAY, JsonScope.NONEMPTY_ARRAY -> { JsonScope.EMPTY_ARRAY -> {
buffer.readByte() // Consume ']'.
setPeeked(PEEKED_END_ARRAY)
}
JsonScope.NONEMPTY_ARRAY -> {
// In lenient mode, a 0-length literal in an array means 'null'.
checkLenient() checkLenient()
setPeeked(PEEKED_NULL) setPeeked(PEEKED_NULL)
} }
@@ -320,11 +321,11 @@ internal class JsonUtf8Reader : JsonReader {
// Confirm that chars [1..length) match the keyword. // Confirm that chars [1..length) match the keyword.
val length = keyword.length val length = keyword.length
for (i in 1 until length) { for (i in 1 until length) {
val i_long = i.toLong() val iAsLong = i.toLong()
if (!source.request(i_long + 1)) { if (!source.request(iAsLong + 1)) {
return PEEKED_NONE return PEEKED_NONE
} }
c = buffer[i_long].asChar() c = buffer[iAsLong].asChar()
if (c != keyword[i] && c != keywordUpper[i]) { if (c != keyword[i] && c != keywordUpper[i]) {
return PEEKED_NONE return PEEKED_NONE
} }
@@ -622,19 +623,22 @@ internal class JsonUtf8Reader : JsonReader {
pathIndices[stackSize - 1]++ pathIndices[stackSize - 1]++
return peekedLong.toDouble() return peekedLong.toDouble()
} }
val next = when { val next = when (p) {
p == PEEKED_NUMBER -> buffer.readUtf8(peekedNumberLength.toLong()) PEEKED_NUMBER -> buffer.readUtf8(peekedNumberLength.toLong()).also { peekedString = it }
p == PEEKED_DOUBLE_QUOTED -> nextQuotedValue(DOUBLE_QUOTE_OR_SLASH) PEEKED_DOUBLE_QUOTED -> nextQuotedValue(DOUBLE_QUOTE_OR_SLASH).also { peekedString = it }
p == PEEKED_SINGLE_QUOTED -> nextQuotedValue(SINGLE_QUOTE_OR_SLASH) PEEKED_SINGLE_QUOTED -> nextQuotedValue(SINGLE_QUOTE_OR_SLASH).also { peekedString = it }
p == PEEKED_UNQUOTED -> nextUnquotedValue() PEEKED_UNQUOTED -> nextUnquotedValue().also { peekedString = it }
p != PEEKED_BUFFERED -> throw JsonDataException("Expected a double but was " + peek() + " at path " + path) PEEKED_BUFFERED -> {
// PEEKED_BUFFERED means the value's been stored in peekedString
knownNotNull(peekedString)
}
else -> throw JsonDataException("Expected a double but was " + peek() + " at path " + path)
} }
peekedString = next
peeked = PEEKED_BUFFERED peeked = PEEKED_BUFFERED
val result = try { val result = try {
next.toDouble() next.toDouble()
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
throw JsonDataException("Expected a double but was $peekedString at path $path") throw JsonDataException("Expected a double but was $next at path $path")
} }
if (!lenient && (result.isNaN() || result.isInfinite())) { if (!lenient && (result.isNaN() || result.isInfinite())) {
throw JsonEncodingException("JSON forbids NaN and infinities: $result at path $path") throw JsonEncodingException("JSON forbids NaN and infinities: $result at path $path")
@@ -755,31 +759,42 @@ internal class JsonUtf8Reader : JsonReader {
pathIndices[stackSize - 1]++ pathIndices[stackSize - 1]++
return result return result
} }
when (p) { val next: String = when (p) {
PEEKED_NUMBER -> peekedString = buffer.readUtf8(peekedNumberLength.toLong()) PEEKED_NUMBER -> {
buffer.readUtf8(peekedNumberLength.toLong()).also { peekedString = it }
}
PEEKED_DOUBLE_QUOTED, PEEKED_SINGLE_QUOTED -> { PEEKED_DOUBLE_QUOTED, PEEKED_SINGLE_QUOTED -> {
val next = if (p == PEEKED_DOUBLE_QUOTED) nextQuotedValue(DOUBLE_QUOTE_OR_SLASH) else nextQuotedValue(SINGLE_QUOTE_OR_SLASH) val next = if (p == PEEKED_DOUBLE_QUOTED) {
nextQuotedValue(DOUBLE_QUOTE_OR_SLASH)
} else {
nextQuotedValue(SINGLE_QUOTE_OR_SLASH)
}
peekedString = next
try { try {
val result = next.toInt() val result = next.toInt()
peekedString = next
peeked = PEEKED_NONE peeked = PEEKED_NONE
pathIndices[stackSize - 1]++ pathIndices[stackSize - 1]++
return result return result
} catch (ignored: NumberFormatException) { } catch (ignored: NumberFormatException) {
// Fall back to parse as a double below. // Fall back to parse as a double below.
next
} }
} }
else -> if (p != PEEKED_BUFFERED) throw JsonDataException("Expected an int but was ${peek()} at path $path") PEEKED_BUFFERED -> {
// PEEKED_BUFFERED means the value's been stored in peekedString
knownNotNull(peekedString)
}
else -> throw JsonDataException("Expected an int but was ${peek()} at path $path")
} }
peeked = PEEKED_BUFFERED peeked = PEEKED_BUFFERED
val asDouble = try { val asDouble = try {
peekedString!!.toDouble() next.toDouble()
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
throw JsonDataException("Expected an int but was $peekedString at path $path") throw JsonDataException("Expected an int but was $next at path $path")
} }
val result = asDouble.toInt() val result = asDouble.toInt()
if (result.toDouble() != asDouble) { // Make sure no precision was lost casting to 'int'. if (result.toDouble() != asDouble) { // Make sure no precision was lost casting to 'int'.
throw JsonDataException("Expected an int but was $peekedString at path $path") throw JsonDataException("Expected an int but was $next at path $path")
} }
peekedString = null peekedString = null
peeked = PEEKED_NONE peeked = PEEKED_NONE
@@ -1026,11 +1041,13 @@ internal class JsonUtf8Reader : JsonReader {
} }
} }
@Suppress("NOTHING_TO_INLINE")
private inline fun peekIfNone(): Int { private inline fun peekIfNone(): Int {
val p = peeked val p = peeked
return if (p == PEEKED_NONE) doPeek() else p return if (p == PEEKED_NONE) doPeek() else p
} }
@Suppress("NOTHING_TO_INLINE")
private inline fun setPeeked(peekedType: Int): Int { private inline fun setPeeked(peekedType: Int): Int {
peeked = peekedType peeked = peekedType
return peekedType return peekedType
@@ -1079,4 +1096,21 @@ internal class JsonUtf8Reader : JsonReader {
} }
} }
@Suppress("NOTHING_TO_INLINE")
private inline fun Byte.asChar(): Char = toInt().toChar() private inline fun Byte.asChar(): Char = toInt().toChar()
// Sneaky backdoor way of marking a value as non-null to the compiler and skip the null-check intrinsic.
// Safe to use (unstable) contracts since they're gone in the final bytecode
// TODO move this to Util.kt after it's migrated to kotlin
@Suppress("NOTHING_TO_INLINE")
private inline fun <T> markNotNull(value: T?) {
contract {
returns() implies (value != null)
}
}
@Suppress("NOTHING_TO_INLINE")
private inline fun <T> knownNotNull(value: T?): T {
markNotNull(value)
return value
}