1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +03:00

JNI: use ByteBuffer.limit() instead of ByteBuffer.capacity() when figuring out where the logical end of a ByteBuffer is, for reasons explained at length in new code comments. This is unfortunately slower but is the correct way to do it.

FossilOrigin-Name: 51539419edc08ee6c70d8719d0f4d5ad47dd545a7fd9bf01d03a434aabd41d68
This commit is contained in:
stephan
2023-11-14 05:33:44 +00:00
parent bdfc51dfef
commit 4ce5bc2836
4 changed files with 81 additions and 45 deletions

View File

@ -661,11 +661,14 @@ struct S3JniGlobalType {
https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#nio_support 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). ByteBuffer is available (which we determine during static init).
*/ */
jclass cByteBuffer /* global ref to java.nio.ByteBuffer */; struct {
jmethodID byteBufferAlloc/* ByteBuffer.allocateDirect() */; jclass klazz /* global ref to java.nio.ByteBuffer */;
jmethodID midAlloc /* ByteBuffer.allocateDirect() */;
jmethodID midLimit /* ByteBuffer.limit() */;
} byteBuffer;
} g; } g;
/* /*
** The list of Java-side auto-extensions ** 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( jBytes ) (*env)->ReleaseByteArrayElements(env, jByteArray, jBytes, JNI_COMMIT)
/* /*
** If jbb is-a java.nio.Buffer object and the JNI environment ** If jbb is-a java.nio.Buffer object and the JNI environment supports
** supports it, *pBuf is set to the buffer's memory and *pN is set to ** it, *pBuf is set to the buffer's memory and *pN is set to its
** its length. If jbb is NULL, not a Buffer, or the JNI environment ** limit() (as opposed to its capacity()). If jbb is NULL, not a
** does not support that operation, *pBuf is set to 0 and *pN is set ** Buffer, or the JNI environment does not support that operation,
** to 0. ** *pBuf is set to 0 and *pN is set to 0.
** **
** Note that the length of the buffer can be larger than SQLITE_LIMIT ** 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 ** but this function does not know what byte range of the buffer is
** required so cannot check for that violation. The caller is required ** required so cannot check for that violation. The caller is required
** to ensure that any to-be-bind()ed range fits within SQLITE_LIMIT. ** 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 ){ static void s3jni__get_nio_buffer(JNIEnv * const env, jobject jbb, void **pBuf, jint * pN ){
*pBuf = 0; *pBuf = 0;
*pN = 0; *pN = 0;
if( jbb ){ if( jbb ){
*pBuf = (*env)->GetDirectBufferAddress(env, jbb); *pBuf = (*env)->GetDirectBufferAddress(env, jbb);
*pN = *pBuf ? (jint)(*env)->GetDirectBufferCapacity(env, jbb) : 0 if( *pBuf ){
/* why the Java limits the buffer length to int but the JNI API /*
uses a jlong for the length is a mystery. */; ** 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) \ #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 ** 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){ static jobject s3jni__new_ByteBuffer(JNIEnv * const env, int n){
jobject rv = 0; jobject rv = 0;
assert( SJG.g.cByteBuffer ); assert( SJG.g.byteBuffer.klazz );
assert( SJG.g.byteBufferAlloc ); assert( SJG.g.byteBuffer.midAlloc );
assert( n > 0 ); assert( n > 0 );
rv = (*env)->CallStaticObjectMethod(env, SJG.g.cByteBuffer, rv = (*env)->CallStaticObjectMethod(env, SJG.g.byteBuffer.klazz,
SJG.g.byteBufferAlloc, (jint)n); SJG.g.byteBuffer.midAlloc, (jint)n);
S3JniIfThrew { S3JniIfThrew {
S3JniExceptionReport; S3JniExceptionReport;
S3JniExceptionClear; S3JniExceptionClear;
@ -1124,7 +1154,7 @@ static jobject s3jni__blob_to_ByteBuffer(JNIEnv * const env,
const void * p, int n){ const void * p, int n){
jobject rv = NULL; jobject rv = NULL;
assert( n >= 0 ); assert( n >= 0 );
if( 0==n || !SJG.g.cByteBuffer ){ if( 0==n || !SJG.g.byteBuffer.klazz ){
return NULL; return NULL;
} }
rv = s3jni__new_ByteBuffer(env, n); 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 ** sqlite3_result_nio_buffer(), and similar methods which take a
** ByteBuffer object as either input or output. Populates pArgs and ** ByteBuffer object as either input or output. Populates pArgs and
** returns 0 on success, non-0 if the operation should fail. The ** 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 ** 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, ** The (jBuffer, iOffset, iHowMany) arguments are the (ByteBuffer, offset,
** length) arguments to the bind/result method. ** length) arguments to the bind/result method.
@ -2523,7 +2553,7 @@ static int s3jni_setup_nio_args(
pArgs->jBuf = jBuffer; pArgs->jBuf = jBuffer;
pArgs->iOffset = iOffset; pArgs->iOffset = iOffset;
pArgs->iHowMany = iHowMany; pArgs->iHowMany = iHowMany;
assert( SJG.g.cByteBuffer ); assert( SJG.g.byteBuffer.klazz );
if( pArgs->iOffset<0 ){ if( pArgs->iOffset<0 ){
return SQLITE_ERROR return SQLITE_ERROR
/* SQLITE_MISUSE or SQLITE_RANGE would fit better but we use /* 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); sqlite3_stmt * pStmt = PtrGet_sqlite3_stmt(jpStmt);
S3JniNioArgs args; S3JniNioArgs args;
int rc; 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); rc = s3jni_setup_nio_args(env, &args, jBuffer, iOffset, iN);
if(rc){ if(rc){
return 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); sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob);
S3JniNioArgs args; S3JniNioArgs args;
int rc; int rc;
if( !b || !SJG.g.cByteBuffer || iHowMany<0 ){ if( !b || !SJG.g.byteBuffer.klazz || iHowMany<0 ){
return SQLITE_MISUSE; return SQLITE_MISUSE;
}else if( iTgtOff<0 || iSrcOff<0 ){ }else if( iTgtOff<0 || iSrcOff<0 ){
return SQLITE_ERROR 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); sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob);
S3JniNioArgs args; S3JniNioArgs args;
int rc; int rc;
if( !b || !SJG.g.cByteBuffer ){ if( !b || !SJG.g.byteBuffer.klazz ){
return SQLITE_MISUSE; return SQLITE_MISUSE;
}else if( iTgtOff<0 || iSrcOff<0 ){ }else if( iTgtOff<0 || iSrcOff<0 ){
return SQLITE_ERROR 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)( S3JniApi(sqlite3_jni_supports_nio(), jboolean,1jni_1supports_1nio)(
JniArgsEnvClass 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; S3JniNioArgs args;
if( !pCx ){ if( !pCx ){
return; return;
}else if( !SJG.g.cByteBuffer ){ }else if( !SJG.g.byteBuffer.klazz ){
sqlite3_result_error( sqlite3_result_error(
pCx, "This JVM does not support JNI access to ByteBuffers.", -1 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}; unsigned char buf[16] = {0};
jobject bb = (*env)->NewDirectByteBuffer(env, buf, 16); jobject bb = (*env)->NewDirectByteBuffer(env, buf, 16);
if( bb ){ if( bb ){
SJG.g.cByteBuffer = S3JniRefGlobal((*env)->GetObjectClass(env, bb)); SJG.g.byteBuffer.klazz = S3JniRefGlobal((*env)->GetObjectClass(env, bb));
SJG.g.byteBufferAlloc = (*env)->GetStaticMethodID( SJG.g.byteBuffer.midAlloc = (*env)->GetStaticMethodID(
env, SJG.g.cByteBuffer, "allocateDirect", "(I)Ljava/nio/ByteBuffer;" env, SJG.g.byteBuffer.klazz, "allocateDirect", "(I)Ljava/nio/ByteBuffer;"
); );
S3JniExceptionIsFatal("Error getting ByteBuffer.allocateDirect() method."); 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); S3JniUnrefLocal(bb);
}else{ }else{
SJG.g.cByteBuffer = 0; SJG.g.byteBuffer.klazz = 0;
SJG.g.byteBufferAlloc = 0; SJG.g.byteBuffer.midAlloc = 0;
} }
} }

View File

@ -308,12 +308,13 @@ public final class CApi {
Binds the contents of the given buffer object as a blob. Binds the contents of the given buffer object as a blob.
The byte range of the buffer may be restricted by providing a The byte range of the buffer may be restricted by providing a
start index and a number of bytes. beginPos may not be negative start index and a number of bytes. beginPos may not be negative.
but a negative howMany is interpretated as the remainder of the Negative howMany is interpretated as the remainder of the buffer
buffer past the given start position. 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 If beginPos+howMany would extend past the limit() of the buffer
SQLITE_ERROR is returned. then SQLITE_ERROR is returned.
If any of the following are true, this function behaves like If any of the following are true, this function behaves like
sqlite3_bind_null(): the buffer is null, beginPos is at or past 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( public static int sqlite3_bind_nio_buffer(
@NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data @NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data

View File

@ -1,5 +1,5 @@
C JNI:\sadd\ssqlite3_blob_read_nio_buffer()\sand\siron\sout\sthe\sblob/ByteBuffer\sinterface\ssomewhat. 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-14T04:59:57.233 D 2023-11-14T05:33:44.492
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -241,7 +241,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3
F ext/jni/GNUmakefile f2f3a31923293659b95225e932a286af1f2287d75bf88ad6c0fd1b9d9cd020d4 F ext/jni/GNUmakefile f2f3a31923293659b95225e932a286af1f2287d75bf88ad6c0fd1b9d9cd020d4
F ext/jni/README.md ef9ac115e97704ea995d743b4a8334e23c659e5534c3b64065a5405256d5f2f4 F ext/jni/README.md ef9ac115e97704ea995d743b4a8334e23c659e5534c3b64065a5405256d5f2f4
F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa 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/c/sqlite3-jni.h 0ed09051f16f612680603a297fefa2c131c4a7e98e0b41cdd9ece08428b47d48
F ext/jni/src/org/sqlite/jni/annotation/NotNull.java 02091a8112e33389f1c160f506cd413168c8dfacbeda608a4946c6e3557b7d5a F ext/jni/src/org/sqlite/jni/annotation/NotNull.java 02091a8112e33389f1c160f506cd413168c8dfacbeda608a4946c6e3557b7d5a
F ext/jni/src/org/sqlite/jni/annotation/Nullable.java 0b1879852707f752512d4db9d7edd0d8db2f0c2612316ce1c832715e012ff6ba 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/AuthorizerCallback.java c045a5b47e02bb5f1af91973814a905f12048c428a3504fbc5266d1c1be3de5a
F ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java 74cc4998a73d6563542ecb90804a3c4f4e828cb4bd69e61226d1a51f4646e759 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/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/CallbackProxy.java 57e2d275dcebe690b1fc1f3d34eb96879b2d7039bce30b563aee547bf45d8a8b
F ext/jni/src/org/sqlite/jni/capi/CollationCallback.java e29bcfc540fdd343e2f5cca4d27235113f2886acb13380686756d5cabdfd065a F ext/jni/src/org/sqlite/jni/capi/CollationCallback.java e29bcfc540fdd343e2f5cca4d27235113f2886acb13380686756d5cabdfd065a
F ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java 5bfa226a8e7a92e804fd52d6e42b4c7b875fa7a94f8e2c330af8cc244a8920ab 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.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 46656b354311ec0a36832af1c4ccb3b6a244aa55cfb3681e25c3f42b13b387dd P 7df317b448a09ae77e2c68cc901fdb6d56a2246c1313f06bebd1f3e53f02c19b
R 4e0b6f66f2c79b3b0f3d9c780b63f86e R dddfec0befd08ab210a022ab61f478a4
U stephan U stephan
Z 5994994aa13f973034ef1e21e15b70de Z 49fb00c3c45f37f7c83250698ea520ef
# Remove this line to create a well-formed Fossil manifest. # Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
7df317b448a09ae77e2c68cc901fdb6d56a2246c1313f06bebd1f3e53f02c19b 51539419edc08ee6c70d8719d0f4d5ad47dd545a7fd9bf01d03a434aabd41d68