Don't return invalid schema from SchemaBundle

SchemaBundle returns whatever GSON parses from input without checking
whether it has a database or not. For some reason that i cannot
discover, this code starts not throwing when invoked with kotlin 1.5 in
classpath (or one of its dependencies).

Nevertheless, it seems to be the right thing to throw if the read schema
bundle does not include a database. I've changed it to throw if schema
file does not have a database and also added logging to the path where
we overwrite the schema.

We should do a followup to add some validation here but I'm only
fixing SchemaBundle for now to unblock kotlin 1.5 update.

I've also updated database processor to catch all exceptions when
parsing schema, not just encoding exception.

Bug: 187208773
Test: DatabaseProcessorTest

Change-Id: I32c7dc31600b5a0a06653b7a713e6af8b7915cf6
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
index 1b39490..de7ea10 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
@@ -43,7 +43,6 @@
 import com.squareup.javapoet.TypeName
 import java.io.File
 import java.io.FileInputStream
-import java.io.UnsupportedEncodingException
 import java.util.Locale
 
 class DatabaseProcessor(baseContext: Context, val element: XTypeElement) {
@@ -168,7 +167,7 @@
             fun deserializeSchemaFile(fileInputStream: FileInputStream, versionNumber: Int): Any {
                 return try {
                     SchemaBundle.deserialize(fileInputStream).database
-                } catch (e: UnsupportedEncodingException) {
+                } catch (th: Throwable) {
                     invalidAutoMigrationSchema(
                         "$versionNumber.json",
                         schemaOutFolderPath
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/Database.kt b/room/compiler/src/main/kotlin/androidx/room/vo/Database.kt
index 7728626..8afced46d 100644
--- a/room/compiler/src/main/kotlin/androidx/room/vo/Database.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/Database.kt
@@ -17,10 +17,10 @@
 package androidx.room.vo
 
 import androidx.room.RoomMasterTable
-import androidx.room.migration.bundle.DatabaseBundle
-import androidx.room.migration.bundle.SchemaBundle
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XTypeElement
+import androidx.room.migration.bundle.DatabaseBundle
+import androidx.room.migration.bundle.SchemaBundle
 import com.squareup.javapoet.ClassName
 import org.apache.commons.codec.digest.DigestUtils
 import java.io.File
@@ -103,8 +103,21 @@
     fun exportSchema(file: File) {
         val schemaBundle = SchemaBundle(SchemaBundle.LATEST_FORMAT, bundle)
         if (file.exists()) {
-            val existing = file.inputStream().use {
-                SchemaBundle.deserialize(it)
+            val existing = try {
+                file.inputStream().use {
+                    SchemaBundle.deserialize(it)
+                }
+            } catch (th: Throwable) {
+                throw IllegalStateException(
+                    """
+                    Cannot parse existing schema file: ${file.absolutePath}.
+                    If you've modified the file, you might've broken the JSON format, try
+                    deleting the file and re-running the compiler.
+                    If you've not modified the file, please file a bug at
+                    https://issuetracker.google.com/issues/new?component=413107&template=1096568
+                    with a sample app to reproduce the issue.
+                    """.trimIndent()
+                )
             }
             if (existing.isSchemaEqual(schemaBundle)) {
                 return
diff --git a/room/migration/api/restricted_current.txt b/room/migration/api/restricted_current.txt
index 436687e..d4f4fff 100644
--- a/room/migration/api/restricted_current.txt
+++ b/room/migration/api/restricted_current.txt
@@ -96,7 +96,7 @@
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SchemaBundle {
     ctor public SchemaBundle(int, androidx.room.migration.bundle.DatabaseBundle!);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.room.migration.bundle.SchemaBundle! deserialize(java.io.InputStream!) throws java.io.UnsupportedEncodingException;
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.room.migration.bundle.SchemaBundle deserialize(java.io.InputStream!) throws java.io.UnsupportedEncodingException;
     method public androidx.room.migration.bundle.DatabaseBundle! getDatabase();
     method public int getFormatVersion();
     method public boolean isSchemaEqual(androidx.room.migration.bundle.SchemaBundle!);
diff --git a/room/migration/src/main/java/androidx/room/migration/bundle/SchemaBundle.java b/room/migration/src/main/java/androidx/room/migration/bundle/SchemaBundle.java
index beb2c5e..9a28299 100644
--- a/room/migration/src/main/java/androidx/room/migration/bundle/SchemaBundle.java
+++ b/room/migration/src/main/java/androidx/room/migration/bundle/SchemaBundle.java
@@ -16,6 +16,7 @@
 
 package androidx.room.migration.bundle;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
 
 import com.google.gson.Gson;
@@ -80,12 +81,17 @@
     /**
      * @hide
      */
+    @NonNull
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
     public static SchemaBundle deserialize(InputStream fis)
             throws UnsupportedEncodingException {
         InputStreamReader is = new InputStreamReader(fis, CHARSET);
         try {
-            return GSON.fromJson(is, SchemaBundle.class);
+            SchemaBundle result = GSON.fromJson(is, SchemaBundle.class);
+            if (result == null || result.getDatabase() == null) {
+                throw new IllegalStateException("Invalid schema file");
+            }
+            return result;
         } finally {
             safeClose(is);
             safeClose(fis);