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:
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
14
manifest
14
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.
|
||||
|
@ -1 +1 @@
|
||||
7df317b448a09ae77e2c68cc901fdb6d56a2246c1313f06bebd1f3e53f02c19b
|
||||
51539419edc08ee6c70d8719d0f4d5ad47dd545a7fd9bf01d03a434aabd41d68
|
Reference in New Issue
Block a user