From: stephan Date: Mon, 28 Aug 2023 20:21:56 +0000 (+0000) Subject: Correct JNI mapping of collations to be 1-db-to-many-collations. X-Git-Tag: version-3.44.0~248 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a08f73750375249268a599ca228cef618fbc51f8;p=thirdparty%2Fsqlite.git Correct JNI mapping of collations to be 1-db-to-many-collations. FossilOrigin-Name: b927b0f5a68684b0a9799396d153bf1e2306351e8039c2bacb3d5b2056a0634f --- diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index a78b07166b..dfa929ce90 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -429,7 +429,6 @@ struct S3JniDb { SQLITE_DBCONFIG_MAINDBNAME. */; struct { S3JniHook busyHandler; - S3JniHook collation; S3JniHook collationNeeded; S3JniHook commit; S3JniHook progress; @@ -1021,12 +1020,14 @@ static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx){ ** problem extracting the exception's message, it's treated as ** non-fatal and zDfltMsg is used in its place. ** +** Locks the global S3JniDb mutex. +** ** This must only be called if a JNI exception is pending. ** ** Returns errCode unless it is 0, in which case SQLITE_ERROR is ** returned. */ -static int s3jni_db_exception(JNIEnv * const env, S3JniDb * const ps, +static int s3jni__db_exception(JNIEnv * const env, S3JniDb * const ps, int errCode, const char *zDfltMsg){ jthrowable const ex = (*env)->ExceptionOccurred(env); @@ -1034,13 +1035,17 @@ static int s3jni_db_exception(JNIEnv * const env, S3JniDb * const ps, if( ex ){ char * zMsg; S3JniExceptionClear; + S3JniMutex_S3JniDb_enter; zMsg = s3jni_exception_error_msg(env, ex); s3jni_db_error(ps->pDb, errCode, zMsg ? zMsg : zDfltMsg); sqlite3_free(zMsg); S3JniUnrefLocal(ex); + S3JniMutex_S3JniDb_leave; } return errCode; } +#define s3jni_db_exception(JniDb,ERRCODE,DFLTMSG) \ + s3jni__db_exception(env, (JniDb), (ERRCODE), (DFLTMSG) ) /* ** Extracts the (void xDestroy()) method from jObj and applies it to @@ -1049,7 +1054,7 @@ static int s3jni_db_exception(JNIEnv * const env, S3JniDb * const ps, ** trigger a warning to stdout or stderr and then the exception is ** suppressed. */ -static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj){ +static void s3jni__call_xDestroy(JNIEnv * const env, jobject jObj){ if( jObj ){ jclass const klazz = (*env)->GetObjectClass(env, jObj); jmethodID method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V"); @@ -1068,25 +1073,7 @@ static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj){ } } } - -/* -** Removes any Java references from s and clears its state. If -** doXDestroy is true and s->jObj is not NULL, s->jObj's -** s is passed to s3jni_call_xDestroy() before any references are -** cleared. It is legal to call this when the object has no Java -** references. -*/ -static void S3JniHook_unref_impl(JNIEnv * const env, S3JniHook * const s, - int doXDestroy){ - if( s->jObj ){ - if( doXDestroy ){ - s3jni_call_xDestroy(env, s->jObj); - } - S3JniUnrefGlobal(s->jObj); - } - memset(s, 0, sizeof(*s)); -} -#define S3JniHook_unref(H,X) S3JniHook_unref_impl(env, (H), (X)) +#define s3jni_call_xDestroy(JOBJ) s3jni__call_xDestroy(env, (JOBJ)) /* ** Internal helper for many hook callback impls. Locks the S3JniDb @@ -1105,15 +1092,36 @@ static void S3JniHook_unref_impl(JNIEnv * const env, S3JniHook * const s, ** another thread modify the hook while we're running it. That copy ** has to have its own Java reference, but it need only be call-local. */ -static void S3JniHook_localdup( JNIEnv * const env, S3JniHook const * const src, - S3JniHook * const dest ){ +static void S3JniHook__localdup( JNIEnv * const env, S3JniHook const * const src, + S3JniHook * const dest ){ S3JniMutex_S3JniDb_enter; *dest = *src; if(dest->jObj) dest->jObj = S3JniRefLocal(dest->jObj); S3JniMutex_S3JniDb_leave; } +#define S3JniHook_localdup(src,dest) S3JniHook__localdup(env,src,dest) #define S3JniHook_localundup(HOOK) S3JniUnrefLocal(HOOK.jObj) +/* +** Removes any Java references from s and clears its state. If +** doXDestroy is true and s->jObj is not NULL, s->jObj's +** s is passed to s3jni_call_xDestroy() before any references are +** 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){ + if( s->jObj ){ + if( doXDestroy ){ + s3jni_call_xDestroy(s->jObj); + } + S3JniUnrefGlobal(s->jObj); + } + memset(s, 0, sizeof(*s)); +} +#define S3JniHook_unref(hook,doDestroy) \ + S3JniHook__unref(env, (hook), (doDestroy)) + /* ** Clears all of s's state. Requires that that the caller has locked ** S3JniGlobal.perDb.mutex. Make sure to do anything needed with @@ -1125,7 +1133,6 @@ static void S3JniDb_clear(JNIEnv * const env, S3JniDb * const s){ #define UNHOOK(MEMBER) S3JniHook_unref(&s->hooks.MEMBER, 0) UNHOOK(auth); UNHOOK(busyHandler); - UNHOOK(collation); UNHOOK(collationNeeded); UNHOOK(commit); UNHOOK(progress); @@ -1163,10 +1170,11 @@ static void S3JniDb__set_aside_unlocked(JNIEnv * const env, S3JniDb * const s){ SJG.perDb.aFree = s; } } +#define S3JniDb_set_aside_unlocked(JniDb) S3JniDb__set_aside_unlocked(env, JniDb) static void S3JniDb__set_aside(JNIEnv * const env, S3JniDb * const s){ S3JniMutex_S3JniDb_enter; - S3JniDb__set_aside_unlocked(env, s); + S3JniDb_set_aside_unlocked(s); S3JniMutex_S3JniDb_leave; } #define S3JniDb_set_aside(JNIDB) S3JniDb__set_aside(env, JNIDB) @@ -1294,10 +1302,8 @@ static jfieldID s3jni_nphop_field(JNIEnv * const env, S3JniNphRef const* pRef){ */ static void NativePointerHolder__set(JNIEnv * const env, S3JniNphRef const* pRef, jobject ppOut, const void * p){ - jfieldID const fid = s3jni_nphop_field(env, pRef); - assert( ppOut ); - (*env)->SetLongField(env, ppOut, fid, (jlong)p); + (*env)->SetLongField(env, ppOut, s3jni_nphop_field(env, pRef), (jlong)p); S3JniExceptionIsFatal("Could not set NativePointerHolder.nativePointer."); } @@ -1313,8 +1319,7 @@ static void * NativePointerHolder__get(JNIEnv * env, jobject pObj, S3JniNphRef const* pRef){ void * rv = 0; if( pObj ){ - jfieldID const fid = s3jni_nphop_field(env, pRef); - rv = (void*)(*env)->GetLongField(env, pObj, fid); + rv = (void*)(*env)->GetLongField(env, pObj, s3jni_nphop_field(env, pRef)); S3JniExceptionIsFatal("Cannot fetch NativePointerHolder.nativePointer."); } return rv; @@ -1427,7 +1432,7 @@ static S3JniDb * S3JniDb__from_java(JNIEnv * const env, jobject jDb){ /* An experiment */ //#define CLOSE_DB_LOCKED -#if defined(CLOSE_DB_LOCKED) +#if 1 || defined(CLOSE_DB_LOCKED) static S3JniDb * S3JniDb__from_java_unlocked(JNIEnv * const env, jobject jDb){ S3JniDb * s = 0; sqlite3 * pDb = 0; @@ -1436,7 +1441,9 @@ static S3JniDb * S3JniDb__from_java_unlocked(JNIEnv * const env, jobject jDb){ if( jDb ) pDb = PtrGet_sqlite3(jDb); S3JniDb_search; return s; + } +#define S3JniDb_from_java_unlocked(JDB) S3JniDb__from_java_unlocked(env, (JDB)) #endif /* @@ -1589,45 +1596,44 @@ static int encodingTypeIsValid(int eTextRep){ } } +/** + State for CollationCallbacks. +*/ +typedef S3JniHook S3JniCollationCallback; + /* ** Proxy for Java-side CollationCallback.xCompare() callbacks. */ static int CollationCallback_xCompare(void *pArg, int nLhs, const void *lhs, int nRhs, const void *rhs){ - S3JniDb * const ps = pArg; + S3JniCollationCallback * const pCC = pArg; S3JniDeclLocal_env; jint rc = 0; - S3JniHook hook; - - S3JniHook_localdup(env, &ps->hooks.collation, &hook ); - if( hook.jObj ){ + if( pCC->jObj ){ jbyteArray jbaLhs = s3jni_new_jbyteArray(lhs, (jint)nLhs); jbyteArray jbaRhs = jbaLhs ? s3jni_new_jbyteArray(rhs, (jint)nRhs) : 0; if( !jbaRhs ){ S3JniUnrefLocal(jbaLhs); - s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + /* We have no recovery strategy here. */ + s3jni_oom_check( jbaRhs ); return 0; } - rc = (*env)->CallIntMethod(env, ps->hooks.collation.jObj, - ps->hooks.collation.midCallback, + rc = (*env)->CallIntMethod(env, pCC->jObj, pCC->midCallback, jbaLhs, jbaRhs); S3JniExceptionIgnore; S3JniUnrefLocal(jbaLhs); S3JniUnrefLocal(jbaRhs); - S3JniHook_localundup(hook); } return (int)rc; } /* CollationCallback finalizer for use by the sqlite3 internals. */ static void CollationCallback_xDestroy(void *pArg){ - S3JniDb * const ps = pArg; + S3JniCollationCallback * const pCC = pArg; S3JniDeclLocal_env; - - S3JniMutex_S3JniDb_enter; - S3JniHook_unref(&ps->hooks.collation, 1); - S3JniMutex_S3JniDb_leave; + S3JniHook_unref(pCC, 1); + sqlite3_free(pCC); } /* For use with sqlite3_result/value_pointer() */ @@ -1763,11 +1769,13 @@ static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){ static void S3JniUdf_free(JNIEnv * const env, S3JniUdf * const s, int cacheIt){ assert( !s->pNext ); - s3jni_call_xDestroy(env, s->jObj); - S3JniUnrefGlobal(s->jObj); - sqlite3_free(s->zFuncName); - assert( !s->pNext ); - memset(s, 0, sizeof(*s)); + if( s->jObj ){ + s3jni_call_xDestroy(s->jObj); + S3JniUnrefGlobal(s->jObj); + sqlite3_free(s->zFuncName); + assert( !s->pNext ); + memset(s, 0, sizeof(*s)); + } if( cacheIt ){ S3JniMutex_Global_enter; s->pNext = S3JniGlobal.udf.aFree; @@ -2309,13 +2317,13 @@ static int s3jni_busy_handler(void* pState, int n){ S3JniDeclLocal_env; S3JniHook hook; - S3JniHook_localdup(env, &ps->hooks.busyHandler, &hook ); + S3JniHook_localdup(&ps->hooks.busyHandler, &hook ); if( hook.jObj ){ rc = (*env)->CallIntMethod(env, hook.jObj, hook.midCallback, (jint)n); S3JniIfThrew{ S3JniExceptionWarnCallbackThrew("sqlite3_busy_handler() callback"); - rc = s3jni_db_exception(env, ps, SQLITE_ERROR, + rc = s3jni_db_exception(ps, SQLITE_ERROR, "sqlite3_busy_handler() callback threw."); } S3JniHook_localundup(hook); @@ -2328,33 +2336,42 @@ S3JniApi(sqlite3_busy_handler(),jint,1busy_1handler)( ){ S3JniDb * const ps = S3JniDb_from_java(jDb); S3JniHook * const pHook = ps ? &ps->hooks.busyHandler : 0; + S3JniHook hook = S3JniHook_empty; int rc = 0; + if( !ps ) return (jint)SQLITE_MISUSE; S3JniMutex_S3JniDb_enter; if( jBusy ){ if( pHook->jObj && (*env)->IsSameObject(env, pHook->jObj, jBusy) ){ /* Same object - this is a no-op. */ - S3JniMutex_S3JniDb_leave; - return 0; - } - jclass klazz; - 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(pHook, 0); - rc = SQLITE_ERROR; + }else{ + jclass const klazz = (*env)->GetObjectClass(env, jBusy); + hook.jObj = S3JniRefGlobal(jBusy); + hook.midCallback = (*env)->GetMethodID(env, klazz, "call", "(I)I"); + S3JniUnrefLocal(klazz); + S3JniIfThrew { + rc = SQLITE_ERROR; + } } - }else{ - S3JniHook_unref(pHook, 0); } if( 0==rc ){ - rc = jBusy - ? sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps) - : sqlite3_busy_handler(ps->pDb, 0, 0); + if( jBusy ){ + if( hook.jObj ){ /* Replace handler */ + rc = sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps); + if( 0==rc ){ + S3JniHook_unref(pHook, 0); + *pHook = hook; + hook = S3JniHook_empty; + } + }/* else no-op */ + }else{ /* Clear handler */ + rc = sqlite3_busy_handler(ps->pDb, 0, 0); + if( 0==rc ){ + S3JniHook_unref(pHook, 0); + } + } } + S3JniHook_unref(&hook, 0); S3JniMutex_S3JniDb_leave; return rc; } @@ -2388,9 +2405,8 @@ S3JniApi(sqlite3_cancel_auto_extension(),jboolean,1cancel_1auto_1extension)( /* Move final entry into this slot. */ --SJG.autoExt.nExt; *ax = SJG.autoExt.aExt[SJG.autoExt.nExt]; - memset(&SJG.autoExt.aExt[SJG.autoExt.nExt], 0, - sizeof(S3JniAutoExtension)); - assert(! SJG.autoExt.aExt[SJG.autoExt.nExt].jObj ); + memset(&SJG.autoExt.aExt[SJG.autoExt.nExt], 0, sizeof(*ax)); + assert( !SJG.autoExt.aExt[SJG.autoExt.nExt].jObj ); rc = JNI_TRUE; break; } @@ -2403,9 +2419,9 @@ S3JniApi(sqlite3_cancel_auto_extension(),jboolean,1cancel_1auto_1extension)( /* Wrapper for sqlite3_close(_v2)(). */ static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ int rc = 0; - #ifndef CLOSE_DB_LOCKED S3JniDb * const ps = S3JniDb_from_java(jDb); + assert(version == 1 || version == 2); if( ps ){ rc = 1==version @@ -2429,14 +2445,14 @@ static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ if( 0!=jDb ){ S3JniDb * ps; S3JniMutex_S3JniDb_enter; - ps = S3JniDb__from_java_unlocked(env, jDb); + ps = S3JniDb_from_java_unlocked(jDb); if( ps && ps->pDb ){ rc = 1==version ? (jint)sqlite3_close(ps->pDb) : (jint)sqlite3_close_v2(ps->pDb); if( 0==rc ){ - S3JniDb__set_aside_unlocked(env,ps) - /* MUST come after close() because of ps->trace. */; + S3JniDb_set_aside_unlocked(ps) + /* MUST come after close() because of ps->hooks.trace. */; NativePointerHolder_set(&S3JniNphRefs.sqlite3, jDb, 0); } }else{ @@ -2478,25 +2494,29 @@ static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, S3JniDeclLocal_env; S3JniHook hook; - S3JniHook_localdup(env, &ps->hooks.collationNeeded, &hook ); + S3JniHook_localdup(&ps->hooks.collationNeeded, &hook ); if( hook.jObj ){ unsigned int const nName = s3jni_utf16_strlen(z16Name); jstring jName = (*env)->NewString(env, (jchar const *)z16Name, nName); - s3jni_oom_check( jName ); S3JniIfThrew{ - s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); S3JniExceptionClear; }else{ - (*env)->CallVoidMethod(env, ps->hooks.collationNeeded.jObj, - ps->hooks.collationNeeded.midCallback, - ps->jDb, (jint)eTextRep, jName); - S3JniIfThrew{ - S3JniExceptionWarnCallbackThrew("sqlite3_collation_needed() callback"); - s3jni_db_exception(env, ps, 0, "sqlite3_collation_needed() callback threw"); + jobject jDb; + S3JniMutex_S3JniDb_enter; + jDb = ps->jDb ? S3JniRefLocal(ps->jDb) : 0; + S3JniMutex_S3JniDb_leave; + if( jDb ){ + (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback, + jDb, (jint)eTextRep, jName); + S3JniIfThrew{ + S3JniExceptionWarnCallbackThrew("sqlite3_collation_needed() callback"); + s3jni_db_exception(ps, 0, "sqlite3_collation_needed() callback threw"); + } + S3JniUnrefLocal(jDb); } - S3JniUnrefLocal(jName); } + S3JniUnrefLocal(jName); S3JniHook_localundup(hook); } } @@ -2504,18 +2524,25 @@ static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, S3JniApi(sqlite3_collation_needed(),jint,1collation_1needed)( JniArgsEnvClass, jobject jDb, jobject jHook ){ - S3JniDb * const ps = S3JniDb_from_java(jDb); - S3JniHook * const pHook = &ps->hooks.collationNeeded; + S3JniDb * ps; + S3JniHook * pHook; int rc = 0; - if( !ps ) return SQLITE_MISUSE; S3JniMutex_S3JniDb_enter; + ps = S3JniDb_from_java_unlocked(jDb); + if( !ps ){ + S3JniMutex_S3JniDb_leave; + return SQLITE_MISUSE; + } + pHook = &ps->hooks.collationNeeded; if( pHook->jObj && jHook && (*env)->IsSameObject(env, pHook->jObj, jHook) ){ /* no-op */ }else if( !jHook ){ - S3JniHook_unref(pHook, 0); - sqlite3_collation_needed(ps->pDb, 0, 0); + rc = sqlite3_collation_needed(ps->pDb, 0, 0); + if( 0==rc ){ + S3JniHook_unref(pHook, 0); + } }else{ jclass const klazz = (*env)->GetObjectClass(env, jHook); jmethodID const xCallback = (*env)->GetMethodID( @@ -2523,13 +2550,12 @@ S3JniApi(sqlite3_collation_needed(),jint,1collation_1needed)( ); S3JniUnrefLocal(klazz); S3JniIfThrew { - rc = s3jni_db_exception(env, ps, SQLITE_MISUSE, - "Cannot not find matching callback on " - "collation-needed hook object."); + rc = s3jni_db_exception(ps, SQLITE_MISUSE, + "Cannot not find matching call() in " + "CollationNeededCallback object."); }else{ rc = sqlite3_collation_needed16(ps->pDb, ps, s3jni_collation_needed_impl16); - if( rc ){ - }else{ + if( 0==rc ){ S3JniHook_unref(pHook, 0); pHook->midCallback = xCallback; pHook->jObj = S3JniRefGlobal(jHook); @@ -2603,15 +2629,17 @@ S3JniApi(sqlite3_column_value(),jobject,1column_1value)( return new_sqlite3_value_wrapper(env, sv); } -static int s3jni_commit_rollback_hook_impl(int isCommit, - S3JniDb * const ps){ +/* +** Impl for both commit hooks (if isCommit is true) or rollback hooks. +*/ +static int s3jni_commit_rollback_hook_impl(int isCommit, S3JniDb * const ps){ S3JniDeclLocal_env; int rc = 0; S3JniHook hook; - S3JniHook_localdup( env, isCommit - ? &ps->hooks.commit : &ps->hooks.rollback, - &hook); + S3JniHook_localdup(isCommit + ? &ps->hooks.commit : &ps->hooks.rollback, + &hook); if( hook.jObj ){ rc = isCommit ? (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback) @@ -2625,32 +2653,35 @@ static int s3jni_commit_rollback_hook_impl(int isCommit, return rc; } +/* C-to-Java commit hook wrapper. */ static int s3jni_commit_hook_impl(void *pP){ return s3jni_commit_rollback_hook_impl(1, pP); } +/* C-to-Java rollback hook wrapper. */ static void s3jni_rollback_hook_impl(void *pP){ (void)s3jni_commit_rollback_hook_impl(0, pP); } static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env, jobject jDb, jobject jHook){ - S3JniDb * const ps = S3JniDb_from_java(jDb); - jclass klazz; + S3JniDb * ps; jobject pOld = 0; - jmethodID xCallback; - S3JniHook * const pHook = - isCommit ? &ps->hooks.commit : &ps->hooks.rollback; + S3JniHook * pHook; + + S3JniMutex_S3JniDb_enter; + ps = S3JniDb_from_java_unlocked(jDb); if( !ps ){ s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + S3JniMutex_S3JniDb_leave; return 0; } + pHook = isCommit ? &ps->hooks.commit : &ps->hooks.rollback; pOld = pHook->jObj; if( pOld && jHook && (*env)->IsSameObject(env, pOld, jHook) ){ - return pOld; - } - if( !jHook ){ + /* No-op. */ + }else if( !jHook ){ if( pOld ){ jobject tmp = S3JniRefLocal(pOld); S3JniUnrefGlobal(pOld); @@ -2659,29 +2690,30 @@ static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env, memset(pHook, 0, sizeof(S3JniHook)); if( isCommit ) sqlite3_commit_hook(ps->pDb, 0, 0); else sqlite3_rollback_hook(ps->pDb, 0, 0); - return pOld; - } - klazz = (*env)->GetObjectClass(env, jHook); - xCallback = (*env)->GetMethodID(env, klazz, "call", - isCommit ? "()I" : "()V"); - S3JniUnrefLocal(klazz); - S3JniIfThrew { - S3JniExceptionReport; - S3JniExceptionClear; - s3jni_db_error(ps->pDb, SQLITE_ERROR, - "Cannot not find matching callback on " - "hook object."); }else{ - pHook->midCallback = xCallback; - pHook->jObj = S3JniRefGlobal(jHook); - if( isCommit ) sqlite3_commit_hook(ps->pDb, s3jni_commit_hook_impl, ps); - else sqlite3_rollback_hook(ps->pDb, s3jni_rollback_hook_impl, ps); - if( pOld ){ - jobject tmp = S3JniRefLocal(pOld); - S3JniUnrefGlobal(pOld); - pOld = tmp; + jclass const klazz = (*env)->GetObjectClass(env, jHook); + jmethodID const xCallback = (*env)->GetMethodID(env, klazz, "call", + isCommit ? "()I" : "()V"); + S3JniUnrefLocal(klazz); + S3JniIfThrew { + S3JniExceptionReport; + S3JniExceptionClear; + s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Cannot not find matching call() in" + "hook object."); + }else{ + pHook->midCallback = xCallback; + pHook->jObj = S3JniRefGlobal(jHook); + if( isCommit ) sqlite3_commit_hook(ps->pDb, s3jni_commit_hook_impl, ps); + else sqlite3_rollback_hook(ps->pDb, s3jni_rollback_hook_impl, ps); + if( pOld ){ + jobject tmp = S3JniRefLocal(pOld); + S3JniUnrefGlobal(pOld); + pOld = tmp; + } } } + S3JniMutex_S3JniDb_leave; return pOld; } @@ -2733,7 +2765,7 @@ static void s3jni_config_sqllog(void *ignored, sqlite3 *pDb, const char *z, int S3JniHook hook = S3JniHook_empty; if( ps ){ - S3JniHook_localdup(env, &SJG.hooks.sqllog, &hook); + S3JniHook_localdup(&SJG.hooks.sqllog, &hook); } if( !hook.jObj ) return; jArg0 = S3JniRefLocal(ps->jDb); @@ -2775,7 +2807,10 @@ S3JniApi(sqlite3_config() /* for SQLLOG */, S3JniMutex_Global_enter; if( !jLog ){ - S3JniHook_unref(pHook, 0); + rc = sqlite3_config( SQLITE_CONFIG_SQLLOG, s3jni_config_sqllog, 0 ); + if( 0==rc ){ + S3JniHook_unref(pHook, 0); + } }else if( pHook->jObj && (*env)->IsSameObject(env, jLog, pHook->jObj) ){ /* No-op */ }else { @@ -2825,26 +2860,28 @@ S3JniApi(sqlite3_create_collation() sqlite3_create_collation_v2(), midCallback = (*env)->GetMethodID(env, klazz, "call", "([B[B)I"); S3JniUnrefLocal(klazz); S3JniIfThrew{ - S3JniUnrefLocal(klazz); rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, - "Could not get xCompare() method for object."); + "Could not get call() method from " + "CollationCallback object."); }else{ char * const zName = s3jni_jstring_to_utf8( name, 0); - if( zName ){ - S3JniMutex_S3JniDb_enter; + S3JniCollationCallback * const pCC = + zName ? s3jni_malloc(sizeof(S3JniCollationCallback)) : 0; + if( pCC ){ + memset( pCC, 0, sizeof(*pCC) ); rc = sqlite3_create_collation_v2(ps->pDb, zName, (int)eTextRep, - ps, CollationCallback_xCompare, + pCC, CollationCallback_xCompare, CollationCallback_xDestroy); - sqlite3_free(zName); if( 0==rc ){ - S3JniHook_unref( &ps->hooks.collation, 1 ); - ps->hooks.collation.midCallback = midCallback; - ps->hooks.collation.jObj = S3JniRefGlobal(oCollation); + pCC->midCallback = midCallback; + pCC->jObj = S3JniRefGlobal(oCollation); + }else{ + CollationCallback_xDestroy(pCC); } - S3JniMutex_S3JniDb_leave; }else{ rc = SQLITE_NOMEM; } + sqlite3_free(zName); } return (jint)rc; } @@ -3363,7 +3400,7 @@ static void s3jni_updatepre_hook_impl(void * pState, sqlite3 *pDb, int opId, const int isPre = 0!=pDb; S3JniHook hook; - S3JniHook_localdup(env, isPre ? + S3JniHook_localdup(isPre ? #ifdef SQLITE_ENABLE_PREUPDATE_HOOK &ps->hooks.preUpdate #else @@ -3392,7 +3429,7 @@ static void s3jni_updatepre_hook_impl(void * pState, sqlite3 *pDb, int opId, (jint)opId, jDbName, jTable, (jlong)iKey1); S3JniIfThrew{ S3JniExceptionWarnCallbackThrew("sqlite3_(pre)update_hook() callback"); - s3jni_db_exception(env, ps, 0, + s3jni_db_exception(ps, 0, "sqlite3_(pre)update_hook() callback threw"); } } @@ -3516,19 +3553,21 @@ S3JniApi(sqlite3_preupdate_hook(),jobject,1preupdate_1hook)( static int s3jni_preupdate_newold(JNIEnv * const env, int isNew, jobject jDb, jint iCol, jobject jOut){ #ifdef SQLITE_ENABLE_PREUPDATE_HOOK - sqlite3_value * pOut = 0; sqlite3 * const pDb = PtrGet_sqlite3(jDb); - int rc; - int (*fOrig)(sqlite3*,int,sqlite3_value**) = - isNew ? sqlite3_preupdate_new : sqlite3_preupdate_old; - rc = fOrig(pDb, (int)iCol, &pOut); - if( 0==rc ){ - jobject pWrap = new_sqlite3_value_wrapper(env, pOut); - if( pWrap ){ - OutputPointer_set_sqlite3_value(env, jOut, pWrap); - S3JniUnrefLocal(pWrap); - }else{ - rc = SQLITE_NOMEM; + int rc = SQLITE_MISUSE; + if( pDb ){ + sqlite3_value * pOut = 0; + int (*fOrig)(sqlite3*,int,sqlite3_value**) = + isNew ? sqlite3_preupdate_new : sqlite3_preupdate_old; + rc = fOrig(pDb, (int)iCol, &pOut); + if( 0==rc ){ + jobject pWrap = new_sqlite3_value_wrapper(env, pOut); + if( pWrap ){ + OutputPointer_set_sqlite3_value(env, jOut, pWrap); + S3JniUnrefLocal(pWrap); + }else{ + rc = SQLITE_NOMEM; + } } } return rc; @@ -3557,11 +3596,11 @@ static int s3jni_progress_handler_impl(void *pP){ S3JniDeclLocal_env; S3JniHook hook; - S3JniHook_localdup( env, &ps->hooks.progress, &hook ); + S3JniHook_localdup(&ps->hooks.progress, &hook); if( hook.jObj ){ rc = (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback); S3JniIfThrew{ - rc = s3jni_db_exception(env, ps, rc, + rc = s3jni_db_exception(ps, rc, "sqlite3_progress_handler() callback threw"); } S3JniHook_localundup(hook); @@ -3573,8 +3612,6 @@ S3JniApi(sqlite3_progress_handler(),void,1progress_1handler)( JniArgsEnvClass,jobject jDb, jint n, jobject jProgress ){ S3JniDb * const ps = S3JniDb_from_java(jDb); - jclass klazz; - jmethodID xCallback; S3JniHook * const pHook = ps ? &ps->hooks.progress : 0; if( !ps ) return; @@ -3582,22 +3619,21 @@ S3JniApi(sqlite3_progress_handler(),void,1progress_1handler)( if( n<1 || !jProgress ){ S3JniHook_unref(pHook, 0); sqlite3_progress_handler(ps->pDb, 0, 0, 0); - S3JniMutex_S3JniDb_leave; - return; - } - klazz = (*env)->GetObjectClass(env, jProgress); - xCallback = (*env)->GetMethodID(env, klazz, "call", "()I"); - S3JniUnrefLocal(klazz); - S3JniIfThrew { - S3JniExceptionClear; - s3jni_db_error(ps->pDb, SQLITE_ERROR, - "Cannot not find matching xCallback() on " - "ProgressHandler object."); }else{ - S3JniUnrefGlobal(pHook->jObj); - pHook->midCallback = xCallback; - pHook->jObj = S3JniRefGlobal(jProgress); - sqlite3_progress_handler(ps->pDb, (int)n, s3jni_progress_handler_impl, ps); + jclass const klazz = (*env)->GetObjectClass(env, jProgress); + jmethodID const xCallback = (*env)->GetMethodID(env, klazz, "call", "()I"); + S3JniUnrefLocal(klazz); + S3JniIfThrew { + S3JniExceptionClear; + s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Cannot not find matching xCallback() on " + "ProgressHandler object."); + }else{ + S3JniUnrefGlobal(pHook->jObj); + pHook->midCallback = xCallback; + pHook->jObj = S3JniRefGlobal(jProgress); + sqlite3_progress_handler(ps->pDb, (int)n, s3jni_progress_handler_impl, ps); + } } S3JniMutex_S3JniDb_leave; } @@ -3752,7 +3788,7 @@ S3JniApi(sqlite3_result_error(),void,1result_1error)( S3JniApi(sqlite3_result_error_code(),void,1result_1error_1code)( JniArgsEnvClass, jobject jpCx, jint v ){ - sqlite3_result_error_code(PtrGet_sqlite3_context(jpCx), v ? (int)v : SQLITE_ERROR); + sqlite3_result_error_code(PtrGet_sqlite3_context(jpCx), (int)v); } S3JniApi(sqlite3_result_error_nomem(),void,1result_1error_1nomem)( @@ -3850,7 +3886,7 @@ int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1, S3JniHook hook; int rc = 0; - S3JniHook_localdup(env, &ps->hooks.auth, &hook ); + S3JniHook_localdup(&ps->hooks.auth, &hook ); if( hook.jObj ){ jstring const s0 = z0 ? s3jni_utf8_to_jstring( z0, -1) : 0; jstring const s1 = z1 ? s3jni_utf8_to_jstring( z1, -1) : 0; @@ -3860,7 +3896,7 @@ int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1, rc = (*env)->CallIntMethod(env, hook.jObj, hook.midCallback, (jint)op, s0, s1, s3, s3); S3JniIfThrew{ - rc = s3jni_db_exception(env, ps, rc, "sqlite3_set_authorizer() callback"); + rc = s3jni_db_exception(ps, rc, "sqlite3_set_authorizer() callback"); } S3JniUnrefLocal(s0); S3JniUnrefLocal(s1); @@ -3924,50 +3960,6 @@ S3JniApi(sqlite3_set_last_insert_rowid(),void,1set_1last_1insert_1rowid)( (sqlite3_int64)rowId); } -S3JniApi(sqlite3_status(),jint,1status)( - JniArgsEnvClass, jint op, jobject jOutCurrent, jobject jOutHigh, - jboolean reset -){ - int iCur = 0, iHigh = 0; - int rc = sqlite3_status( op, &iCur, &iHigh, reset ); - if( 0==rc ){ - OutputPointer_set_Int32(env, jOutCurrent, iCur); - OutputPointer_set_Int32(env, jOutHigh, iHigh); - } - return (jint)rc; -} - -S3JniApi(sqlite3_status64(),jint,1status64)( - JniArgsEnvClass, jint op, jobject jOutCurrent, jobject jOutHigh, - jboolean reset -){ - sqlite3_int64 iCur = 0, iHigh = 0; - int rc = sqlite3_status64( op, &iCur, &iHigh, reset ); - if( 0==rc ){ - OutputPointer_set_Int64(env, jOutCurrent, iCur); - OutputPointer_set_Int64(env, jOutHigh, iHigh); - } - return (jint)rc; -} - -static int s3jni_strlike_glob(int isLike, JNIEnv *const env, - jbyteArray baG, jbyteArray baT, jint escLike){ - int rc = 0; - jbyte * const pG = s3jni_jbytearray_bytes(baG); - jbyte * const pT = pG ? s3jni_jbytearray_bytes(baT) : 0; - - s3jni_oom_fatal(pT); - /* Note that we're relying on the byte arrays having been - NUL-terminated on the Java side. */ - rc = isLike - ? sqlite3_strlike((const char *)pG, (const char *)pT, - (unsigned int)escLike) - : sqlite3_strglob((const char *)pG, (const char *)pT); - s3jni_jbytearray_release(baG, pG); - s3jni_jbytearray_release(baT, pT); - return rc; -} - S3JniApi(sqlite3_shutdown(),jint,1shutdown)( JniArgsEnvClass ){ @@ -3997,7 +3989,6 @@ S3JniApi(sqlite3_shutdown(),jint,1shutdown)( sqlite3_free(d); } S3JniMutex_S3JniDb_leave; - #if 0 /* ** Is automatically closing any still-open dbs a good idea? We will @@ -4011,11 +4002,54 @@ S3JniApi(sqlite3_shutdown(),jint,1shutdown)( } S3JniMutex_S3JniDb_leave; #endif - /* Do not clear S3JniGlobal.jvm: it's legal to restart the lib. */ return sqlite3_shutdown(); } +S3JniApi(sqlite3_status(),jint,1status)( + JniArgsEnvClass, jint op, jobject jOutCurrent, jobject jOutHigh, + jboolean reset +){ + int iCur = 0, iHigh = 0; + int rc = sqlite3_status( op, &iCur, &iHigh, reset ); + if( 0==rc ){ + OutputPointer_set_Int32(env, jOutCurrent, iCur); + OutputPointer_set_Int32(env, jOutHigh, iHigh); + } + return (jint)rc; +} + +S3JniApi(sqlite3_status64(),jint,1status64)( + JniArgsEnvClass, jint op, jobject jOutCurrent, jobject jOutHigh, + jboolean reset +){ + sqlite3_int64 iCur = 0, iHigh = 0; + int rc = sqlite3_status64( op, &iCur, &iHigh, reset ); + if( 0==rc ){ + OutputPointer_set_Int64(env, jOutCurrent, iCur); + OutputPointer_set_Int64(env, jOutHigh, iHigh); + } + return (jint)rc; +} + +static int s3jni_strlike_glob(int isLike, JNIEnv *const env, + jbyteArray baG, jbyteArray baT, jint escLike){ + int rc = 0; + jbyte * const pG = s3jni_jbytearray_bytes(baG); + jbyte * const pT = pG ? s3jni_jbytearray_bytes(baT) : 0; + + s3jni_oom_fatal(pT); + /* Note that we're relying on the byte arrays having been + NUL-terminated on the Java side. */ + rc = isLike + ? sqlite3_strlike((const char *)pG, (const char *)pT, + (unsigned int)escLike) + : sqlite3_strglob((const char *)pG, (const char *)pT); + s3jni_jbytearray_release(baG, pG); + s3jni_jbytearray_release(baT, pT); + return rc; +} + S3JniApi(sqlite3_strglob(),jint,1strglob)( JniArgsEnvClass, jbyteArray baG, jbyteArray baT ){ @@ -4061,7 +4095,7 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ int rc = 0; S3JniHook hook; - S3JniHook_localdup( env, &ps->hooks.trace, &hook ); + S3JniHook_localdup(&ps->hooks.trace, &hook ); if( !hook.jObj ){ return 0; } @@ -4092,16 +4126,17 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ /* Create a new temporary sqlite3_stmt wrapper */ jP = jPUnref = new_sqlite3_stmt_wrapper(env, pP); if( !jP ){ - S3JniUnrefLocal(jX); rc = SQLITE_NOMEM; } } - assert(jP); - rc = (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback, - (jint)traceflag, jP, jX); - S3JniIfThrew{ - rc = s3jni_db_exception(env, ps, SQLITE_ERROR, - "sqlite3_trace_v2() callback threw."); + if( 0==rc ){ + assert(jP); + rc = (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback, + (jint)traceflag, jP, jX); + S3JniIfThrew{ + rc = s3jni_db_exception(ps, SQLITE_ERROR, + "sqlite3_trace_v2() callback threw."); + } } } S3JniUnrefLocal(jPUnref); @@ -4135,8 +4170,8 @@ S3JniApi(sqlite3_trace_v2(),jint,1trace_1v2)( "Cannot not find matching call() on " "TracerCallback object."); }else{ - S3JniMutex_S3JniDb_enter; hook.jObj = S3JniRefGlobal(jTracer); + S3JniMutex_S3JniDb_enter; rc = sqlite3_trace_v2(ps->pDb, (unsigned)traceMask, s3jni_trace_impl, ps); if( 0==rc ){ S3JniHook_unref(&ps->hooks.trace, 0); @@ -4349,7 +4384,7 @@ static void Fts5JniAux_free(Fts5JniAux * const s){ S3JniDeclLocal_env; if( env ){ /*MARKER(("FTS5 aux function cleanup: %s\n", s->zFuncName));*/ - s3jni_call_xDestroy(env, s->jObj); + s3jni_call_xDestroy(s->jObj); S3JniUnrefGlobal(s->jObj); S3JniUnrefGlobal(s->jUserData); } @@ -4579,7 +4614,7 @@ static void S3JniFts5AuxData_xDestroy(void *x){ S3JniFts5AuxData * const p = x; if( p->jObj ){ S3JniDeclLocal_env; - s3jni_call_xDestroy(env, p->jObj); + s3jni_call_xDestroy(p->jObj); S3JniUnrefGlobal(p->jObj); } sqlite3_free(x); @@ -4792,7 +4827,7 @@ JniDeclFtsXA(int,xSetAuxdata)(JniArgsEnvObj,jobject jCtx, jobject jAux){ if( jAux ){ /* Emulate how xSetAuxdata() behaves when it cannot alloc ** its auxdata wrapper. */ - s3jni_call_xDestroy(env, jAux); + s3jni_call_xDestroy(jAux); } return SQLITE_NOMEM; } diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 3d60362f61..e418de6096 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -1498,7 +1498,7 @@ public class Tester1 implements Runnable { if( sqlLog ){ if( sqlite3_compileoption_used("ENABLE_SQLLOG") ){ - int rc = sqlite3_config( new ConfigSqllogCallback() { + final ConfigSqllogCallback log = new ConfigSqllogCallback() { @Override public void call(sqlite3 db, String msg, int op){ switch(op){ case 0: outln("Opening db: ",db); break; @@ -1506,7 +1506,12 @@ public class Tester1 implements Runnable { case 2: outln("Closing db: ",db); break; } } - }); + }; + int rc = sqlite3_config( log ); + affirm( 0==rc ); + rc = sqlite3_config( null ); + affirm( 0==rc ); + rc = sqlite3_config( log ); affirm( 0==rc ); }else{ outln("WARNING: -sqllog is not active because library was built ", diff --git a/ext/jni/src/org/sqlite/jni/XDestroyCallback.java b/ext/jni/src/org/sqlite/jni/XDestroyCallback.java index d1e4b1ee3c..4b547e6bc9 100644 --- a/ext/jni/src/org/sqlite/jni/XDestroyCallback.java +++ b/ext/jni/src/org/sqlite/jni/XDestroyCallback.java @@ -24,6 +24,14 @@ public interface XDestroyCallback { Must perform any cleanup required by this object. Must not throw. Must not call back into the sqlite3 API, else it might invoke a deadlock. + + WARNING: as a rule, it is never safe to register individual + instances with this interface multiple times in the + library. e.g., do not register the same CollationCallback with + multiple arities or names using sqlite3_create_collation(). If + this rule is violated, the library will eventually try to free + each individual reference, leading to memory corruption or a + crash via duplicate free(). */ public void xDestroy(); } diff --git a/manifest b/manifest index 7c36b33c28..ae37df779d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Updates\sto\stestrunner.tcl\sso\sthat\sit\sruns\sfuzztest\susing\smultiple\sjobs. -D 2023-08-28T20:14:19.097 +C Correct\sJNI\smapping\sof\scollations\sto\sbe\s1-db-to-many-collations. +D 2023-08-28T20:21:56.713 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -237,7 +237,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile 374873bf6d2cd6ceafb458e28b59140dbb074f01f7adddf7e15a3ee3daf44551 F ext/jni/README.md 1332b1fa27918bd5d9ca2d0d4f3ac3a6ab86b9e3699dc5bfe32904a027f3d2a9 F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa -F ext/jni/src/c/sqlite3-jni.c c92a764a728a3fcd34882defb7517c47af22c9f7fe184113015cdab9fe401c21 +F ext/jni/src/c/sqlite3-jni.c 3c16035f6d604c17e50a6477b7309ca8885c54cd8a9c85e8c36d2f8b918d71e7 F ext/jni/src/c/sqlite3-jni.h 12e1a5ef5ee1795dc22577c285b4518dfd8aa4af45757f6cb81a555d967bf201 F ext/jni/src/org/sqlite/jni/AbstractCollationCallback.java 95e88ba04f4aac51ffec65693e878e234088b2f21b387f4e4285c8b72b33e436 F ext/jni/src/org/sqlite/jni/AggregateFunction.java 7312486bc65fecdb91753c0a4515799194e031f45edbe16a6373cea18f404dc4 @@ -264,13 +264,13 @@ F ext/jni/src/org/sqlite/jni/SQLFunction.java 544a875d33fd160467d82e2397ac33157b F ext/jni/src/org/sqlite/jni/SQLite3CallbackProxy.java c2748ab52856075b053a55b317988d95dc7fb4d3d42520f8c33573effe1cd185 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 440d64e8c4cff53bd3c0cc676381212489198302d7f1aaa535712c2d7163cc69 F ext/jni/src/org/sqlite/jni/ScalarFunction.java 6d387bb499fbe3bc13c53315335233dbf6a0c711e8fa7c521683219b041c614c -F ext/jni/src/org/sqlite/jni/Tester1.java a9558165dbb085494705525ef28e41d337d8348bf44259ce1f77cad72547bfdf +F ext/jni/src/org/sqlite/jni/Tester1.java 21d78aa59bfc5ce5ff242d4bb6f6d2255d162fba8be5859ab87c9201d61433f0 F ext/jni/src/org/sqlite/jni/TesterFts5.java 6f135c60e24c89e8eecb9fe61dde0f3bb2906de668ca6c9186bcf34bdaf94629 F ext/jni/src/org/sqlite/jni/TraceV2Callback.java 641926b05a772c2c05c842a81aa839053ba4a13b78ef04b402f5705d060c6246 F ext/jni/src/org/sqlite/jni/UpdateHookCallback.java be2bc96ff4f56b3c1fd18ae7dba9b207b25b6c123b8a5fd2f7aaf3cc208d8b7d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee F ext/jni/src/org/sqlite/jni/WindowFunction.java 488980f4dbb6bdd7067d6cb9c43e4075475e51c54d9b74a5834422654b126246 -F ext/jni/src/org/sqlite/jni/XDestroyCallback.java 95fb66353e62e4aca8d6ab60e8f14f9235bd10373c34db0a64f5f13f016f0471 +F ext/jni/src/org/sqlite/jni/XDestroyCallback.java 50c5ca124ef6c6b735a7e136e7a23a557be367e61b56d4aab5777a614ab46cc2 F ext/jni/src/org/sqlite/jni/annotation/Canonical.java e55b82c8259b617ff754ac493fd8b79602631d659b87a858b987540e4c4fdf56 F ext/jni/src/org/sqlite/jni/annotation/NotNull.java d48ebd7ae6bbb78bd47d54431c85e1521c89b1d3864a2b6eafd9c0e1b2341457 F ext/jni/src/org/sqlite/jni/annotation/Nullable.java 6f962a98c9a5c6e9d21c50ae8716b16bdfdc934a191608cbb7e12ea588ddb6af @@ -2107,9 +2107,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 ecf07a0144dc6402b1e0924b1775d99dc465b27aa86a2718cac60a9b4c974312 6463192369ff22eb15d4e34590ef55cd8a6f2501227835664816277806bf961b -R 31bc738c1437151db584e22f2c509958 -T +closed 6463192369ff22eb15d4e34590ef55cd8a6f2501227835664816277806bf961b -U dan -Z 97aabfccc20038ea125aae98b8297d02 +P ceeabe9f8b31a30c65147fd270b92d43c7842247548cee9de165113991f6c2cf +R 68323f68f37601aa48cb5f2c61fd0c0a +U stephan +Z 6560ea32a29f03de2be1f2fe7d94526d # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index d7d1100a9c..3ef7700236 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -ceeabe9f8b31a30c65147fd270b92d43c7842247548cee9de165113991f6c2cf \ No newline at end of file +b927b0f5a68684b0a9799396d153bf1e2306351e8039c2bacb3d5b2056a0634f \ No newline at end of file