From 71fe19b298dc0814719e006c2e0031b0af0d1384 Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Tue, 7 Jul 2026 16:40:37 +0300 Subject: [PATCH 1/2] refactor: simplify local scan/branch/copy idioms in host, percent, query Four small behavior-preserving cleanups: - Replace two hand-rolled "first offending index" scan loops (firstForbiddenDomainIndex in host/HostParser, firstForbiddenHostIndex in host/OpaqueHost) with stdlib indexOfFirst. The manual loops existed mostly to host in-bounds check() postconditions that only asserted their own iteration mechanics; those are vacuous over a vetted stdlib call. - Collapse Ipv4.parsePart's two parallel hex/octal/decimal if/else chains (one selecting the radix, one selecting the digit substring) into a single `when`, carrying the digit substring so the common decimal path keeps using the part directly with no extra allocation. - Extract the byte-identical triplet-run materialization loop shared by PercentCodec's two run appenders into a private tripletRunBytes helper; the differing run-scan loops and surrounding logic stay put. - Drop a redundant defensive copy in the QueryParameters builder's build(): the QueryParameters constructor already snapshots via toList(), so build() can hand it the builder's list directly. Pure refactor: output unchanged, public API unchanged. --- .../org/dexpace/kuri/host/HostParser.kt | 13 ++------ .../kotlin/org/dexpace/kuri/host/Ipv4.kt | 20 ++++--------- .../org/dexpace/kuri/host/OpaqueHost.kt | 16 ++-------- .../org/dexpace/kuri/percent/PercentCodec.kt | 30 +++++++++++-------- .../kuri/query/QueryParametersBuilder.kt | 2 +- 5 files changed, 27 insertions(+), 54 deletions(-) diff --git a/kuri/src/commonMain/kotlin/org/dexpace/kuri/host/HostParser.kt b/kuri/src/commonMain/kotlin/org/dexpace/kuri/host/HostParser.kt index b8d0b88..9210166 100644 --- a/kuri/src/commonMain/kotlin/org/dexpace/kuri/host/HostParser.kt +++ b/kuri/src/commonMain/kotlin/org/dexpace/kuri/host/HostParser.kt @@ -133,17 +133,8 @@ internal object HostParser { } } - /** Index of the first forbidden-domain code point in [domain] (§7.6 [HOST-37]), or [NOT_FOUND]. */ - private fun firstForbiddenDomainIndex(domain: String): Int { - var index = 0 - var found = NOT_FOUND - while (index < domain.length && found == NOT_FOUND) { - if (isForbiddenDomainCodePoint(domain[index])) found = index - index++ - } - check(found == NOT_FOUND || found in domain.indices) { "found out of bounds: $found" } - return found - } + /** Index of the first forbidden-domain code point in [domain] (§7.6 [HOST-37]), or [NOT_FOUND] (`-1`). */ + private fun firstForbiddenDomainIndex(domain: String): Int = domain.indexOfFirst { isForbiddenDomainCodePoint(it) } // --- Uri profile (RFC 3986 §3.2.2 host grammar, §7.2/§7.5/§7.8) ------------------- diff --git a/kuri/src/commonMain/kotlin/org/dexpace/kuri/host/Ipv4.kt b/kuri/src/commonMain/kotlin/org/dexpace/kuri/host/Ipv4.kt index a8b2e8f..8b6908c 100644 --- a/kuri/src/commonMain/kotlin/org/dexpace/kuri/host/Ipv4.kt +++ b/kuri/src/commonMain/kotlin/org/dexpace/kuri/host/Ipv4.kt @@ -164,21 +164,11 @@ internal object Ipv4 { require(part.isNotEmpty()) { "empty part reached parsePart" } val hex = isHexPrefixed(part) val octal = !hex && part.length > 1 && part[0] == '0' - val radix = - if (hex) { - RADIX_HEX - } else if (octal) { - RADIX_OCTAL - } else { - RADIX_DECIMAL - } - val digits = - if (hex) { - part.substring(2) - } else if (octal) { - part.substring(1) - } else { - part + val (digits, radix) = + when { + hex -> part.substring(2) to RADIX_HEX + octal -> part.substring(1) to RADIX_OCTAL + else -> part to RADIX_DECIMAL } return parseInRadix(digits, radix) } diff --git a/kuri/src/commonMain/kotlin/org/dexpace/kuri/host/OpaqueHost.kt b/kuri/src/commonMain/kotlin/org/dexpace/kuri/host/OpaqueHost.kt index 86225be..00bc207 100644 --- a/kuri/src/commonMain/kotlin/org/dexpace/kuri/host/OpaqueHost.kt +++ b/kuri/src/commonMain/kotlin/org/dexpace/kuri/host/OpaqueHost.kt @@ -60,18 +60,6 @@ internal object OpaqueHost { * `%` is excluded from the scan because it is permitted in an opaque host ([HOST-33]); all * forbidden host code points are in the Basic Multilingual Plane, so a per-`Char` scan is exact. */ - private fun firstForbiddenHostIndex(input: String): Int { - var index = 0 - var found = NOT_FOUND - while (index < input.length && found == NOT_FOUND) { - val cp = input[index] - if (cp != '%' && isForbiddenHostCodePoint(cp)) { - found = index - } - index++ - } - check(found == NOT_FOUND || found in input.indices) { "found index out of bounds: $found" } - check(found == NOT_FOUND || input[found] != '%') { "'%' must not be flagged forbidden" } - return found - } + private fun firstForbiddenHostIndex(input: String): Int = + input.indexOfFirst { it != '%' && isForbiddenHostCodePoint(it) } } diff --git a/kuri/src/commonMain/kotlin/org/dexpace/kuri/percent/PercentCodec.kt b/kuri/src/commonMain/kotlin/org/dexpace/kuri/percent/PercentCodec.kt index b9dfd37..73aaa48 100644 --- a/kuri/src/commonMain/kotlin/org/dexpace/kuri/percent/PercentCodec.kt +++ b/kuri/src/commonMain/kotlin/org/dexpace/kuri/percent/PercentCodec.kt @@ -154,16 +154,7 @@ internal object PercentCodec { out.appendRange(input, start, start + TRIPLET_LENGTH) return start + TRIPLET_LENGTH } - val count = (end - start) / TRIPLET_LENGTH - check(count >= 1) { "a decode run must contain at least one non-ASCII triplet" } - val bytes = ByteArray(count) - var source = start - var target = 0 - while (target < count) { - bytes[target] = percentByteAt(input, source).toByte() - source += TRIPLET_LENGTH - target++ - } + val bytes = tripletRunBytes(input, start, end) val decoded = bytes.decodeToString() // A run decodes only when it is well-formed UTF-8: re-encoding the decoded text must // reproduce the exact octets. This preserves a genuinely-encoded U+FFFD (which round-trips) @@ -254,8 +245,22 @@ internal object PercentCodec { while (end < input.length && input[end] == '%' && hasPercentHexPairAt(input, end)) { end += TRIPLET_LENGTH } + out.append(tripletRunBytes(input, start, end).decodeToString()) + return end + } + + /** + * Materializes the triplet run `input[start, end)` into one decoded octet per `%HH` triplet + * ([PCT-25] gathering); [start]..[end] MUST span a whole, non-empty number of triplets. Shared by + * the lenient decode and RFC 3987 display-decode run appenders, whose surrounding logic differs. + */ + private fun tripletRunBytes( + input: String, + start: Int, + end: Int, + ): ByteArray { val count = (end - start) / TRIPLET_LENGTH - check(count >= 1) { "triplet run must contain at least one triplet" } + check(count >= 1) { "a triplet run must contain at least one triplet" } val bytes = ByteArray(count) var source = start var target = 0 @@ -264,7 +269,6 @@ internal object PercentCodec { source += TRIPLET_LENGTH target++ } - out.append(bytes.decodeToString()) - return end + return bytes } } diff --git a/kuri/src/commonMain/kotlin/org/dexpace/kuri/query/QueryParametersBuilder.kt b/kuri/src/commonMain/kotlin/org/dexpace/kuri/query/QueryParametersBuilder.kt index b95c011..664c1c7 100644 --- a/kuri/src/commonMain/kotlin/org/dexpace/kuri/query/QueryParametersBuilder.kt +++ b/kuri/src/commonMain/kotlin/org/dexpace/kuri/query/QueryParametersBuilder.kt @@ -135,7 +135,7 @@ public class QueryParametersBuilder internal constructor( * * @return a snapshot of the accumulated pairs; later mutation of this builder does not affect it. */ - public fun build(): QueryParameters = QueryParameters(pairs.toList()) + public fun build(): QueryParameters = QueryParameters(pairs) /** True when no pairs are accumulated; the O(1) check the `Uri`/`Url` builders use to collapse an emptied query. */ internal fun isEmpty(): Boolean = pairs.isEmpty() From 9ac5cbefcb6a46357c6353b951e34a0f090f1dfa Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Wed, 8 Jul 2026 14:07:11 +0300 Subject: [PATCH 2/2] refactor: make Ipv4.parsePart decimal path genuinely allocation-free The prior single-when form returned a (digits, radix) Pair, so every part allocated a Pair even though the common decimal case carries the input string unchanged. Drive both the radix and the prefix length to strip from one when into plain vals instead: the decimal path uses offset 0 and reuses part verbatim, allocating neither a substring nor a Pair. Replaces the bare 2/1 prefix lengths with named HEX_PREFIX_LENGTH / OCTAL_PREFIX_LENGTH constants. --- .../kotlin/org/dexpace/kuri/host/Ipv4.kt | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/kuri/src/commonMain/kotlin/org/dexpace/kuri/host/Ipv4.kt b/kuri/src/commonMain/kotlin/org/dexpace/kuri/host/Ipv4.kt index 8b6908c..dcb244f 100644 --- a/kuri/src/commonMain/kotlin/org/dexpace/kuri/host/Ipv4.kt +++ b/kuri/src/commonMain/kotlin/org/dexpace/kuri/host/Ipv4.kt @@ -48,6 +48,12 @@ private const val RADIX_OCTAL: Int = 8 /** Hexadecimal radix selected by a `0x`/`0X` part prefix. */ private const val RADIX_HEX: Int = 16 +/** Length of the `0x`/`0X` prefix stripped from a hexadecimal part before its digits ([HOST-21] step 3). */ +private const val HEX_PREFIX_LENGTH: Int = 2 + +/** Length of the single leading `0` stripped from an octal part before its digits ([HOST-21] step 3). */ +private const val OCTAL_PREFIX_LENGTH: Int = 1 + /** Sentinel returned by [parsePart]/[decOctetOrNull] for an unparsable part. */ private const val INVALID_PART: Long = -1L @@ -164,12 +170,25 @@ internal object Ipv4 { require(part.isNotEmpty()) { "empty part reached parsePart" } val hex = isHexPrefixed(part) val octal = !hex && part.length > 1 && part[0] == '0' - val (digits, radix) = - when { - hex -> part.substring(2) to RADIX_HEX - octal -> part.substring(1) to RADIX_OCTAL - else -> part to RADIX_DECIMAL + // One decision selects both the radix and the prefix length to strip; a decimal part carries + // offset 0 so it reuses `part` verbatim, materializing no substring on the common path. + val radix: Int + val prefixLength: Int + when { + hex -> { + radix = RADIX_HEX + prefixLength = HEX_PREFIX_LENGTH + } + octal -> { + radix = RADIX_OCTAL + prefixLength = OCTAL_PREFIX_LENGTH } + else -> { + radix = RADIX_DECIMAL + prefixLength = 0 + } + } + val digits = if (prefixLength == 0) part else part.substring(prefixLength) return parseInRadix(digits, radix) }