]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
JNI: use ByteBuffer.limit() instead of ByteBuffer.capacity() when figuring out where...
authorstephan <stephan@noemail.net>
Tue, 14 Nov 2023 05:33:44 +0000 (05:33 +0000)
committerstephan <stephan@noemail.net>
Tue, 14 Nov 2023 05:33:44 +0000 (05:33 +0000)
FossilOrigin-Name: 51539419edc08ee6c70d8719d0f4d5ad47dd545a7fd9bf01d03a434aabd41d68

ext/jni/src/c/sqlite3-jni.c
ext/jni/src/org/sqlite/jni/capi/CApi.java
manifest
manifest.uuid

index 6668f0b72874e92489f8af188e0f263b54538050..79918b3bdbccb37d8f3f0cf9a489e094ec72e2d9 100644 (file)
@@ -661,11 +661,14 @@ struct S3JniGlobalType {
 
       https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#nio_support
 
-      We only store a ref to the following if JNI support for
+      We only store a ref to byteBuffer.klazz if JNI support for
       ByteBuffer is available (which we determine during static init).
     */
-    jclass cByteBuffer       /* global ref to java.nio.ByteBuffer */;
-    jmethodID byteBufferAlloc/* ByteBuffer.allocateDirect() */;
+    struct {
+      jclass klazz       /* global ref to java.nio.ByteBuffer */;
+      jmethodID midAlloc /* ByteBuffer.allocateDirect() */;
+      jmethodID midLimit /* ByteBuffer.limit() */;
+    } byteBuffer;
   } g;
   /*
   ** The list of Java-side auto-extensions
@@ -873,25 +876,52 @@ static jbyte * s3jni__jbyteArray_bytes2(JNIEnv * const env, jbyteArray jBA, jsiz
   if( jBytes ) (*env)->ReleaseByteArrayElements(env, jByteArray, jBytes, JNI_COMMIT)
 
 /*
-** If jbb is-a java.nio.Buffer object and the JNI environment
-** supports it, *pBuf is set to the buffer's memory and *pN is set to
-** its length. If jbb is NULL, not a Buffer, or the JNI environment
-** does not support that operation, *pBuf is set to 0 and *pN is set
-** to 0.
+** If jbb is-a java.nio.Buffer object and the JNI environment supports
+** it, *pBuf is set to the buffer's memory and *pN is set to its
+** limit() (as opposed to its capacity()). If jbb is NULL, not a
+** Buffer, or the JNI environment does not support that operation,
+** *pBuf is set to 0 and *pN is set to 0.
 **
 ** Note that the length of the buffer can be larger than SQLITE_LIMIT
 ** but this function does not know what byte range of the buffer is
 ** required so cannot check for that violation. The caller is required
 ** to ensure that any to-be-bind()ed range fits within SQLITE_LIMIT.
- */
+**
+** Sidebar: it is unfortunate that we cannot get ByteBuffer.limit()
+** via a JNI method like we can for ByteBuffer.capacity(). We instead
+** have to call back into Java to get the limit(). Depending on how
+** the ByteBuffer is used, the limit and capacity might be the same,
+** but when reusing a buffer, the limit may well change whereas the
+** capacity is fixed. The problem with, e.g., read()ing blob data to a
+** ByteBuffer's memory based on its capacity is that Java-level code
+** is restricted to accessing the range specified in
+** ByteBuffer.limit().  If we were to honor only the capacity, we
+** could end up writing to, or reading from, parts of a ByteBuffer
+** which client code itself cannot access without explicitly modifying
+** the limit. The penalty we pay for this correctness is that we must
+** call into Java to get the limit() of every ByteBuffer we work with.
+**
+** An alternative to having to call into ByteBuffer.limit() from here
+** would be to add private native impls of all ByteBuffer-using
+** methods, each of which adds a jint parameter which _must_ be set to
+** theBuffer.limit() by public Java APIs which use those private impls
+** to do the real work.
+*/
 static void s3jni__get_nio_buffer(JNIEnv * const env, jobject jbb, void **pBuf, jint * pN ){
   *pBuf = 0;
   *pN = 0;
   if( jbb ){
     *pBuf = (*env)->GetDirectBufferAddress(env, jbb);
-    *pN = *pBuf ? (jint)(*env)->GetDirectBufferCapacity(env, jbb) : 0
-      /* why the Java limits the buffer length to int but the JNI API
-         uses a jlong for the length is a mystery. */;
+    if( *pBuf ){
+      /*
+      ** Maintenance reminder: do not use
+      ** (*env)->GetDirectBufferCapacity(env,jbb), even though it
+      ** would be much faster, for reasons explained in this
+      ** function's comments.
+      */
+      *pN = (*env)->CallIntMethod(env, jbb, SJG.g.byteBuffer.midLimit);
+      S3JniExceptionIsFatal("Error calling ByteBuffer.limit() method.");
+    }
   }
 }
 #define s3jni_get_nio_buffer(JOBJ,vpOut,jpOut) \
@@ -1097,15 +1127,15 @@ static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p,
 
 /*
 ** Creates a new ByteBuffer instance with a capacity of n. assert()s
-** that SJG.g.cByteBuffer is not 0 and n>0.
+** that SJG.g.byteBuffer.klazz is not 0 and n>0.
 */
 static jobject s3jni__new_ByteBuffer(JNIEnv * const env, int n){
   jobject rv = 0;
-  assert( SJG.g.cByteBuffer );
-  assert( SJG.g.byteBufferAlloc );
+  assert( SJG.g.byteBuffer.klazz );
+  assert( SJG.g.byteBuffer.midAlloc );
   assert( n > 0 );
-  rv = (*env)->CallStaticObjectMethod(env, SJG.g.cByteBuffer,
-                                      SJG.g.byteBufferAlloc, (jint)n);
+  rv = (*env)->CallStaticObjectMethod(env, SJG.g.byteBuffer.klazz,
+                                      SJG.g.byteBuffer.midAlloc, (jint)n);
   S3JniIfThrew {
     S3JniExceptionReport;
     S3JniExceptionClear;
@@ -1124,7 +1154,7 @@ static jobject s3jni__blob_to_ByteBuffer(JNIEnv * const env,
                                          const void * p, int n){
   jobject rv = NULL;
   assert( n >= 0 );
-  if( 0==n || !SJG.g.cByteBuffer ){
+  if( 0==n || !SJG.g.byteBuffer.klazz ){
     return NULL;
   }
   rv = s3jni__new_ByteBuffer(env, n);
@@ -2498,9 +2528,9 @@ static const S3JniNioArgs S3JniNioArgs_empty = {
 ** sqlite3_result_nio_buffer(), and similar methods which take a
 ** ByteBuffer object as either input or output. Populates pArgs and
 ** returns 0 on success, non-0 if the operation should fail. The
-** caller is required to check for SJG.g.cByteBuffer!=0 before calling
+** caller is required to check for SJG.g.byteBuffer.klazz!=0 before calling
 ** this and reporting it in a way appropriate for that routine.  This
-** function may assert() that SJG.g.cByteBuffer is not 0.
+** function may assert() that SJG.g.byteBuffer.klazz is not 0.
 **
 ** The (jBuffer, iOffset, iHowMany) arguments are the (ByteBuffer, offset,
 ** length) arguments to the bind/result method.
@@ -2523,7 +2553,7 @@ static int s3jni_setup_nio_args(
   pArgs->jBuf = jBuffer;
   pArgs->iOffset = iOffset;
   pArgs->iHowMany = iHowMany;
-  assert( SJG.g.cByteBuffer );
+  assert( SJG.g.byteBuffer.klazz );
   if( pArgs->iOffset<0 ){
     return SQLITE_ERROR
       /* SQLITE_MISUSE or SQLITE_RANGE would fit better but we use
@@ -2571,7 +2601,7 @@ S3JniApi(sqlite3_bind_nio_buffer(),jint,1bind_1nio_1buffer)(
   sqlite3_stmt * pStmt = PtrGet_sqlite3_stmt(jpStmt);
   S3JniNioArgs args;
   int rc;
-  if( !pStmt || !SJG.g.cByteBuffer ) return SQLITE_MISUSE;
+  if( !pStmt || !SJG.g.byteBuffer.klazz ) return SQLITE_MISUSE;
   rc = s3jni_setup_nio_args(env, &args, jBuffer, iOffset, iN);
   if(rc){
     return rc;
@@ -2802,7 +2832,7 @@ S3JniApi(sqlite3_blob_read_nio_buffer(),jint,1blob_1read_1nio_1buffer)(
   sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob);
   S3JniNioArgs args;
   int rc;
-  if( !b || !SJG.g.cByteBuffer || iHowMany<0 ){
+  if( !b || !SJG.g.byteBuffer.klazz || iHowMany<0 ){
     return SQLITE_MISUSE;
   }else if( iTgtOff<0 || iSrcOff<0 ){
     return SQLITE_ERROR
@@ -2847,7 +2877,7 @@ S3JniApi(sqlite3_blob_write_nio_buffer(),jint,1blob_1write_1nio_1buffer)(
   sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob);
   S3JniNioArgs args;
   int rc;
-  if( !b || !SJG.g.cByteBuffer ){
+  if( !b || !SJG.g.byteBuffer.klazz ){
     return SQLITE_MISUSE;
   }else if( iTgtOff<0 || iSrcOff<0 ){
     return SQLITE_ERROR
@@ -3940,7 +3970,7 @@ S3JniApi(sqlite3_jni_db_error(), jint, 1jni_1db_1error)(
 S3JniApi(sqlite3_jni_supports_nio(), jboolean,1jni_1supports_1nio)(
   JniArgsEnvClass
 ){
-  return SJG.g.cByteBuffer ? JNI_TRUE : JNI_FALSE;
+  return SJG.g.byteBuffer.klazz ? JNI_TRUE : JNI_FALSE;
 }
 
 
@@ -4683,7 +4713,7 @@ S3JniApi(sqlite3_result_nio_buffer(),void,1result_1nio_1buffer)(
   S3JniNioArgs args;
   if( !pCx ){
     return;
-  }else if( !SJG.g.cByteBuffer ){
+  }else if( !SJG.g.byteBuffer.klazz ){
     sqlite3_result_error(
       pCx, "This JVM does not support JNI access to ByteBuffers.", -1
     );
@@ -6257,15 +6287,19 @@ Java_org_sqlite_jni_capi_CApi_init(JniArgsEnvClass){
     unsigned char buf[16] = {0};
     jobject bb = (*env)->NewDirectByteBuffer(env, buf, 16);
     if( bb ){
-      SJG.g.cByteBuffer = S3JniRefGlobal((*env)->GetObjectClass(env, bb));
-      SJG.g.byteBufferAlloc = (*env)->GetStaticMethodID(
-        env, SJG.g.cByteBuffer, "allocateDirect", "(I)Ljava/nio/ByteBuffer;"
+      SJG.g.byteBuffer.klazz = S3JniRefGlobal((*env)->GetObjectClass(env, bb));
+      SJG.g.byteBuffer.midAlloc = (*env)->GetStaticMethodID(
+        env, SJG.g.byteBuffer.klazz, "allocateDirect", "(I)Ljava/nio/ByteBuffer;"
       );
       S3JniExceptionIsFatal("Error getting ByteBuffer.allocateDirect() method.");
+      SJG.g.byteBuffer.midLimit = (*env)->GetMethodID(
+        env, SJG.g.byteBuffer.klazz, "limit", "()I"
+      );
+      S3JniExceptionIsFatal("Error getting ByteBuffer.limit() method.");
       S3JniUnrefLocal(bb);
     }else{
-      SJG.g.cByteBuffer = 0;
-      SJG.g.byteBufferAlloc = 0;
+      SJG.g.byteBuffer.klazz = 0;
+      SJG.g.byteBuffer.midAlloc = 0;
     }
   }
 
index 090adf94f474b449ea520c8507485538ce9ffce7..67bdcd676b2a25154b190894f83a8c80b039403d 100644 (file)
@@ -308,12 +308,13 @@ public final class CApi {
      Binds the contents of the given buffer object as a blob.
 
      The byte range of the buffer may be restricted by providing a
-     start index and a number of bytes. beginPos may not be negative
-     but a negative howMany is interpretated as the remainder of the
-     buffer past the given start position.
+     start index and a number of bytes. beginPos may not be negative.
+     Negative howMany is interpretated as the remainder of the buffer
+     past the given start position, up to the buffer's limit() (as
+     opposed its capacity()).
 
-     If beginPos+howMany would extend past the end of the buffer then
-     SQLITE_ERROR is returned.
+     If beginPos+howMany would extend past the limit() of the buffer
+     then SQLITE_ERROR is returned.
 
      If any of the following are true, this function behaves like
      sqlite3_bind_null(): the buffer is null, beginPos is at or past
@@ -347,7 +348,8 @@ public final class CApi {
   );
 
   /**
-     Convenience overload which binds the given buffer's entire contents.
+     Convenience overload which binds the given buffer's entire
+     contents, up to its limit() (as opposed to its capacity()).
   */
   public static int sqlite3_bind_nio_buffer(
     @NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data
index d62656917f332a99f2f08ad7c86ad985736f4232..db02dc3c5c28288b42c6ec48e8227a2277b1a7e7 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C JNI:\sadd\ssqlite3_blob_read_nio_buffer()\sand\siron\sout\sthe\sblob/ByteBuffer\sinterface\ssomewhat.
-D 2023-11-14T04:59:57.233
+C JNI:\suse\sByteBuffer.limit()\sinstead\sof\sByteBuffer.capacity()\swhen\sfiguring\sout\swhere\sthe\slogical\send\sof\sa\sByteBuffer\sis,\sfor\sreasons\sexplained\sat\slength\sin\snew\scode\scomments.\sThis\sis\sunfortunately\sslower\sbut\sis\sthe\scorrect\sway\sto\sdo\sit.
+D 2023-11-14T05:33:44.492
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -241,7 +241,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3
 F ext/jni/GNUmakefile f2f3a31923293659b95225e932a286af1f2287d75bf88ad6c0fd1b9d9cd020d4
 F ext/jni/README.md ef9ac115e97704ea995d743b4a8334e23c659e5534c3b64065a5405256d5f2f4
 F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa
-F ext/jni/src/c/sqlite3-jni.c 9828d7b6b584c55261e4dd65d86ce4da33daf6cee2966b191ac332ce47efac1c
+F ext/jni/src/c/sqlite3-jni.c 524ca86d59c07db31ad6feb93f2dece977563e1264b903ccfbbdbeff5f288089
 F ext/jni/src/c/sqlite3-jni.h 0ed09051f16f612680603a297fefa2c131c4a7e98e0b41cdd9ece08428b47d48
 F ext/jni/src/org/sqlite/jni/annotation/NotNull.java 02091a8112e33389f1c160f506cd413168c8dfacbeda608a4946c6e3557b7d5a
 F ext/jni/src/org/sqlite/jni/annotation/Nullable.java 0b1879852707f752512d4db9d7edd0d8db2f0c2612316ce1c832715e012ff6ba
@@ -251,7 +251,7 @@ F ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java 0b72cdff61533b564d65b63
 F ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java c045a5b47e02bb5f1af91973814a905f12048c428a3504fbc5266d1c1be3de5a
 F ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java 74cc4998a73d6563542ecb90804a3c4f4e828cb4bd69e61226d1a51f4646e759
 F ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java 7b8e19810c42b0ad21a04b5d8c804b32ee5905d137148703f16a75b612c380ca
-F ext/jni/src/org/sqlite/jni/capi/CApi.java a7c53a3226c6826ada00752a651a31e6cce3b8d741a02b2d45cb0d1e3dfc3a80
+F ext/jni/src/org/sqlite/jni/capi/CApi.java 7b2eae29f21db915bd358f3fab1eb9f1a4cbc8b2cc4c78aab7309cd69b71958a
 F ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java 57e2d275dcebe690b1fc1f3d34eb96879b2d7039bce30b563aee547bf45d8a8b
 F ext/jni/src/org/sqlite/jni/capi/CollationCallback.java e29bcfc540fdd343e2f5cca4d27235113f2886acb13380686756d5cabdfd065a
 F ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java 5bfa226a8e7a92e804fd52d6e42b4c7b875fa7a94f8e2c330af8cc244a8920ab
@@ -2139,8 +2139,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 46656b354311ec0a36832af1c4ccb3b6a244aa55cfb3681e25c3f42b13b387dd
-R 4e0b6f66f2c79b3b0f3d9c780b63f86e
+P 7df317b448a09ae77e2c68cc901fdb6d56a2246c1313f06bebd1f3e53f02c19b
+R dddfec0befd08ab210a022ab61f478a4
 U stephan
-Z 5994994aa13f973034ef1e21e15b70de
+Z 49fb00c3c45f37f7c83250698ea520ef
 # Remove this line to create a well-formed Fossil manifest.
index 8b049893ca8d451221811261e927e2cc6cb56629..fde4bb75f67a5fb101eeeb6b3dc787951c3bb8b6 100644 (file)
@@ -1 +1 @@
-7df317b448a09ae77e2c68cc901fdb6d56a2246c1313f06bebd1f3e53f02c19b
\ No newline at end of file
+51539419edc08ee6c70d8719d0f4d5ad47dd545a7fd9bf01d03a434aabd41d68
\ No newline at end of file