Check for out-of-memory errors and throw exception on native SQLite drivers.

Bug: 304297717
Test: ./sqlite/scripts/runConformanceTest.sh
Change-Id: Ieff2dd0e67bd32ed8aa1382d2d5d0b1baa5f0448
diff --git a/sqlite/sqlite-bundled/src/jvmAndroidMain/jni/sqlite_bindings.cpp b/sqlite/sqlite-bundled/src/jvmAndroidMain/jni/sqlite_bindings.cpp
index ffc8b89..47e170a 100644
--- a/sqlite/sqlite-bundled/src/jvmAndroidMain/jni/sqlite_bindings.cpp
+++ b/sqlite/sqlite-bundled/src/jvmAndroidMain/jni/sqlite_bindings.cpp
@@ -41,6 +41,20 @@
     return false;
 }
 
+static bool throwOutOfMemoryError(JNIEnv *env) {
+    jclass exceptionClass = env->FindClass("java/lang/OutOfMemoryError");
+    int throwResult = env->ThrowNew(exceptionClass, nullptr);
+    return throwResult == 0;
+}
+
+static bool throwIfOutOfMemory(JNIEnv *env, sqlite3_stmt *stmt) {
+    int lastRc = sqlite3_errcode(sqlite3_db_handle(stmt));
+    if (lastRc == SQLITE_NOMEM) {
+        return throwOutOfMemoryError(env);
+    }
+    return false;
+}
+
 extern "C" JNIEXPORT jint JNICALL
 Java_androidx_sqlite_driver_bundled_BundledSQLiteDriverKt_nativeThreadSafeMode(
         JNIEnv* env,
@@ -196,8 +210,9 @@
     if (throwIfNoRow(env, stmt)) return nullptr;
     if (throwIfInvalidColumn(env, stmt, index)) return nullptr;
     const void *blob = sqlite3_column_blob(stmt, index);
+    if (blob == nullptr && throwIfOutOfMemory(env, stmt)) return nullptr;
     int size = sqlite3_column_bytes(stmt, index);
-    // TODO(b/304297717): Use sqlite3_errcode() to check for out-of-memory
+    if (size == 0 && throwIfOutOfMemory(env, stmt)) return nullptr;
     jbyteArray byteArray = env->NewByteArray(size);
     if (size > 0) {
         env->SetByteArrayRegion(byteArray, 0, size, static_cast<const jbyte*>(blob));
@@ -240,12 +255,10 @@
     if (throwIfInvalidColumn(env, stmt, index)) return nullptr;
     // Java / jstring represents a string in UTF-16 encoding.
     const jchar *text = static_cast<const jchar*>(sqlite3_column_text16(stmt, index));
-    if (text) {
-        int length = sqlite3_column_bytes16(stmt, index) / sizeof(jchar);
-        return env->NewString(text, length);
-    }
-    // TODO: Use sqlite3_errcode() to check for out-of-memory
-    return nullptr;
+    if (text == nullptr && throwIfOutOfMemory(env, stmt)) return nullptr;
+    size_t length = sqlite3_column_bytes16(stmt, index) / sizeof(jchar);
+    if (length == 0 && throwIfOutOfMemory(env, stmt)) return nullptr;
+    return env->NewString(text, length);
 }
 
 extern "C" JNIEXPORT jint JNICALL
@@ -266,8 +279,9 @@
     sqlite3_stmt *stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
     if (throwIfInvalidColumn(env, stmt, index)) return nullptr;
     const char *name = sqlite3_column_name(stmt, index);
-    if (name == NULL) {
-        // TODO: throw out-of-memory exception
+    if (name == nullptr) {
+        throwOutOfMemoryError(env);
+        return nullptr;
     }
     return env->NewStringUTF(name);
 }
diff --git a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteStatement.kt b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteStatement.kt
index b339344..957158ca 100644
--- a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteStatement.kt
+++ b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteStatement.kt
@@ -130,9 +130,7 @@
         // sqlite3_column_text16(), even empty strings, are always zero-terminated. Thus we use
         // toKStringFromUtf16() that returns a kotlin.String from a zero-terminated C string.
         val value = sqlite3_column_text16(stmtPointer, index)
-        if (sqlite3_errcode(dbPointer) == SQLITE_NOMEM) {
-            throw OutOfMemoryError()
-        }
+        if (value == null) throwIfOutOfMemory()
         return value!!.reinterpret<UShortVar>().toKStringFromUtf16()
     }
 
@@ -148,10 +146,9 @@
         throwIfNoRow()
         throwIfInvalidColumn(index)
         val blob = sqlite3_column_blob(stmtPointer, index)
-        if (sqlite3_errcode(dbPointer) == SQLITE_NOMEM) {
-            throw OutOfMemoryError()
-        }
+        if (blob == null) throwIfOutOfMemory()
         val size = sqlite3_column_bytes(stmtPointer, index)
+        if (size == 0) throwIfOutOfMemory()
         return if (blob != null && size > 0) {
             blob.readBytes(size)
         } else {
@@ -241,4 +238,11 @@
             throwSQLiteException(SQLITE_RANGE, "column index out of range")
         }
     }
+
+    private fun throwIfOutOfMemory() {
+        val lastResultCode = sqlite3_errcode(dbPointer)
+        if (lastResultCode == SQLITE_NOMEM) {
+            throw OutOfMemoryError()
+        }
+    }
 }