Verify JNI UTF-8 strings against a real JVM with checked JNI
This commit is contained in:
1 parent
86f0ac7625
commit
c0a4beeec4
3 files changed
+86
No files matched your search
@@ -0,0 +1,10 @@
|
||||
# Проверка строк JNI
|
||||
|
||||
`python3 tools/chatgui-android/tests/jni_utf8/run.py` из корня запускает настоящий
|
||||
код преобразования Android bridge в JVM с `-Xcheck:jni`. Нужны Linux, Python 3,
|
||||
C99-компилятор, OpenSSL headers и JDK с javac/libjvm. Телефон не требуется.
|
||||
|
||||
1024 преобразования UTF-16 → обычный UTF-8 → UTF-16 проверяют кириллицу, surrogate pair emoji,
|
||||
кавычки и перевод строки. Проверяются отказ выделения C-буфера с повтором, отклонение
|
||||
внутреннего NUL, пустая строка, NULL и сохранение уже ожидающего Java exception.
|
||||
JVM проверяет корректность вызовов JNI; ART и Android SDK этим тестом не запускаются.
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Linux: проверить обычный UTF-8 настоящего Android bridge в JVM с -Xcheck:jni."""
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
|
||||
def main():
|
||||
directory = Path(__file__).resolve().parent
|
||||
root = directory.parents[3]
|
||||
javac = shutil.which("javac")
|
||||
if not javac:
|
||||
raise SystemExit("JDK with javac/libjvm required")
|
||||
java_home = Path(javac).resolve().parents[1]
|
||||
server = java_home / "lib/server"
|
||||
includes = [directory.parent / "jni_log", java_home / "include", java_home / "include/linux",
|
||||
root / "src", root / "src/transport_layer", root / "src/routing_layer",
|
||||
root / "src/chat", root / "src/media_async", root / "lib"]
|
||||
with tempfile.TemporaryDirectory(prefix="utun-jni-utf8-") as output:
|
||||
binary = Path(output) / "test_jni_utf8"
|
||||
command = shlex.split(os.environ.get("CC", "cc")) + ["-std=c99", "-D_DEFAULT_SOURCE", "-D__ANDROID__", "-DUSE_OPENSSL",
|
||||
"-ffunction-sections", "-fdata-sections", "-Wl,--gc-sections", "-pthread", "-Wno-incompatible-pointer-types"]
|
||||
command += [f"-I{path}" for path in includes]
|
||||
command += [str(directory / "test_jni_utf8.c"), f"-L{server}", f"-Wl,-rpath,{server}", "-ljvm", "-o", str(binary)]
|
||||
subprocess.run(command, check=True, timeout=90)
|
||||
subprocess.run([str(binary)], check=True, timeout=30)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,43 @@
|
||||
/* Настоящая JVM с -Xcheck:jni: строки с emoji/кириллицей, NUL и сохранение чужого Java exception. */
|
||||
#include "../../jni_bridge/android_jni_bridge.c"
|
||||
|
||||
static int fail_allocation;
|
||||
void* u_malloc(size_t size) { return fail_allocation ? NULL : malloc(size); }
|
||||
void u_free(void* pointer) { free(pointer); }
|
||||
int __android_log_print(int priority, const char* tag, const char* format, ...) {
|
||||
(void)priority; (void)tag;
|
||||
va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); fputc('\n', stderr); return 0;
|
||||
}
|
||||
#define CHECK(value) do { if (!(value)) { fprintf(stderr, "JNI UTF-8 failed line=%d: %s\n", __LINE__, #value); return 1; } } while (0)
|
||||
|
||||
int main(void) {
|
||||
JavaVM* vm = NULL; JNIEnv* env = NULL;
|
||||
JavaVMOption option = {.optionString = "-Xcheck:jni"};
|
||||
JavaVMInitArgs args = {.version = JNI_VERSION_1_6, .nOptions = 1, .options = &option};
|
||||
CHECK(JNI_CreateJavaVM(&vm, (void**)&env, &args) == JNI_OK);
|
||||
const jchar characters[] = {0x0418, 0x043c, 0x044f, ' ', 0xd83d, 0xde00, '/', '"', '\n'};
|
||||
const char* expected = "Имя 😀/\"\n";
|
||||
jstring original = (*env)->NewString(env, characters, sizeof(characters) / sizeof(characters[0])); CHECK(original);
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
char* bytes = jni_utf8(env, original); CHECK(bytes && !strcmp(bytes, expected));
|
||||
jstring restored = jni_text(env, bytes); u_free(bytes); CHECK(restored);
|
||||
CHECK((*env)->GetStringLength(env, restored) == sizeof(characters) / sizeof(characters[0]));
|
||||
const jchar* actual = (*env)->GetStringChars(env, restored, NULL); CHECK(actual && !memcmp(actual, characters, sizeof(characters)));
|
||||
(*env)->ReleaseStringChars(env, restored, actual); (*env)->DeleteLocalRef(env, restored);
|
||||
}
|
||||
fail_allocation = 1; CHECK(jni_utf8(env, original) == NULL); fail_allocation = 0;
|
||||
char* retried = jni_utf8(env, original); CHECK(retried && !strcmp(retried, expected)); u_free(retried);
|
||||
const jchar nul[] = {'a', 0, 'b'};
|
||||
jstring embedded = (*env)->NewString(env, nul, 3); CHECK(embedded && !jni_utf8(env, embedded));
|
||||
CHECK(!jni_utf8(env, NULL) && !jni_text(env, NULL));
|
||||
jclass exception = (*env)->FindClass(env, "java/lang/IllegalStateException"); CHECK(exception);
|
||||
CHECK(!(*env)->ThrowNew(env, exception, "preserved"));
|
||||
CHECK(!jni_utf8(env, original) && !jni_text(env, expected) && (*env)->ExceptionCheck(env));
|
||||
(*env)->ExceptionClear(env);
|
||||
jstring empty = jni_text(env, ""); CHECK(empty && !(*env)->GetStringLength(env, empty));
|
||||
(*env)->DeleteLocalRef(env, empty); (*env)->DeleteLocalRef(env, exception);
|
||||
(*env)->DeleteLocalRef(env, embedded); (*env)->DeleteLocalRef(env, original);
|
||||
CHECK(!(*env)->ExceptionCheck(env) && (*vm)->DestroyJavaVM(vm) == JNI_OK);
|
||||
puts("JNI UTF-8: 1024 real JVM roundtrips, allocation failure/retry, embedded NUL and pending exception PASS");
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user