SQLite Forum

Bugs in mechanism of loading of extensions
Login

Bugs in mechanism of loading of extensions

(1.1) By Arfrever Frehtes Taifersar Arahesis (Arfrever) on 2021-11-30 16:45:24 edited from 1.0 [source]

SQLite contains at least 3 mechanisms of loading of extensions:

  • One of them is built-in extensions which are defined in array sqlite3BuiltinExtensions in src/main.c.

  • Another one is extensions registered by function sqlite3_auto_extension().

  • Extensions in separate .so files (not discussed here)

Loading of extensions defined in array sqlite3BuiltinExtensions and loading of automatic extensions (registered by function sqlite3_auto_extension()) is performed in function openDatabase() in src/main.c:

  /* Load compiled-in extensions */
  for(i=0; rc==SQLITE_OK && i<ArraySize(sqlite3BuiltinExtensions); i++){
    rc = sqlite3BuiltinExtensions[i](db);
  }

  /* Load automatic extensions - extensions that have been registered
  ** using the sqlite3_automatic_extension() API.
  */
  if( rc==SQLITE_OK ){
    sqlite3AutoLoadExtensions(db);
    rc = sqlite3_errcode(db);
    if( rc!=SQLITE_OK ){
      goto opendb_out;
    }
  }

Initialization functions of most of extensions distributed in SQLite return SQLITE_OK, but in case of 4 extensions, SQLITE_OK_LOAD_PERMANENTLY is returned:

ext/misc/appendvfs.c:  if( rc==SQLITE_OK ) rc = SQLITE_OK_LOAD_PERMANENTLY;
ext/misc/appendvfs.c:  return rc;
--
ext/misc/cksumvfs.c:  if( rc==SQLITE_OK ) rc = SQLITE_OK_LOAD_PERMANENTLY;
ext/misc/cksumvfs.c:  return rc;
--
ext/misc/memvfs.c:  if( rc==SQLITE_OK ) rc = SQLITE_OK_LOAD_PERMANENTLY;
ext/misc/memvfs.c:  return rc;
--
ext/misc/vfsstat.c:  if( rc==SQLITE_OK ) rc = SQLITE_OK_LOAD_PERMANENTLY;
ext/misc/vfsstat.c:  return rc;

SQLITE_OK_LOAD_PERMANENTLY is handled in src/loadext.c:

static int sqlite3LoadExtension(
  ...
  rc = xInit(db, &zErrmsg, &sqlite3Apis);
  if( rc ){
    if( rc==SQLITE_OK_LOAD_PERMANENTLY ) return SQLITE_OK;
    if( pzErrMsg ){
      *pzErrMsg = sqlite3_mprintf("error during initialization: %s", zErrmsg);
    }
    sqlite3_free(zErrmsg);
    sqlite3OsDlClose(pVfs, handle);
    return SQLITE_ERROR;
  }

SQLITE_OK_LOAD_PERMANENTLY is also handled in src/test1.c:

static int SQLITE_TCLAPI tclLoadStaticExtensionCmd(
  ...
    if( aExtension[i].pInit ){
      rc = aExtension[i].pInit(db, &zErrMsg, 0);
    }else{
      rc = SQLITE_OK;
    }
    if( (rc!=SQLITE_OK && rc!=SQLITE_OK_LOAD_PERMANENTLY) || zErrMsg ){
      Tcl_AppendResult(interp, "initialization of ", zName, " failed: ", zErrMsg,
                       (char*)0);
      sqlite3_free(zErrMsg);
      return TCL_ERROR;
    }

Bugs in mechanism of loading of extensions:

  1. Code for processing array sqlite3BuiltinExtensions in src/main.c does not handle SQLITE_OK_LOAD_PERMANENTLY, i.e. SQLITE_OK_LOAD_PERMANENTLY is treated as failure.

  2. If loading of any extension (by aforementioned code in src/main.c) fails for whatever reason, then no error message is printed. For usefulness, error message should contain human-readable name of extension, not something like "extension #11".

  3. If loading of any extension (by aforementioned code in src/main.c) fails for whatever reason, then loading of all subsequent extensions (including automatic extensions) is skipped. SQLite still continues running, just without some extensions. This is unlike e.g. unhandled ImportError in Python which terminates Python process, which strongly suggests that SQLite should try to load subsequent extensions.

  4. (Minor) Typo in this code: sqlite3_automatic_extension should be sqlite3_auto_extension.

Potential fix for bug #1:

--- src/main.c
+++ src/main.c
@@ -3411,6 +3411,9 @@
   /* Load compiled-in extensions */
   for(i=0; rc==SQLITE_OK && i<ArraySize(sqlite3BuiltinExtensions); i++){
     rc = sqlite3BuiltinExtensions[i](db);
+    if( rc==SQLITE_OK_LOAD_PERMANENTLY ){
+      rc = SQLITE_OK;
+    }
   }
 
   /* Load automatic extensions - extensions that have been registered
--- src/loadext.c
+++ src/loadext.c
@@ -859,10 +859,13 @@
     }
     sqlite3_mutex_leave(mutex);
     zErrmsg = 0;
-    if( xInit && (rc = xInit(db, &zErrmsg, pThunk))!=0 ){
-      sqlite3ErrorWithMsg(db, rc,
-            "automatic extension loading failed: %s", zErrmsg);
-      go = 0;
+    if( xInit ){
+      rc = xInit(db, &zErrmsg, pThunk);
+      if( rc!=SQLITE_OK && rc!=SQLITE_OK_LOAD_PERMANENTLY ){
+        sqlite3ErrorWithMsg(db, rc,
+              "automatic extension loading failed: %s", zErrmsg);
+        go = 0;
+      }
     }
     sqlite3_free(zErrmsg);
   }

Bug #2 can be partially solved by using array of structures similar to aExtension in src/test1.c.

(2.1) By Arfrever Frehtes Taifersar Arahesis (Arfrever) on 2021-11-30 20:58:06 edited from 2.0 in reply to 1.1 [link] [source]

Potential patch for bug #2 (missing error message):

--- src/main.c
+++ src/main.c
@@ -61,40 +61,43 @@
 ** An array of pointers to extension initializer functions for
 ** built-in extensions.
 */
-static int (*const sqlite3BuiltinExtensions[])(sqlite3*) = {
+static const struct {
+  const char *zExtName;
+  int (*pInit)(sqlite3*);
+} sqlite3BuiltinExtensions[] = {
 #ifdef SQLITE_ENABLE_FTS1
-  sqlite3Fts1Init,
+  { "fts1",                            sqlite3Fts1Init                       },
 #endif
 #ifdef SQLITE_ENABLE_FTS2
-  sqlite3Fts2Init,
+  { "fts2",                            sqlite3Fts2Init                       },
 #endif
 #ifdef SQLITE_ENABLE_FTS3
-  sqlite3Fts3Init,
+  { "fts3",                            sqlite3Fts3Init                       },
 #endif
 #ifdef SQLITE_ENABLE_FTS5
-  sqlite3Fts5Init,
+  { "fts5",                            sqlite3Fts5Init                       },
 #endif
 #if defined(SQLITE_ENABLE_ICU) || defined(SQLITE_ENABLE_ICU_COLLATIONS)
-  sqlite3IcuInit,
+  { "icu",                             sqlite3IcuInit                        },
 #endif
 #ifdef SQLITE_ENABLE_RTREE
-  sqlite3RtreeInit,
+  { "rtree",                           sqlite3RtreeInit                      },
 #endif
 #ifdef SQLITE_ENABLE_DBPAGE_VTAB
-  sqlite3DbpageRegister,
+  { "dbpage",                          sqlite3DbpageRegister                 },
 #endif
 #ifdef SQLITE_ENABLE_DBSTAT_VTAB
-  sqlite3DbstatRegister,
+  { "dbstat",                          sqlite3DbstatRegister                 },
 #endif
-  sqlite3TestExtInit,
+  { "testext",                         sqlite3TestExtInit                    },
 #ifdef SQLITE_ENABLE_JSON1
-  sqlite3Json1Init,
+  { "json1",                           sqlite3Json1Init                      },
 #endif
 #ifdef SQLITE_ENABLE_STMTVTAB
-  sqlite3StmtVtabInit,
+  { "stmt",                            sqlite3StmtVtabInit                   },
 #endif
 #ifdef SQLITE_ENABLE_BYTECODE_VTAB
-  sqlite3VdbeBytecodeVtabInit,
+  { "vdbebytecode",                    sqlite3VdbeBytecodeVtabInit,          },
 #endif
 };
 
@@ -3410,8 +3413,16 @@
 
   /* Load compiled-in extensions */
   for(i=0; rc==SQLITE_OK && i<ArraySize(sqlite3BuiltinExtensions); i++){
-    rc = sqlite3BuiltinExtensions[i](db);
-    if( rc==SQLITE_OK_LOAD_PERMANENTLY ){
+    if( sqlite3BuiltinExtensions[i].zExtName && sqlite3BuiltinExtensions[i].pInit ){
+      rc = sqlite3BuiltinExtensions[i].pInit(db);
+      if( rc==SQLITE_OK_LOAD_PERMANENTLY ){
+        rc = SQLITE_OK;
+      }
+      if( rc!=SQLITE_OK ){
+        fprintf(stderr, "Error: initialization of built-in extension %s failed: %d\n",
+                sqlite3BuiltinExtensions[i].zExtName, rc);
+      }
+    }else{
       rc = SQLITE_OK;
     }
   }

I also tried sqlite3_log() and sqlite3ErrorWithMsg(), but neither of them results in showing any message in terminal, so above patch uses fprintf().