diff --git a/CHANGELOG.md b/CHANGELOG.md index c0a378f..0a4e4e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ +# 1.0.6 +- Fix a crash on startup or during backup when backup data is corrupted. ([HeatherComputer/AdvancedBackups#110](https://github.com/HeatherComputer/AdvancedBackups/issues/110)) + +* * * + # 1.0.5 -- Fix a rare crash when entering a singleplayer world. ([HeatherComputer/AdvancedBackups#110](https://github.com/HeatherComputer/AdvancedBackups/issues/110)) +- Fix a rare crash when entering a singleplayer world. ([HeatherComputer/AdvancedBackups#87](https://github.com/HeatherComputer/AdvancedBackups/issues/87)) - Fix a potential crash during backup when a backup directory is missing. - Fix a potential crash when cleaning up old backup files. diff --git a/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ABCoreMixin.java b/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ABCoreMixin.java new file mode 100644 index 0000000..bde9f47 --- /dev/null +++ b/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ABCoreMixin.java @@ -0,0 +1,42 @@ +package com.github.gtexpert.advancedbackupspatch.mixin; + +import computer.heather.advancedbackups.core.ABCore; +import computer.heather.advancedbackups.core.backups.gson.BackupManifest; + +import com.google.gson.Gson; +import com.google.gson.JsonParseException; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(value = ABCore.class, remap = false) +public abstract class ABCoreMixin { + + /** + * Prevent NPE when parsing a corrupted backup manifest in setActivity. + *
+ * The JAR v3.7.1 only catches {@link JsonParseException}, but {@code gson.fromJson()} + * can return null or a manifest with null fields for certain malformed JSON, + * causing a {@link NullPointerException} when accessing {@code manifest.general.activity}. + *
+ * This redirect converts the null case into a {@link JsonParseException},
+ * which is already caught and handled by the existing recovery logic.
+ *
+ * @see Issue #110
+ */
+ @Redirect(method = "setActivity",
+ at = @At(value = "INVOKE",
+ target = "Lcom/google/gson/Gson;fromJson(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;"))
+ private static Object safeFromJson(Gson gson, String json, Class> classOfT) {
+ Object result = gson.fromJson(json, classOfT);
+ if (result == null) {
+ throw new JsonParseException("Backup manifest parsed as null");
+ }
+ BackupManifest manifest = (BackupManifest) result;
+ if (manifest.general == null) {
+ throw new JsonParseException("Backup manifest 'general' field is null");
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/AdvancedBackupsMixin.java b/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/AdvancedBackupsMixin.java
index 7e377c2..ecf17d3 100644
--- a/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/AdvancedBackupsMixin.java
+++ b/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/AdvancedBackupsMixin.java
@@ -32,6 +32,8 @@ public abstract class AdvancedBackupsMixin {
/**
* Disable EVENT_BUS.register(this) in the constructor.
* The mod container is not yet established at construction time, so defer to preInit.
+ *
+ * @see Issue #87
*/
@Redirect(method = "
+ * The JAR v3.7.1 only catches {@link JsonParseException}, but {@code gson.fromJson()}
+ * can return null (or a manifest with null fields) for certain malformed JSON,
+ * causing a {@link NullPointerException} when accessing {@code manifest.general.activity}.
+ *
+ * This redirect converts the null case into a {@link JsonParseException},
+ * which is already caught and handled by the existing recovery logic
+ * (creates default manifest and writes it back to disk).
+ *
+ * @see Issue #110
+ */
+ @Redirect(method = "checkStartupBackups",
+ at = @At(value = "INVOKE",
+ target = "Lcom/google/gson/Gson;fromJson(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;"))
+ private static Object safeFromJson(Gson gson, String json, Class> classOfT) {
+ Object result = gson.fromJson(json, classOfT);
+ if (result == null) {
+ throw new JsonParseException("Backup manifest parsed as null");
+ }
+ BackupManifest manifest = (BackupManifest) result;
+ if (manifest.general == null) {
+ throw new JsonParseException("Backup manifest 'general' field is null");
+ }
+ return result;
+ }
}
diff --git a/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ClientConfigManagerMixin.java b/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ClientConfigManagerMixin.java
index 3c095c7..37d45ac 100644
--- a/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ClientConfigManagerMixin.java
+++ b/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ClientConfigManagerMixin.java
@@ -25,6 +25,8 @@ public abstract class ClientConfigManagerMixin {
* which may not see logger writes from the IntegratedServer thread
* due to the Java Memory Model (non-volatile fields).
* This provides a fallback to prevent NPE.
+ *
+ * @see Issue #87
*/
@Inject(method = "loadOrCreateConfig", at = @At("HEAD"))
private static void ensureLoggers(CallbackInfo ci) {
diff --git a/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/CoreCommandSystemMixin.java b/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/CoreCommandSystemMixin.java
new file mode 100644
index 0000000..7f4a4ea
--- /dev/null
+++ b/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/CoreCommandSystemMixin.java
@@ -0,0 +1,42 @@
+package com.github.gtexpert.advancedbackupspatch.mixin;
+
+import computer.heather.advancedbackups.core.CoreCommandSystem;
+import computer.heather.advancedbackups.core.backups.gson.BackupManifest;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonParseException;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+@Mixin(value = CoreCommandSystem.class, remap = false)
+public abstract class CoreCommandSystemMixin {
+
+ /**
+ * Prevent NPE when parsing a corrupted backup manifest in resetChainLength.
+ *
+ * The JAR v3.7.1 only catches {@link JsonParseException}, but {@code gson.fromJson()}
+ * can return null or a manifest with null fields for certain malformed JSON,
+ * causing a {@link NullPointerException} when accessing manifest fields.
+ *
+ * This redirect converts the null case into a {@link JsonParseException},
+ * which is already caught and handled by the existing recovery logic.
+ *
+ * @see Issue #110
+ */
+ @Redirect(method = "resetChainLength",
+ at = @At(value = "INVOKE",
+ target = "Lcom/google/gson/Gson;fromJson(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;"))
+ private static Object safeFromJson(Gson gson, String json, Class> classOfT) {
+ Object result = gson.fromJson(json, classOfT);
+ if (result == null) {
+ throw new JsonParseException("Backup manifest parsed as null");
+ }
+ BackupManifest manifest = (BackupManifest) result;
+ if (manifest.incremental == null || manifest.differential == null) {
+ throw new JsonParseException("Backup manifest chain fields are null");
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ThreadedBackupMixin.java b/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ThreadedBackupMixin.java
index 6d12167..e09c9cc 100644
--- a/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ThreadedBackupMixin.java
+++ b/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ThreadedBackupMixin.java
@@ -3,6 +3,10 @@
import java.io.File;
import computer.heather.advancedbackups.core.backups.ThreadedBackup;
+import computer.heather.advancedbackups.core.backups.gson.BackupManifest;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonParseException;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@@ -14,6 +18,8 @@ public abstract class ThreadedBackupMixin {
/**
* Prevent NPE when File.list() returns null in performRename.
* This happens when the directory does not exist or an I/O error occurs.
+ *
+ * @see Issue #103
*/
@Redirect(method = "performRename",
at = @At(value = "INVOKE",
@@ -26,6 +32,8 @@ private String[] safeListInRename(File file) {
/**
* Prevent NPE when File.list() returns null in performDelete.
* This happens when the directory does not exist or an I/O error occurs.
+ *
+ * @see Issue #103
*/
@Redirect(method = "performDelete",
at = @At(value = "INVOKE",
@@ -34,4 +42,28 @@ private String[] safeListInDelete(File file) {
String[] result = file.list();
return result != null ? result : new String[0];
}
+
+ /**
+ * Prevent NPE when parsing a corrupted backup manifest in makeDifferentialOrIncrementalBackup.
+ *
+ * Same issue as in {@code BackupWrapper.checkStartupBackups()} — the JAR v3.7.1
+ * only catches {@link JsonParseException}, but {@code gson.fromJson()} can return null
+ * or a manifest with null fields, causing a {@link NullPointerException}.
+ *
+ * @see Issue #110
+ */
+ @Redirect(method = "makeDifferentialOrIncrementalBackup",
+ at = @At(value = "INVOKE",
+ target = "Lcom/google/gson/Gson;fromJson(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;"))
+ private Object safeFromJson(Gson gson, String json, Class> classOfT) {
+ Object result = gson.fromJson(json, classOfT);
+ if (result == null) {
+ throw new JsonParseException("Backup manifest parsed as null");
+ }
+ BackupManifest manifest = (BackupManifest) result;
+ if (manifest.differential == null || manifest.incremental == null) {
+ throw new JsonParseException("Backup manifest fields are null");
+ }
+ return result;
+ }
}
diff --git a/src/main/resources/mixins.advancedbackupspatch.json b/src/main/resources/mixins.advancedbackupspatch.json
index 5ae7042..aaf96b7 100644
--- a/src/main/resources/mixins.advancedbackupspatch.json
+++ b/src/main/resources/mixins.advancedbackupspatch.json
@@ -3,9 +3,11 @@
"package": "com.github.gtexpert.advancedbackupspatch.mixin",
"compatibilityLevel": "JAVA_8",
"mixins": [
+ "ABCoreMixin",
"AdvancedBackupsMixin",
"BackupWrapperMixin",
"ClientConfigManagerMixin",
+ "CoreCommandSystemMixin",
"ThreadedBackupMixin"
]
}