Skip to content

Commit 8b02a0c

Browse files
committed
refactor(util): fix ScanStateReducer precedence cascade
The previous when cascade used the pattern REGEX.find(line)?.let { ...; null } != null -> return ... which always evaluated to false on both arms (the let returned null on match, and short-circuited to null on no-match), making the right-side return dead code. Every matching regex's let body ran and the function fell through to the trailing blocks, destroying the first-match-wins precedence the original parseScanLine early-returns guaranteed. Test case 13 (scanTotalsPreemptsActiveResolversOnSameLine) would have observed activeResolvers=5 instead of the expected 0. Rewrite using the idiomatic Option A shape: pre-compute the 6 early-return match results (scanMatch, activeMatch, totalActiveMatch, remainingMatch, syncedMtuMatch, testingMtu) before the when, let the when mutate scanStatus for the first matching arm only, then a single if (anyMatched) return short-circuits the trailing MTU-completed / Session-initialized / SESSION_INIT_BACKOFF blocks. The 4 non-returning pre blocks (RESOLVER_ADDED, RESOLVER_REMOVED, INDEXED_PROGRESS, TOTAL_CANDIDATES) stay before the cascade, unchanged. Hand-traced cases 9, 10, 12, 13 line-by-line against the original parseScanLine; all produce identical state.
1 parent 985dfb0 commit 8b02a0c

1 file changed

Lines changed: 36 additions & 43 deletions

File tree

android/app/src/main/java/com/masterdns/vpn/util/ScanStateReducer.kt

Lines changed: 36 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,24 @@ internal object ScanStateReducer {
8686
}
8787
}
8888

89-
// ponytail: when-cascade with explicit `return` mirrors the original
90-
// parseScanLine early-returns so a line matching multiple patterns
91-
// only fires the first match (e.g. SCAN_TOTALS preempts ACTIVE_RESOLVERS).
89+
// ponytail: pre-compute the 6 early-return matches, then a when cascade
90+
// mutates scanStatus for the first matching arm only, then a single
91+
// guarded return short-circuits the trailing blocks. Mirrors the
92+
// original parseScanLine early-returns (SCAN_TOTALS, ACTIVE_RESOLVERS,
93+
// TOTAL_ACTIVE, REMAINING, SYNCED_MTU, "Testing MTU sizes") so a line
94+
// matching multiple patterns only fires the first match.
95+
val scanMatch = SCAN_TOTALS_REGEX.find(line)
96+
val activeMatch = ACTIVE_RESOLVERS_REGEX.find(line)
97+
val totalActiveMatch = TOTAL_ACTIVE_REGEX.find(line)
98+
val remainingMatch = REMAINING_REGEX.find(line)
99+
val syncedMtuMatch = SYNCED_MTU_REGEX.find(line)
100+
val testingMtu = line.contains("Testing MTU sizes", ignoreCase = true)
101+
92102
when {
93-
SCAN_TOTALS_REGEX.find(line)?.let { match ->
94-
val resolver = match.groupValues[1]
95-
val valid = match.groupValues[2].toIntOrNull() ?: scanStatus.validCount
96-
val rejected = match.groupValues[3].toIntOrNull() ?: scanStatus.rejectedCount
103+
scanMatch != null -> {
104+
val resolver = scanMatch!!.groupValues[1]
105+
val valid = scanMatch!!.groupValues[2].toIntOrNull() ?: scanStatus.validCount
106+
val rejected = scanMatch!!.groupValues[3].toIntOrNull() ?: scanStatus.rejectedCount
97107
val decision = when {
98108
line.contains("Accepted", ignoreCase = true) -> "Accepted"
99109
line.contains("Rejected", ignoreCase = true) -> "Rejected"
@@ -106,44 +116,27 @@ internal object ScanStateReducer {
106116
validCount = valid,
107117
rejectedCount = rejected
108118
)
109-
null
110-
} != null -> return ScanStateBundle(scanStatus, activeResolvers, connectionWarning)
111-
112-
ACTIVE_RESOLVERS_REGEX.find(line)?.let { match ->
113-
scanStatus = scanStatus.copy(
114-
activeResolvers = match.groupValues[1].toIntOrNull() ?: scanStatus.activeResolvers
115-
)
116-
null
117-
} != null -> return ScanStateBundle(scanStatus, activeResolvers, connectionWarning)
118-
119-
TOTAL_ACTIVE_REGEX.find(line)?.let { match ->
120-
scanStatus = scanStatus.copy(
121-
activeResolvers = match.groupValues[1].toIntOrNull() ?: scanStatus.activeResolvers
122-
)
123-
null
124-
} != null -> return ScanStateBundle(scanStatus, activeResolvers, connectionWarning)
125-
126-
REMAINING_REGEX.find(line)?.let { match ->
127-
scanStatus = scanStatus.copy(
128-
activeResolvers = match.groupValues[1].toIntOrNull() ?: scanStatus.activeResolvers
129-
)
130-
null
131-
} != null -> return ScanStateBundle(scanStatus, activeResolvers, connectionWarning)
132-
133-
SYNCED_MTU_REGEX.find(line)?.let { match ->
134-
scanStatus = scanStatus.copy(
135-
syncedUploadMtu = match.groupValues[1].toIntOrNull() ?: 0,
136-
syncedDownloadMtu = match.groupValues[2].toIntOrNull() ?: 0
137-
)
138-
null
139-
} != null -> return ScanStateBundle(scanStatus, activeResolvers, connectionWarning)
140-
141-
line.contains("Testing MTU sizes", ignoreCase = true) -> {
142-
scanStatus = scanStatus.copy(scanning = true)
143-
return ScanStateBundle(scanStatus, activeResolvers, connectionWarning)
144119
}
120+
activeMatch != null -> scanStatus = scanStatus.copy(
121+
activeResolvers = activeMatch!!.groupValues[1].toIntOrNull() ?: scanStatus.activeResolvers
122+
)
123+
totalActiveMatch != null -> scanStatus = scanStatus.copy(
124+
activeResolvers = totalActiveMatch!!.groupValues[1].toIntOrNull() ?: scanStatus.activeResolvers
125+
)
126+
remainingMatch != null -> scanStatus = scanStatus.copy(
127+
activeResolvers = remainingMatch!!.groupValues[1].toIntOrNull() ?: scanStatus.activeResolvers
128+
)
129+
syncedMtuMatch != null -> scanStatus = scanStatus.copy(
130+
syncedUploadMtu = syncedMtuMatch!!.groupValues[1].toIntOrNull() ?: 0,
131+
syncedDownloadMtu = syncedMtuMatch!!.groupValues[2].toIntOrNull() ?: 0
132+
)
133+
testingMtu -> scanStatus = scanStatus.copy(scanning = true)
134+
}
145135

146-
else -> Unit
136+
if (scanMatch != null || activeMatch != null || totalActiveMatch != null ||
137+
remainingMatch != null || syncedMtuMatch != null || testingMtu
138+
) {
139+
return ScanStateBundle(scanStatus, activeResolvers, connectionWarning)
147140
}
148141

149142
if (line.contains("MTU Testing Completed", ignoreCase = true) ||

0 commit comments

Comments
 (0)