From: stephan Date: Sun, 6 Aug 2023 10:49:47 +0000 (+0000) Subject: Add a way to convert from standard UTF-8 to a Java string (JNI lacks this capability). X-Git-Tag: version-3.43.0~47^2~91 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=57645b67fa790e1ec9293fa99166be8c90904722;p=thirdparty%2Fsqlite.git Add a way to convert from standard UTF-8 to a Java string (JNI lacks this capability). FossilOrigin-Name: 07dd082c9e371829a18aeb574f842891e545e1fc125760238ede7e7e2b6a4262 --- diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 646ecab06c..377c0e3a9c 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -343,12 +343,14 @@ struct JNIEnvCacheLine { //! The various refs to global classes might be cacheable a single // time globally. Information online seems inconsistent on that // point. - jclass globalClassObj /* global ref to java.lang.Object */; - jclass globalClassLong /* global ref to java.lang.Long */; - jclass globalClassString /* global ref to java.lang.String */; - jobject globalClassCharsetUtf8 /* global ref to StandardCharset.UTF_8 */; - jmethodID ctorLong1 /* the Long(long) constructor */; - jmethodID ctorStringBA /* the String(byte[],Charset) constructor */; + 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 */; + jmethodID ctorLong1 /* the Long(long) constructor */; + jmethodID ctorStringBA /* the String(byte[],Charset) constructor */; + } g; jobject currentStmt /* Current Java sqlite3_stmt object being prepared, stepped, reset, or finalized. Needed for tracing, the @@ -536,6 +538,41 @@ static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){ } } +/** + Uses the java.lang.String(byte[],Charset) constructor to create a + new String from UTF-8 string z. n is the number of bytes to + copy. If n<0 then sqlite3Strlen30() is used to calculate it. + + Returns NULL if z is NULL or on OOM, else returns a new jstring + owned by the caller. + + Sidebar: this is a painfully inefficient way to convert from + standard UTF-8 to a Java string, but JNI offers only algorithms for + working with MUTF-8, not UTF-8. +*/ +static jstring s3jni_string_from_utf8(JNIEnvCacheLine * const jc, + const char * const z, int n){ + jstring rv = NULL; + JNIEnv * const env = jc->env; + if( 0==n || (z && !z[0]) ){ + /* Fast-track the empty-string case. We could hypothetically do + this for any strings where n<4 and z is NUL-terminated and none + of z[0..3] are NUL bytes. */ + rv = (*env)->NewStringUTF(env, ""); + }else if( z ){ + jbyteArray jba; + if( n<0 ) n = sqlite3Strlen30(z); + jba = (*env)->NewByteArray(env, (jsize)n); + if( jba ){ + (*env)->SetByteArrayRegion(env, jba, 0, n, (jbyte const *)z); + rv = (*env)->NewObject(env, jc->g.cString, jc->g.ctorStringBA, + jba, jc->g.oCharsetUtf8); + UNREF_L(jba); + } + } + return rv; +} + /** Fetches the S3Global.envCache row for the given env, allocing a row if needed. When a row is allocated, its state is initialized @@ -570,19 +607,21 @@ static JNIEnvCacheLine * S3Global_JNIEnvCache_cache(JNIEnv * const env){ if(row->pNext) row->pNext->pPrev = row; S3Global.envCache.aHead = row; row->env = env; - row->globalClassObj = REF_G((*env)->FindClass(env,"java/lang/Object")); + + /* Grab references to various global classes and objects... */ + row->g.cObj = REF_G((*env)->FindClass(env,"java/lang/Object")); EXCEPTION_IS_FATAL("Error getting reference to Object class."); - row->globalClassLong = REF_G((*env)->FindClass(env,"java/lang/Long")); + row->g.cLong = REF_G((*env)->FindClass(env,"java/lang/Long")); EXCEPTION_IS_FATAL("Error getting reference to Long class."); - row->ctorLong1 = (*env)->GetMethodID(env, row->globalClassLong, - "", "(J)V"); + row->g.ctorLong1 = (*env)->GetMethodID(env, row->g.cLong, + "", "(J)V"); EXCEPTION_IS_FATAL("Error getting reference to Long constructor."); - row->globalClassString = REF_G((*env)->FindClass(env,"java/lang/String")); + row->g.cString = REF_G((*env)->FindClass(env,"java/lang/String")); EXCEPTION_IS_FATAL("Error getting reference to String class."); - row->ctorStringBA = - (*env)->GetMethodID(env, row->globalClassString, + row->g.ctorStringBA = + (*env)->GetMethodID(env, row->g.cString, "", "([BLjava/nio/charset/Charset;)V"); EXCEPTION_IS_FATAL("Error getting reference to String(byte[],Charset) ctor."); @@ -594,7 +633,7 @@ static JNIEnvCacheLine * S3Global_JNIEnvCache_cache(JNIEnv * const env){ fUtf8 = (*env)->GetStaticFieldID(env, klazzSC, "UTF_8", "Ljava/nio/charset/Charset;"); EXCEPTION_IS_FATAL("Error getting StndardCharsets.UTF_8 field."); - row->globalClassCharsetUtf8 = + row->g.oCharsetUtf8 = REF_G((*env)->GetStaticObjectField(env, klazzSC, fUtf8)); EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets.UTF_8."); } @@ -667,10 +706,10 @@ static void JNIEnvCacheLine_clear(JNIEnvCacheLine * const p){ JNIEnv * const env = p->env; if(env){ int i; - UNREF_G(p->globalClassObj); - UNREF_G(p->globalClassLong); - UNREF_G(p->globalClassString); - UNREF_G(p->globalClassCharsetUtf8); + UNREF_G(p->g.cObj); + UNREF_G(p->g.cLong); + UNREF_G(p->g.cString); + UNREF_G(p->g.oCharsetUtf8); UNREF_G(p->currentStmt); #ifdef SQLITE_ENABLE_FTS5 UNREF_G(p->jFtsExt); @@ -1377,7 +1416,7 @@ static int udf_args(JNIEnv *env, *jArgv = 0; if(!jcx) goto error_oom; ja = (*env)->NewObjectArray(env, argc, - S3Global_JNIEnvCache_cache(env)->globalClassObj, + S3Global_JNIEnvCache_cache(env)->g.cObj, NULL); if(!ja) goto error_oom; for(i = 0; i < argc; ++i){ @@ -2553,16 +2592,14 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ int rc; switch(traceflag){ case SQLITE_TRACE_STMT: - /* This is not _quite_ right: we're converting to MUTF-8. It - should(?) suffice for purposes of tracing, though. */ - jX = (*env)->NewStringUTF(env, (const char *)pX); + jX = s3jni_string_from_utf8(jc, (const char *)pX, -1); if(!jX) return SQLITE_NOMEM; jP = jc->currentStmt; break; case SQLITE_TRACE_PROFILE: - jX = (*env)->NewObject(env, jc->globalClassLong, jc->ctorLong1, + jX = (*env)->NewObject(env, jc->g.cLong, jc->g.ctorLong1, (jlong)*((sqlite3_int64*)pX)); - // hmm. It really is zero. + // hmm. ^^^ (*pX) really is zero. // MARKER(("profile time = %llu\n", *((sqlite3_int64*)pX))); jP = jc->currentStmt; if(!jP){ @@ -3547,7 +3584,7 @@ Java_org_sqlite_jni_SQLite3Jni_init(JNIEnv * const env, jclass self, jclass klaz } assert( 1 == S3Global.metrics.envCacheMisses ); assert( env == S3Global.envCache.aHead->env ); - assert( 0 != S3Global.envCache.aHead->globalClassObj ); + assert( 0 != S3Global.envCache.aHead->g.cObj ); for( pConfFlag = &aLimits[0]; pConfFlag->zName; ++pConfFlag ){ char const * zSig = (JTYPE_BOOL == pConfFlag->jtype) ? "Z" : "I"; diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 334484166c..2d1dcd4be8 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -683,6 +683,10 @@ public class Tester1 { private static void testTrace(){ final sqlite3 db = createNewDb(); final ValueHolder counter = new ValueHolder<>(0); + /* Ensure that characters outside of the UTF BMP survive the trip + from Java to sqlite3 and back to Java. (At no small efficiency + penalty.) */ + final String nonBmpChar = "😃"; sqlite3_trace_v2( db, SQLITE_TRACE_STMT | SQLITE_TRACE_PROFILE | SQLITE_TRACE_ROW | SQLITE_TRACE_CLOSE, @@ -694,6 +698,7 @@ public class Tester1 { affirm(pNative instanceof sqlite3_stmt); affirm(x instanceof String); //outln("TRACE_STMT sql = "+x); + affirm( ((String)x).indexOf(nonBmpChar) > 0 ); break; case SQLITE_TRACE_PROFILE: affirm(pNative instanceof sqlite3_stmt); @@ -716,7 +721,8 @@ public class Tester1 { return 0; } }); - execSql(db, "SELECT coalesce(null,null,null,'hi'); SELECT 'world'"); + execSql(db, "SELECT coalesce(null,null,'"+nonBmpChar+"'); "+ + "SELECT 'w"+nonBmpChar+"orld'"); affirm( 6 == counter.value ); sqlite3_close_v2(db); affirm( 7 == counter.value ); diff --git a/manifest b/manifest index bc94970f53..5cc0301b26 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\ssqlite3_db_filename()\sand\s(closely\srelated)\s(A)\sadd\smany\smore\sdocs\sabout\sthe\sUTF-8/MUTF-8\sdiscrepancy\s(B)\sstart\sadding\sinternals\sto\senable\sus\sto\sperform\sthe\sstandard-UTF-8-to-Java\sconversion\sfrom\sC. -D 2023-08-06T10:14:53.465 +C Add\sa\sway\sto\sconvert\sfrom\sstandard\sUTF-8\sto\sa\sJava\sstring\s(JNI\slacks\sthis\scapability). +D 2023-08-06T10:49:47.843 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile bb4cd99bd8da534215cb6d278f05a626283eb5d2e8aebdb4d35e548637d35a9a F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf -F ext/jni/src/c/sqlite3-jni.c 88c18f2f1dd8064ca3d264bcda0df950b57bc0f5b9d8bfeb43bdd3f5be723ab8 +F ext/jni/src/c/sqlite3-jni.c 0d487a655b1fe60906d1df71f2b99b59c7644015be92ccd531ceefee596dec97 F ext/jni/src/c/sqlite3-jni.h 03c61c4f84c028169633392d7eb06caa6000e8bf3c0a3f7ac44e645dedbbfb9a F ext/jni/src/org/sqlite/jni/Authorizer.java 8dde03bbe50896d2f426240a4af4dcb6d98b655af84fe6ed86e637f5d5ac1fc8 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -251,7 +251,7 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d3 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 7268b37a32657798d01e116d639585cb5ed9f83b0760eb0416882bef79ffcb78 -F ext/jni/src/org/sqlite/jni/Tester1.java f6fcd218eb9d459866578d80b4223c15a2336af7fd52454c0be57bc855b1a892 +F ext/jni/src/org/sqlite/jni/Tester1.java ecc72fcba231f5dfd787fd5d62fac685e8cfc349f74d11245d19325643517bfd F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2082,8 +2082,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 e0fa03135942cd2fe732a74510d380ba78ab230c452168e638f32b4aee04b3f7 -R 67866ade0f482ffd79944a87403dd96d +P 586720fa714ac74491cd85d0c6645242e55e5989ad312ef6e15e0b0acc6906ff +R 8ee2983dae779c21de40b749b6e6f931 U stephan -Z b7b857a7fcd9ef420d175786c42a22e5 +Z c665b4e52180bf9728f0f7861f45043f # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 6a5c31e951..ba35528220 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -586720fa714ac74491cd85d0c6645242e55e5989ad312ef6e15e0b0acc6906ff \ No newline at end of file +07dd082c9e371829a18aeb574f842891e545e1fc125760238ede7e7e2b6a4262 \ No newline at end of file