Bug 1184607 P7.2 Validate Cache schema in debug builds. r=ehsan
authorBen Kelly <ben@wanderview.com>
Mon, 31 Aug 2015 14:26:30 -0700
changeset 260222 35262b99f9c712c89fb2a268fca93d52acafdbff
parent 260221 9a054173a13e4275eb3be08e8eabb25a8451d5bc
child 260223 95fed4c06038b5b6bce9f5e50d88ea51d9cd8e6e
push id29304
push usercbook@mozilla.com
push dateTue, 01 Sep 2015 12:32:25 +0000
treeherdermozilla-central@dd509db16a13 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs1184607
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1184607 P7.2 Validate Cache schema in debug builds. r=ehsan
dom/cache/DBSchema.cpp
--- a/dom/cache/DBSchema.cpp
+++ b/dom/cache/DBSchema.cpp
@@ -320,30 +320,35 @@ static nsresult BindId(mozIStorageStatem
 static nsresult ExtractId(mozIStorageStatement* aState, uint32_t aPos,
                           nsID* aIdOut);
 static nsresult CreateAndBindKeyStatement(mozIStorageConnection* aConn,
                                           const char* aQueryFormat,
                                           const nsAString& aKey,
                                           mozIStorageStatement** aStateOut);
 static nsresult HashCString(nsICryptoHash* aCrypto, const nsACString& aIn,
                             nsACString& aOut);
+nsresult Validate(mozIStorageConnection* aConn);
 } // namespace
 
 nsresult
 CreateSchema(mozIStorageConnection* aConn)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aConn);
 
   int32_t schemaVersion;
   nsresult rv = aConn->GetSchemaVersion(&schemaVersion);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   if (schemaVersion == kLatestSchemaVersion) {
-    // We already have the correct schema, so just get started.
+    // We already have the correct schema version.  Validate it matches
+    // our expected schema and then proceed.
+    rv = Validate(aConn);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
     return rv;
   }
 
   if (!schemaVersion) {
     rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableCaches));
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
     rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableSecurityInfo));
@@ -372,19 +377,18 @@ CreateSchema(mozIStorageConnection* aCon
 
     rv = aConn->SetSchemaVersion(kLatestSchemaVersion);
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
     rv = aConn->GetSchemaVersion(&schemaVersion);
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   }
 
-  if (schemaVersion != kLatestSchemaVersion) {
-    return NS_ERROR_FAILURE;
-  }
+  rv = Validate(aConn);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   return rv;
 }
 
 nsresult
 InitializeConnection(mozIStorageConnection* aConn)
 {
   MOZ_ASSERT(!NS_IsMainThread());
@@ -2200,12 +2204,122 @@ IncrementalVacuum(mozIStorageConnection*
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   MOZ_ASSERT(freePages <= kMaxFreePages);
 #endif
 
   return NS_OK;
 }
 
+namespace {
+
+#ifdef DEBUG
+struct Expect
+{
+  // Expect exact SQL
+  Expect(const char* aName, const char* aType, const char* aSql)
+    : mName(aName)
+    , mType(aType)
+    , mSql(aSql)
+    , mIgnoreSql(false)
+  { }
+
+  // Ignore SQL
+  Expect(const char* aName, const char* aType)
+    : mName(aName)
+    , mType(aType)
+    , mIgnoreSql(true)
+  { }
+
+  const nsCString mName;
+  const nsCString mType;
+  const nsCString mSql;
+  const bool mIgnoreSql;
+};
+#endif
+
+nsresult
+Validate(mozIStorageConnection* aConn)
+{
+  int32_t schemaVersion;
+  nsresult rv = aConn->GetSchemaVersion(&schemaVersion);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  if (NS_WARN_IF(schemaVersion != kLatestSchemaVersion)) {
+    return NS_ERROR_FAILURE;
+  }
+
+#ifdef DEBUG
+  // This is the schema we expect the database at the latest version to
+  // contain.  Update this list if you add a new table or index.
+  Expect expect[] = {
+    Expect("caches", "table", kTableCaches),
+    Expect("sqlite_sequence", "table"), // auto-gen by sqlite
+    Expect("security_info", "table", kTableSecurityInfo),
+    Expect("security_info_hash_index", "index", kIndexSecurityInfoHash),
+    Expect("entries", "table", kTableEntries),
+    Expect("entries_request_match_index", "index", kIndexEntriesRequest),
+    Expect("request_headers", "table", kTableRequestHeaders),
+    Expect("response_headers", "table", kTableResponseHeaders),
+    Expect("response_headers_name_index", "index", kIndexResponseHeadersName),
+    Expect("storage", "table", kTableStorage),
+    Expect("sqlite_autoindex_storage_1", "index"), // auto-gen by sqlite
+  };
+  const uint32_t expectLength = sizeof(expect) / sizeof(Expect);
+
+  // Read the schema from the sqlite_master table and compare.
+  nsCOMPtr<mozIStorageStatement> state;
+  rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT name, type, sql FROM sqlite_master;"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  bool hasMoreData = false;
+  while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) {
+    nsAutoCString name;
+    rv = state->GetUTF8String(0, name);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    nsAutoCString type;
+    rv = state->GetUTF8String(1, type);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    nsAutoCString sql;
+    rv = state->GetUTF8String(2, sql);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    bool foundMatch = false;
+    for (uint32_t i = 0; i < expectLength; ++i) {
+      if (name == expect[i].mName) {
+        if (type != expect[i].mType) {
+          NS_WARNING(nsPrintfCString("Unexpected type for Cache schema entry %s",
+                     name.get()).get());
+          return NS_ERROR_FAILURE;
+        }
+
+        if (!expect[i].mIgnoreSql && sql != expect[i].mSql) {
+          NS_WARNING(nsPrintfCString("Unexpected SQL for Cache schema entry %s",
+                     name.get()).get());
+          return NS_ERROR_FAILURE;
+        }
+
+        foundMatch = true;
+        break;
+      }
+    }
+
+    if (NS_WARN_IF(!foundMatch)) {
+      NS_WARNING(nsPrintfCString("Unexpected schema entry %s in Cache database",
+                 name.get()).get());
+      return NS_ERROR_FAILURE;
+    }
+  }
+#endif
+
+  return rv;
+}
+
+} // anonymous namespace
+
 } // namespace db
 } // namespace cache
 } // namespace dom
 } // namespace mozilla