Skip to content

Commit 141d71d

Browse files
committed
Fix Injekt TypeReference crash and harden null safety in handlers
- proguard: add -keep for all FullTypeReference subclasses so R8 cannot remove the anonymous objects that carry generic type info; this definitively fixes the IllegalArgumentException in SettingsEhScreen / RecommendsScreen - GlobalExceptionHandler: null-safe extra read instead of !! unwrap - BilibiliHandler: use firstOrNull() + error message instead of [0]!! - AzukiHandler: safe JSON navigation with mapIndexedNotNull (no more !! chains) - ComikeyHandler: safe og:url lookup and safe readingOrder navigation - GalleryAdder: replace manga!!/id!! double-unwrap with explicit early returns
1 parent 676a55c commit 141d71d

6 files changed

Lines changed: 41 additions & 22 deletions

File tree

app/proguard-rules.pro

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
# Keep generic type signatures required by Injekt (FullTypeReference uses Signature attribute)
44
-keepattributes Signature
55

6+
# Keep all anonymous FullTypeReference subclasses so R8 cannot remove them.
7+
# Injekt.get<T>() creates `object : FullTypeReference<T>() {}` at every call site;
8+
# if R8 removes those anonymous classes the Signature attribute is gone and
9+
# FullTypeReference.<init> throws IllegalArgumentException at runtime.
10+
-keep class * extends uy.kohesive.injekt.api.FullTypeReference { *; }
11+
612
-keep,allowoptimization class eu.kanade.**
713
-keep,allowoptimization class tachiyomi.**
814
-keep,allowoptimization class yokai.**

app/src/main/java/eu/kanade/tachiyomi/ui/crash/GlobalExceptionHandler.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,9 @@ class GlobalExceptionHandler private constructor(
7070

7171
fun getThrowableFromIntent(intent: Intent): Throwable? {
7272
return try {
73-
Json.decodeFromString(ThrowableSerializer, intent.getStringExtra(INTENT_EXTRA)!!)
74-
} catch (e: Exception) {
73+
val extra = intent.getStringExtra(INTENT_EXTRA) ?: return null
74+
Json.decodeFromString(ThrowableSerializer, extra)
75+
} catch (e: Throwable) {
7576
Logger.e(e) { "Wasn't able to retrieve throwable from intent" }
7677
null
7778
}

app/src/main/java/exh/GalleryAdder.kt

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,14 +140,18 @@ class GalleryAdder {
140140
}
141141

142142
// Fetch and update manga details from source
143-
val newMangaDetails = retry(retry) { source.getMangaDetails(manga!!) }
144-
manga!!.copyFrom(newMangaDetails)
145-
manga!!.initialized = true
146-
updateManga.await(manga!!.toMangaUpdate())
147-
manga = getManga.awaitById(manga!!.id!!)!!
143+
// manga is non-null here: either found in DB or inserted and re-fetched above
144+
val nonNullManga = manga ?: return GalleryAddEvent.Fail.Error(url, "Manga not found (Gallery: $url)")
145+
val newMangaDetails = retry(retry) { source.getMangaDetails(nonNullManga) }
146+
nonNullManga.copyFrom(newMangaDetails)
147+
nonNullManga.initialized = true
148+
updateManga.await(nonNullManga.toMangaUpdate())
149+
val mangaId = nonNullManga.id ?: return GalleryAddEvent.Fail.Error(url, "Manga has no ID (Gallery: $url)")
150+
manga = getManga.awaitById(mangaId)
151+
?: return GalleryAddEvent.Fail.Error(url, "Failed to reload manga after update (Gallery: $url)")
148152

149153
if (fav) {
150-
updateManga.await(MangaUpdate(id = manga.id!!, favorite = true))
154+
updateManga.await(MangaUpdate(id = manga.id ?: mangaId, favorite = true))
151155
manga.favorite = true
152156
}
153157

app/src/main/java/exh/md/handlers/AzukiHandler.kt

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,16 @@ class AzukiHandler(currentClient: OkHttpClient, userAgent: String) {
3232
}
3333

3434
fun pageListParse(response: Response): List<Page> {
35-
return Json.parseToJsonElement(response.body.string())
36-
.jsonObject["pages"]!!
37-
.jsonArray.mapIndexed { index, element ->
38-
val url = element.jsonObject["image_wm"]!!.jsonObject["webp"]!!.jsonArray[1].jsonObject["url"]!!.jsonPrimitive.content
39-
Page(index, url, url)
40-
}
35+
val pages = Json.parseToJsonElement(response.body.string())
36+
.jsonObject["pages"]
37+
?.jsonArray ?: throw Exception("Azuki: missing 'pages' in response")
38+
return pages.mapIndexedNotNull { index, element ->
39+
val url = element.jsonObject["image_wm"]
40+
?.jsonObject?.get("webp")
41+
?.jsonArray?.getOrNull(1)
42+
?.jsonObject?.get("url")
43+
?.jsonPrimitive?.content ?: return@mapIndexedNotNull null
44+
Page(index, url, url)
45+
}
4146
}
4247
}

app/src/main/java/exh/md/handlers/BilibiliHandler.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ class BilibiliHandler(currentClient: OkHttpClient) {
181181

182182
private fun imageUrlParse(response: Response): String {
183183
val result = response.parseAs<BilibiliResultDto<List<BilibiliPageDto>>>()
184-
val page = result.data!![0]
184+
val page = result.data?.firstOrNull()
185+
?: throw Exception("Bilibili: empty image token response")
185186

186187
return "${page.url}?token=${page.token}"
187188
}

app/src/main/java/exh/md/handlers/ComikeyHandler.kt

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ class ComikeyHandler(cloudflareClient: OkHttpClient, userAgent: String) {
3636

3737
suspend fun getMangaId(mangaUrl: String): Int {
3838
val response = client.newCall(GET("$baseUrl/read/$mangaUrl")).awaitSuccess()
39-
val url = response.asJsoup().selectFirst("meta[property=og:url]")!!.attr("content")
39+
val url = response.asJsoup().selectFirst("meta[property=og:url]")?.attr("content")
40+
?: throw Exception("Comikey: could not find og:url meta tag")
4041
return url.trimEnd('/').substringAfterLast('/').toInt()
4142
}
4243

@@ -55,11 +56,12 @@ class ComikeyHandler(cloudflareClient: OkHttpClient, userAgent: String) {
5556
}
5657

5758
fun pageListParse(response: Response): List<Page> {
58-
return Json.parseToJsonElement(response.body.string())
59-
.jsonObject["readingOrder"]!!
60-
.jsonArray.mapIndexed { index, element ->
61-
val url = element.jsonObject["href"]!!.jsonPrimitive.content
62-
Page(index, url, url)
63-
}
59+
val order = Json.parseToJsonElement(response.body.string())
60+
.jsonObject["readingOrder"]
61+
?.jsonArray ?: throw Exception("Comikey: missing 'readingOrder' in response")
62+
return order.mapIndexedNotNull { index, element ->
63+
val url = element.jsonObject["href"]?.jsonPrimitive?.content ?: return@mapIndexedNotNull null
64+
Page(index, url, url)
65+
}
6466
}
6567
}

0 commit comments

Comments
 (0)