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
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;
}
}

View 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