Skip to content

Commit 12c340e

Browse files
authored
Fix a crash on startup or during backup when backup data is corrupted (#5)
1 parent 2f91be3 commit 12c340e

8 files changed

Lines changed: 166 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
# 1.0.6
2+
- Fix a crash on startup or during backup when backup data is corrupted. ([HeatherComputer/AdvancedBackups#110](https://github.com/HeatherComputer/AdvancedBackups/issues/110))
3+
4+
* * *
5+
16
# 1.0.5
2-
- Fix a rare crash when entering a singleplayer world. ([HeatherComputer/AdvancedBackups#110](https://github.com/HeatherComputer/AdvancedBackups/issues/110))
7+
- Fix a rare crash when entering a singleplayer world. ([HeatherComputer/AdvancedBackups#87](https://github.com/HeatherComputer/AdvancedBackups/issues/87))
38
- Fix a potential crash during backup when a backup directory is missing.
49
- Fix a potential crash when cleaning up old backup files.
510

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.github.gtexpert.advancedbackupspatch.mixin;
2+
3+
import computer.heather.advancedbackups.core.ABCore;
4+
import computer.heather.advancedbackups.core.backups.gson.BackupManifest;
5+
6+
import com.google.gson.Gson;
7+
import com.google.gson.JsonParseException;
8+
9+
import org.spongepowered.asm.mixin.Mixin;
10+
import org.spongepowered.asm.mixin.injection.At;
11+
import org.spongepowered.asm.mixin.injection.Redirect;
12+
13+
@Mixin(value = ABCore.class, remap = false)
14+
public abstract class ABCoreMixin {
15+
16+
/**
17+
* Prevent NPE when parsing a corrupted backup manifest in setActivity.
18+
* <p>
19+
* The JAR v3.7.1 only catches {@link JsonParseException}, but {@code gson.fromJson()}
20+
* can return null or a manifest with null fields for certain malformed JSON,
21+
* causing a {@link NullPointerException} when accessing {@code manifest.general.activity}.
22+
* <p>
23+
* This redirect converts the null case into a {@link JsonParseException},
24+
* which is already caught and handled by the existing recovery logic.
25+
*
26+
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/110">Issue #110</a>
27+
*/
28+
@Redirect(method = "setActivity",
29+
at = @At(value = "INVOKE",
30+
target = "Lcom/google/gson/Gson;fromJson(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;"))
31+
private static Object safeFromJson(Gson gson, String json, Class<?> classOfT) {
32+
Object result = gson.fromJson(json, classOfT);
33+
if (result == null) {
34+
throw new JsonParseException("Backup manifest parsed as null");
35+
}
36+
BackupManifest manifest = (BackupManifest) result;
37+
if (manifest.general == null) {
38+
throw new JsonParseException("Backup manifest 'general' field is null");
39+
}
40+
return result;
41+
}
42+
}

src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/AdvancedBackupsMixin.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ public abstract class AdvancedBackupsMixin {
3232
/**
3333
* Disable EVENT_BUS.register(this) in the constructor.
3434
* The mod container is not yet established at construction time, so defer to preInit.
35+
*
36+
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/87">Issue #87</a>
3537
*/
3638
@Redirect(method = "<init>",
3739
at = @At(value = "INVOKE",
@@ -43,6 +45,8 @@ private void redirectEventBusRegister(EventBus bus, Object target) {
4345
/**
4446
* Disable NetworkHandler.registerMessages() in the constructor.
4547
* Defer network channel registration until the mod container is established.
48+
*
49+
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/87">Issue #87</a>
4650
*/
4751
@Redirect(method = "<init>",
4852
at = @At(value = "INVOKE",
@@ -54,6 +58,8 @@ private void redirectNetworkRegister() {
5458
/**
5559
* Register the event bus and network messages at the beginning of preInit,
5660
* where the mod container is properly established.
61+
*
62+
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/87">Issue #87</a>
5763
*/
5864
@Inject(method = "preInit", at = @At("HEAD"))
5965
private void onPreInit(FMLPreInitializationEvent event, CallbackInfo ci) {
@@ -65,6 +71,8 @@ private void onPreInit(FMLPreInitializationEvent event, CallbackInfo ci) {
6571
* Set ABCore loggers after preInit sets them on AdvancedBackups.
6672
* Without this, ABCore.infoLogger is null on the client side (only set in onServerStarting),
6773
* causing NPE in ClientConfigManager when receiving PacketToastTest.
74+
*
75+
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/87">Issue #87</a>
6876
*/
6977
@Inject(method = "preInit", at = @At("TAIL"))
7078
private void onPreInitTail(FMLPreInitializationEvent event, CallbackInfo ci) {

src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/BackupWrapperMixin.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
import java.io.File;
44

55
import computer.heather.advancedbackups.core.backups.BackupWrapper;
6+
import computer.heather.advancedbackups.core.backups.gson.BackupManifest;
7+
8+
import com.google.gson.Gson;
9+
import com.google.gson.JsonParseException;
610

711
import org.spongepowered.asm.mixin.Mixin;
812
import org.spongepowered.asm.mixin.injection.At;
@@ -22,4 +26,32 @@ private static File[] safeListFiles(File directory) {
2226
File[] result = directory.listFiles();
2327
return result != null ? result : new File[0];
2428
}
29+
30+
/**
31+
* Prevent NPE when parsing a corrupted backup manifest in checkStartupBackups.
32+
* <p>
33+
* The JAR v3.7.1 only catches {@link JsonParseException}, but {@code gson.fromJson()}
34+
* can return null (or a manifest with null fields) for certain malformed JSON,
35+
* causing a {@link NullPointerException} when accessing {@code manifest.general.activity}.
36+
* <p>
37+
* This redirect converts the null case into a {@link JsonParseException},
38+
* which is already caught and handled by the existing recovery logic
39+
* (creates default manifest and writes it back to disk).
40+
*
41+
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/110">Issue #110</a>
42+
*/
43+
@Redirect(method = "checkStartupBackups",
44+
at = @At(value = "INVOKE",
45+
target = "Lcom/google/gson/Gson;fromJson(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;"))
46+
private static Object safeFromJson(Gson gson, String json, Class<?> classOfT) {
47+
Object result = gson.fromJson(json, classOfT);
48+
if (result == null) {
49+
throw new JsonParseException("Backup manifest parsed as null");
50+
}
51+
BackupManifest manifest = (BackupManifest) result;
52+
if (manifest.general == null) {
53+
throw new JsonParseException("Backup manifest 'general' field is null");
54+
}
55+
return result;
56+
}
2557
}

src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ClientConfigManagerMixin.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public abstract class ClientConfigManagerMixin {
2525
* which may not see logger writes from the IntegratedServer thread
2626
* due to the Java Memory Model (non-volatile fields).
2727
* This provides a fallback to prevent NPE.
28+
*
29+
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/87">Issue #87</a>
2830
*/
2931
@Inject(method = "loadOrCreateConfig", at = @At("HEAD"))
3032
private static void ensureLoggers(CallbackInfo ci) {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.github.gtexpert.advancedbackupspatch.mixin;
2+
3+
import computer.heather.advancedbackups.core.CoreCommandSystem;
4+
import computer.heather.advancedbackups.core.backups.gson.BackupManifest;
5+
6+
import com.google.gson.Gson;
7+
import com.google.gson.JsonParseException;
8+
9+
import org.spongepowered.asm.mixin.Mixin;
10+
import org.spongepowered.asm.mixin.injection.At;
11+
import org.spongepowered.asm.mixin.injection.Redirect;
12+
13+
@Mixin(value = CoreCommandSystem.class, remap = false)
14+
public abstract class CoreCommandSystemMixin {
15+
16+
/**
17+
* Prevent NPE when parsing a corrupted backup manifest in resetChainLength.
18+
* <p>
19+
* The JAR v3.7.1 only catches {@link JsonParseException}, but {@code gson.fromJson()}
20+
* can return null or a manifest with null fields for certain malformed JSON,
21+
* causing a {@link NullPointerException} when accessing manifest fields.
22+
* <p>
23+
* This redirect converts the null case into a {@link JsonParseException},
24+
* which is already caught and handled by the existing recovery logic.
25+
*
26+
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/110">Issue #110</a>
27+
*/
28+
@Redirect(method = "resetChainLength",
29+
at = @At(value = "INVOKE",
30+
target = "Lcom/google/gson/Gson;fromJson(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;"))
31+
private static Object safeFromJson(Gson gson, String json, Class<?> classOfT) {
32+
Object result = gson.fromJson(json, classOfT);
33+
if (result == null) {
34+
throw new JsonParseException("Backup manifest parsed as null");
35+
}
36+
BackupManifest manifest = (BackupManifest) result;
37+
if (manifest.incremental == null || manifest.differential == null) {
38+
throw new JsonParseException("Backup manifest chain fields are null");
39+
}
40+
return result;
41+
}
42+
}

src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ThreadedBackupMixin.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
import java.io.File;
44

55
import computer.heather.advancedbackups.core.backups.ThreadedBackup;
6+
import computer.heather.advancedbackups.core.backups.gson.BackupManifest;
7+
8+
import com.google.gson.Gson;
9+
import com.google.gson.JsonParseException;
610

711
import org.spongepowered.asm.mixin.Mixin;
812
import org.spongepowered.asm.mixin.injection.At;
@@ -14,6 +18,8 @@ public abstract class ThreadedBackupMixin {
1418
/**
1519
* Prevent NPE when File.list() returns null in performRename.
1620
* This happens when the directory does not exist or an I/O error occurs.
21+
*
22+
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/103">Issue #103</a>
1723
*/
1824
@Redirect(method = "performRename",
1925
at = @At(value = "INVOKE",
@@ -26,6 +32,8 @@ private String[] safeListInRename(File file) {
2632
/**
2733
* Prevent NPE when File.list() returns null in performDelete.
2834
* This happens when the directory does not exist or an I/O error occurs.
35+
*
36+
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/103">Issue #103</a>
2937
*/
3038
@Redirect(method = "performDelete",
3139
at = @At(value = "INVOKE",
@@ -34,4 +42,28 @@ private String[] safeListInDelete(File file) {
3442
String[] result = file.list();
3543
return result != null ? result : new String[0];
3644
}
45+
46+
/**
47+
* Prevent NPE when parsing a corrupted backup manifest in makeDifferentialOrIncrementalBackup.
48+
* <p>
49+
* Same issue as in {@code BackupWrapper.checkStartupBackups()} — the JAR v3.7.1
50+
* only catches {@link JsonParseException}, but {@code gson.fromJson()} can return null
51+
* or a manifest with null fields, causing a {@link NullPointerException}.
52+
*
53+
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/110">Issue #110</a>
54+
*/
55+
@Redirect(method = "makeDifferentialOrIncrementalBackup",
56+
at = @At(value = "INVOKE",
57+
target = "Lcom/google/gson/Gson;fromJson(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;"))
58+
private Object safeFromJson(Gson gson, String json, Class<?> classOfT) {
59+
Object result = gson.fromJson(json, classOfT);
60+
if (result == null) {
61+
throw new JsonParseException("Backup manifest parsed as null");
62+
}
63+
BackupManifest manifest = (BackupManifest) result;
64+
if (manifest.differential == null || manifest.incremental == null) {
65+
throw new JsonParseException("Backup manifest fields are null");
66+
}
67+
return result;
68+
}
3769
}

src/main/resources/mixins.advancedbackupspatch.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
"package": "com.github.gtexpert.advancedbackupspatch.mixin",
44
"compatibilityLevel": "JAVA_8",
55
"mixins": [
6+
"ABCoreMixin",
67
"AdvancedBackupsMixin",
78
"BackupWrapperMixin",
89
"ClientConfigManagerMixin",
10+
"CoreCommandSystemMixin",
911
"ThreadedBackupMixin"
1012
]
1113
}

0 commit comments

Comments
 (0)