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

Make JNI sqlite3_trace_v2() thread-safe. Re-add a piece removed in [bae4d022aad9b] to work around a JVM crash which is unpredictably triggered by its substitute. Fix the THREADMODE=0 JNI build. Further internal API simplifications.

FossilOrigin-Name: 3f9f7a9cb08b0687ad206605a5109306762df9ae8bdeab2d8d60bf9373c9ad32
This commit is contained in:
stephan
2023-08-27 10:40:00 +00:00
parent 32a79760b5
commit 95f5d85d4a
5 changed files with 115 additions and 90 deletions

View File

@ -404,9 +404,8 @@ struct S3JniHook{
/* We lookup the jObj.xDestroy() method as-needed for contexts which
** have custom finalizers. */
};
#if !defined(SQLITE_ENABLE_PREUPDATE_HOOK) || defined(SQLITE_ENABLE_SQLLOG)
/* For clean bitwise-copy init of local instances. */
static const S3JniHook S3JniHook_empty = {0,0};
#endif
/*
** Per-(sqlite3*) state for various JNI bindings. This state is
@ -456,10 +455,10 @@ struct S3JniEnv {
JNIEnv *env /* env in which this cache entry was created */;
/*
** pdbOpening is used to coordinate the Java/DB connection of a
** being-open()'d db in the face of auto-extensions. "The problem"
** is that auto-extensions run before we can bind the C db to its
** Java representation, but auto-extensions require that binding. We
** handle this as follows:
** being-open()'d db in the face of auto-extensions.
** Auto-extensions run before we can bind the C db to its Java
** representation, but auto-extensions require that binding to pass
** on to their Java-side callbacks. We handle this as follows:
**
** - In the JNI side of sqlite3_open(), allocate the Java side of
** that connection and set pdbOpening to point to that
@ -497,8 +496,8 @@ enum UDFType {
UDF_WINDOW
};
/**
State for binding Java-side UDFs.
/*
** State for binding Java-side UDFs.
*/
typedef struct S3JniUdf S3JniUdf;
struct S3JniUdf {
@ -506,11 +505,11 @@ struct S3JniUdf {
char * zFuncName /* Only for error reporting and debug logging */;
enum UDFType type;
/** Method IDs for the various UDF methods. */
jmethodID jmidxFunc /* Java ID of xFunc method */;
jmethodID jmidxStep /* Java ID of xStep method */;
jmethodID jmidxFinal /* Java ID of xFinal method */;
jmethodID jmidxValue /* Java ID of xValue method */;
jmethodID jmidxInverse /* Java ID of xInverse method */;
jmethodID jmidxFunc /* xFunc method */;
jmethodID jmidxStep /* xStep method */;
jmethodID jmidxFinal /* xFinal method */;
jmethodID jmidxValue /* xValue method */;
jmethodID jmidxInverse /* xInverse method */;
S3JniUdf * pNext /* Next entry in SJG.udf.aFree. */;
};
@ -592,6 +591,7 @@ struct S3JniGlobalType {
** and never released.
*/
struct {
jclass cObj /* global ref to java.lang.Object */;
jclass cLong /* global ref to java.lang.Long */;
jclass cString /* global ref to java.lang.String */;
jobject oCharsetUtf8 /* global ref to StandardCharset.UTF_8 */;
@ -729,10 +729,10 @@ static void s3jni_incr( volatile unsigned int * const p ){
SJG.autoExt.locker = env; \
s3jni_incr( &SJG.metrics.nMutexAutoExt )
#define S3JniMutex_Ext_leave \
assert( env == SJG.autoExt.locker ); \
assert( env == SJG.autoExt.locker && "Misuse of S3JniGlobal.autoExt.mutex" ); \
sqlite3_mutex_leave( SJG.autoExt.mutex )
#define S3JniMutex_Ext_assertLocker \
assert( env == SJG.autoExt.locker )
assert( env == SJG.autoExt.locker && "Misuse of S3JniGlobal.autoExt.mutex" )
#define S3JniMutex_Global_enter \
sqlite3_mutex_enter( SJG.mutex ); \
@ -775,6 +775,7 @@ static void s3jni_incr( volatile unsigned int * const p ){
#define S3JniMutex_Global_leave
#define S3JniMutex_Nph_enter
#define S3JniMutex_Nph_leave
#define S3JniMutex_S3JniDb_assertLocker
#define S3JniMutex_S3JniDb_enter
#define S3JniMutex_S3JniDb_leave
#endif
@ -1090,8 +1091,8 @@ static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj){
** cleared. It is legal to call this when the object has no Java
** references.
*/
static void S3JniHook_unref(JNIEnv * const env, S3JniHook * const s,
int doXDestroy){
static void S3JniHook_unref_impl(JNIEnv * const env, S3JniHook * const s,
int doXDestroy){
if( s->jObj ){
if( doXDestroy ){
s3jni_call_xDestroy(env, s->jObj);
@ -1100,17 +1101,20 @@ static void S3JniHook_unref(JNIEnv * const env, S3JniHook * const s,
}
memset(s, 0, sizeof(*s));
}
#define S3JniHook_unref(H,X) S3JniHook_unref_impl(env, (H), (X))
/*
** Internal helper for many hook callback impls. Locks the S3JniDb
** mutex, makes a copy of src into dest, with one change: if src->jObj
** is not NULL then dest->jObj will be a new LOCAL ref to src->jObj
** instead of a copy of the prior GLOBAL ref. Then it unlocks the
** mutex. If dest->jObj is not NULL when this returns then the caller
** is obligated to eventually free the new ref by passing dest->jObj
** to S3JniUnrefLocal(). The dest pointer must NOT be passed to
** S3JniHook_unref(), as that one assumes that dest->jObj is a GLOBAL
** ref (it's illegal to try to unref the wrong ref type)..
** mutex.
**
** If dest->jObj is not NULL when this returns then the caller is
** obligated to eventually free the new ref by passing *dest to
** S3JniHook_localundup(). The dest pointer must NOT be passed to
** S3JniHook_unref(), as that routine assumes that dest->jObj is a
** GLOBAL ref (it's illegal to try to unref the wrong ref type)..
**
** Background: when running a hook we need a call-local copy lest
** another thread modify the hook while we're running it. That copy
@ -1123,6 +1127,7 @@ static void S3JniHook_localdup( JNIEnv * const env, S3JniHook const * const src,
if(dest->jObj) dest->jObj = S3JniRefLocal(dest->jObj);
S3JniMutex_S3JniDb_leave;
}
#define S3JniHook_localundup(HOOK) S3JniUnrefLocal(HOOK.jObj)
/*
** Clears all of s's state. Requires that that the caller has locked
@ -1132,7 +1137,7 @@ static void S3JniHook_localdup( JNIEnv * const env, S3JniHook const * const src,
static void S3JniDb_clear(JNIEnv * const env, S3JniDb * const s){
S3JniMutex_S3JniDb_assertLocker;
sqlite3_free( s->zMainDbName );
#define UNHOOK(MEMBER) S3JniHook_unref(env, &s->hooks.MEMBER, 0)
#define UNHOOK(MEMBER) S3JniHook_unref(&s->hooks.MEMBER, 0)
UNHOOK(auth);
UNHOOK(busyHandler);
UNHOOK(collation);
@ -1380,7 +1385,7 @@ static S3JniDb * S3JniDb_get(JNIEnv * const env, jobject jDb, sqlite3 *pDb){
** Unref any Java-side state in (S3JniAutoExtension*) AX and zero out
** AX.
*/
#define S3JniAutoExtension_clear(AX) S3JniHook_unref(env, AX, 0);
#define S3JniAutoExtension_clear(AX) S3JniHook_unref(AX, 0);
/*
** Initializes a pre-allocated S3JniAutoExtension object. Returns
@ -1561,7 +1566,7 @@ static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs,
S3JniExceptionIgnore;
S3JniUnrefLocal(jbaLhs);
S3JniUnrefLocal(jbaRhs);
S3JniUnrefLocal(hook.jObj);
S3JniHook_localundup(hook);
}
return (int)rc;
}
@ -1572,7 +1577,7 @@ static void CollationState_xDestroy(void *pArg){
S3JniDeclLocal_env;
S3JniMutex_S3JniDb_enter;
S3JniHook_unref( env, &ps->hooks.collation, 1 );
S3JniHook_unref(&ps->hooks.collation, 1);
S3JniMutex_S3JniDb_leave;
}
@ -1750,12 +1755,14 @@ static int udf_args(JNIEnv *env,
jobjectArray ja = 0;
jobject jcx = new_sqlite3_context_wrapper(env, cx);
jint i;
S3JniNphClass * const pNC =
S3JniGlobal_nph(env, &S3NphRefs.sqlite3_value);
*jCx = 0;
*jArgv = 0;
if( !jcx ) goto error_oom;
ja = (*env)->NewObjectArray(env, argc, pNC->klazz, NULL);
ja = (*env)->NewObjectArray(
env, argc, SJG.g.cObj
/* S3JniGlobal_nph(env,&S3NphRefs.sqlite3_value)->klazz would be
more correct, but it unpredictably triggers an assert in the
JVM. */, NULL);
s3jni_oom_check( ja );
if( !ja ) goto error_oom;
for(i = 0; i < argc; ++i){
@ -2240,7 +2247,7 @@ static int s3jni_busy_handler(void* pState, int n){
rc = s3jni_db_exception(env, ps, SQLITE_ERROR,
"sqlite3_busy_handler() callback threw.");
}
S3JniUnrefLocal(hook.jObj);
S3JniHook_localundup(hook);
}
return rc;
}
@ -2260,17 +2267,17 @@ S3JniApi(sqlite3_busy_handler(),jint,1busy_1handler)(
return 0;
}
jclass klazz;
S3JniHook_unref(env, pHook, 0);
S3JniHook_unref(pHook, 0);
pHook->jObj = S3JniRefGlobal(jBusy);
klazz = (*env)->GetObjectClass(env, jBusy);
pHook->midCallback = (*env)->GetMethodID(env, klazz, "call", "(I)I");
S3JniUnrefLocal(klazz);
S3JniIfThrew {
S3JniHook_unref(env, pHook, 0);
S3JniHook_unref(pHook, 0);
rc = SQLITE_ERROR;
}
}else{
S3JniHook_unref(env, pHook, 0);
S3JniHook_unref(pHook, 0);
}
if( 0==rc ){
rc = jBusy
@ -2288,7 +2295,7 @@ S3JniApi(sqlite3_busy_timeout(),jint,1busy_1timeout)(
int rc = SQLITE_MISUSE;
if( ps ){
S3JniMutex_S3JniDb_enter;
S3JniHook_unref(env, &ps->hooks.busyHandler, 0);
S3JniHook_unref(&ps->hooks.busyHandler, 0);
rc = sqlite3_busy_timeout(ps->pDb, (int)ms);
S3JniMutex_S3JniDb_leave;
}
@ -2387,7 +2394,7 @@ static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb,
}
S3JniUnrefLocal(jName);
}
S3JniUnrefLocal(hook.jObj);
S3JniHook_localundup(hook);
}
}
@ -2404,7 +2411,7 @@ S3JniApi(sqlite3_collation_needed(),jint,1collation_1needed)(
(*env)->IsSameObject(env, pHook->jObj, jHook) ){
/* no-op */
}else if( !jHook ){
S3JniHook_unref(env, pHook, 0);
S3JniHook_unref(pHook, 0);
sqlite3_collation_needed(ps->pDb, 0, 0);
}else{
jclass const klazz = (*env)->GetObjectClass(env, jHook);
@ -2420,7 +2427,7 @@ S3JniApi(sqlite3_collation_needed(),jint,1collation_1needed)(
rc = sqlite3_collation_needed16(ps->pDb, ps, s3jni_collation_needed_impl16);
if( rc ){
}else{
S3JniHook_unref(env, pHook, 0);
S3JniHook_unref(pHook, 0);
pHook->midCallback = xCallback;
pHook->jObj = S3JniRefGlobal(jHook);
}
@ -2501,7 +2508,7 @@ static int s3jni_commit_rollback_hook_impl(int isCommit,
S3JniExceptionClear;
rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "hook callback threw.");
}
S3JniUnrefLocal(hook.jObj);
S3JniHook_localundup(hook);
}
return rc;
}
@ -2634,7 +2641,7 @@ static void s3jni_config_sqllog(void *ignored, sqlite3 *pDb, const char *z, int
S3JniExceptionWarnCallbackThrew("SQLITE_CONFIG_SQLLOG callback");
S3JniExceptionClear;
}
S3JniUnrefLocal(hook.jObj);
S3JniHook_localundup(hook);
S3JniUnrefLocal(jArg0);
S3JniUnrefLocal(jArg1);
}
@ -2656,7 +2663,7 @@ S3JniApi(sqlite3_config() /* for SQLLOG */,
S3JniMutex_Global_enter;
if( !jLog ){
S3JniHook_unref(env, pHook, 0);
S3JniHook_unref(pHook, 0);
}else if( pHook->jObj && (*env)->IsSameObject(env, jLog, pHook->jObj) ){
/* No-op */
}else {
@ -2669,7 +2676,7 @@ S3JniApi(sqlite3_config() /* for SQLLOG */,
if( midCallback ){
rc = sqlite3_config( SQLITE_CONFIG_SQLLOG, s3jni_config_sqllog, 0 );
if( 0==rc ){
S3JniHook_unref(env, pHook, 0);
S3JniHook_unref(pHook, 0);
pHook->midCallback = midCallback;
pHook->jObj = S3JniRefGlobal(jLog);
}
@ -2718,7 +2725,7 @@ S3JniApi(sqlite3_create_collation() sqlite3_create_collation_v2(),
CollationState_xDestroy);
sqlite3_free(zName);
if( 0==rc ){
S3JniHook_unref( env, &ps->hooks.collation, 1 );
S3JniHook_unref( &ps->hooks.collation, 1 );
ps->hooks.collation.midCallback = midCallback;
ps->hooks.collation.jObj = S3JniRefGlobal(oCollation);
}
@ -3080,7 +3087,8 @@ static int s3jni_open_post(JNIEnv * const env, S3JniEnv * const jc,
NativePointerHolder_set(env, &S3NphRefs.sqlite3, ps->jDb, *ppDb)
/* As of here, the Java/C connection is complete */;
}else{
assert( ps->pDb == *ppDb /* set up via s3jni_run_java_auto_extensions() */);
assert( ps->pDb==*ppDb
&& "Set up via s3jni_run_java_auto_extensions()" );
}
}else{
S3JniDb_set_aside(env, ps);
@ -3100,8 +3108,8 @@ S3JniApi(sqlite3_open(),jint,1open)(
int rc;
rc = s3jni_open_pre(env, &jc, strName, &zName, &ps);
if( 0==rc ){
rc = sqlite3_open(zName, &pOut);
rc = s3jni_open_post(env, jc, ps, &pOut, jOut, rc);
rc = s3jni_open_post(env, jc, ps, &pOut, jOut,
sqlite3_open(zName, &pOut));
assert(rc==0 ? pOut!=0 : 1);
sqlite3_free(zName);
}
@ -3270,7 +3278,7 @@ static void s3jni_updatepre_hook_impl(void * pState, sqlite3 *pDb, int opId,
}
S3JniUnrefLocal(jDbName);
S3JniUnrefLocal(jTable);
S3JniUnrefLocal(hook.jObj);
S3JniHook_localundup(hook);
}
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
@ -3436,7 +3444,7 @@ static int s3jni_progress_handler_impl(void *pP){
rc = s3jni_db_exception(env, ps, rc,
"sqlite3_progress_handler() callback threw");
}
S3JniUnrefLocal(hook.jObj);
S3JniHook_localundup(hook);
}
return rc;
}
@ -3452,7 +3460,7 @@ S3JniApi(sqlite3_progress_handler(),void,1progress_1handler)(
if( !ps ) return;
S3JniMutex_S3JniDb_enter;
if( n<1 || !jProgress ){
S3JniHook_unref(env, pHook, 0);
S3JniHook_unref(pHook, 0);
sqlite3_progress_handler(ps->pDb, 0, 0, 0);
S3JniMutex_S3JniDb_leave;
return;
@ -3738,7 +3746,7 @@ int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1,
S3JniUnrefLocal(s1);
S3JniUnrefLocal(s2);
S3JniUnrefLocal(s3);
S3JniUnrefLocal(hook.jObj);
S3JniHook_localundup(hook);
}
return rc;
}
@ -3753,7 +3761,7 @@ S3JniApi(sqlite3_set_authorizer(),jint,1set_1authorizer)(
if( !ps ) return SQLITE_MISUSE;
S3JniMutex_S3JniDb_enter;
if( !jHook ){
S3JniHook_unref(env, pHook, 0);
S3JniHook_unref(pHook, 0);
rc = sqlite3_set_authorizer( ps->pDb, 0, 0 );
}else{
jclass klazz;
@ -3763,7 +3771,7 @@ S3JniApi(sqlite3_set_authorizer(),jint,1set_1authorizer)(
S3JniMutex_S3JniDb_leave;
return 0;
}
S3JniHook_unref(env, pHook, 0);
S3JniHook_unref(pHook, 0);
}
pHook->jObj = S3JniRefGlobal(jHook);
klazz = (*env)->GetObjectClass(env, jHook);
@ -3782,7 +3790,7 @@ S3JniApi(sqlite3_set_authorizer(),jint,1set_1authorizer)(
}else{
rc = sqlite3_set_authorizer(ps->pDb, s3jni_xAuth, ps);
}
if( rc ) S3JniHook_unref(env, pHook, 0);
if( rc ) S3JniHook_unref(pHook, 0);
}
S3JniMutex_S3JniDb_leave;
return rc;
@ -3978,7 +3986,7 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){
}
S3JniUnrefLocal(jPUnref);
S3JniUnrefLocal(jX);
S3JniUnrefLocal(hook.jObj);
S3JniHook_localundup(hook);
return rc;
}
@ -3986,27 +3994,40 @@ S3JniApi(sqlite3_trace_v2(),jint,1trace_1v2)(
JniArgsEnvClass,jobject jDb, jint traceMask, jobject jTracer
){
S3JniDb * const ps = S3JniDb_from_java(jDb);
S3JniHook * const pHook = ps ? &ps->hooks.trace : 0;
jclass klazz;
int rc;
if( !ps ) return SQLITE_MISUSE;
else if( !traceMask || !jTracer ){
S3JniHook_unref(env, pHook, 0);
return (jint)sqlite3_trace_v2(ps->pDb, 0, 0, 0);
if( !traceMask || !jTracer ){
S3JniMutex_S3JniDb_enter;
rc = (jint)sqlite3_trace_v2(ps->pDb, 0, 0, 0);
S3JniHook_unref(&ps->hooks.trace, 0);
S3JniMutex_S3JniDb_leave;
}else{
jclass const klazz = (*env)->GetObjectClass(env, jTracer);
S3JniHook hook = S3JniHook_empty;
hook.midCallback = (*env)->GetMethodID(
env, klazz, "call", "(ILjava/lang/Object;Ljava/lang/Object;)I"
);
S3JniUnrefLocal(klazz);
S3JniIfThrew {
S3JniExceptionClear;
rc = s3jni_db_error(ps->pDb, SQLITE_ERROR,
"Cannot not find matching call() on "
"TracerCallback object.");
}else{
S3JniMutex_S3JniDb_enter;
hook.jObj = S3JniRefGlobal(jTracer);
rc = sqlite3_trace_v2(ps->pDb, (unsigned)traceMask, s3jni_trace_impl, ps);
if( 0==rc ){
S3JniHook_unref(&ps->hooks.trace, 0);
ps->hooks.trace = hook;
}else{
S3JniHook_unref(&hook, 0);
}
S3JniMutex_S3JniDb_leave;
}
}
klazz = (*env)->GetObjectClass(env, jTracer);
pHook->midCallback = (*env)->GetMethodID(
env, klazz, "call", "(ILjava/lang/Object;Ljava/lang/Object;)I"
);
S3JniUnrefLocal(klazz);
S3JniIfThrew {
S3JniExceptionClear;
S3JniHook_unref(env, pHook, 0);
return s3jni_db_error(ps->pDb, SQLITE_ERROR,
"Cannot not find matching xCallback() on Tracer object.");
}
pHook->jObj = S3JniRefGlobal(jTracer);
return sqlite3_trace_v2(ps->pDb, (unsigned)traceMask, s3jni_trace_impl, ps);
return rc;
}
S3JniApi(sqlite3_update_hook(),jobject,1update_1hook)(
@ -5023,6 +5044,8 @@ Java_org_sqlite_jni_SQLite3Jni_init(JniArgsEnvClass){
}
/* Grab references to various global classes and objects... */
SJG.g.cObj = S3JniRefGlobal((*env)->FindClass(env,"java/lang/Object"));
S3JniExceptionIsFatal("Error getting reference to Object class.");
SJG.g.cLong = S3JniRefGlobal((*env)->FindClass(env,"java/lang/Long"));
S3JniExceptionIsFatal("Error getting reference to Long class.");

View File

@ -1151,7 +1151,7 @@ public final class SQLite3Jni {
/**
In addition to calling the C-level sqlite3_shutdown(), the JNI
binding also cleans up all stale per-thread state managed by the
library, as well as any registered auto-extensions and free up
library, as well as any registered auto-extensions, and frees up
various bits of memory. Calling this while database handles or
prepared statements are still active will leak resources. Trying
to use those objects after this routine is called invoked
@ -1179,8 +1179,8 @@ public final class SQLite3Jni {
public static native int sqlite3_step(@NotNull sqlite3_stmt stmt);
/**
Internal impl of the public sqlite3_strglob() method. Neither argument
may be NULL and both MUST be NUL-terminated.
Internal impl of the public sqlite3_strglob() method. Neither
argument may be NULL and both MUST be NUL-terminated UTF-8.
*/
private static native int sqlite3_strglob(
@NotNull byte[] glob, @NotNull byte[] txt
@ -1197,7 +1197,7 @@ public final class SQLite3Jni {
/**
Internal impl of the public sqlite3_strlike() method. Neither
argument may be NULL and both MUST be NUL-terminated.
argument may be NULL and both MUST be NUL-terminated UTF-8.
*/
private static native int sqlite3_strlike(
@NotNull byte[] glob, @NotNull byte[] txt, int escChar
@ -1225,9 +1225,8 @@ public final class SQLite3Jni {
arguments are encapsulated in the final argument to this function.
Unlike the C API, which is documented as always returning 0, this
implementation returns SQLITE_NOMEM if allocation of per-db
mapping state fails and SQLITE_ERROR if the given callback object
cannot be processed propertly (i.e. an internal error).
implementation returns non-0 if initialization of the tracer
mapping state fails.
*/
public static native int sqlite3_trace_v2(
@NotNull sqlite3 db, int traceMask, @Nullable TraceV2Callback tracer