also looking at this
fix(android): text going to the JVM is modified UTF-8, and was not being
#168
12 files
+326
-136
| @@ -359,14 +359,10 @@ fn nativeGetDeviceInfo(env: jni.JNIEnv, _: jni.jobject, activity: jni.jobject) c | ||
| 359 | 359 | |
| 360 | 360 | const json = device.render(allocator, info) catch return null; |
| 361 | 361 | |
| 362 | // `NewStringUTF` reads to a NUL, and `render` produces a plain slice. | |
| 363 | // Copied rather than terminated in place because the reply is JSON built | |
| 364 | // for the page, and giving `render` a sentinel to satisfy would put a | |
| 365 | // JNI detail into the half of this action that has nothing to do with Java. | |
| 366 | const owned = allocator.allocSentinel(u8, json.len, 0) catch return null; | |
| 367 | @memcpy(owned, json); | |
| 368 | ||
| 369 | return j.newStringUtf(owned.ptr) catch null; | |
| 362 | // Re-encoded rather than handed over: this JSON carries device text — a | |
| 363 | // model name, a carrier — and `NewStringUTF` reads modified UTF-8, where a | |
| 364 | // NUL is two bytes and an astral character is a surrogate pair. | |
| 365 | return j.newStringUtf8(allocator, json) catch null; | |
| 370 | 366 | } |
| 371 | 367 | |
| 372 | 368 | /// `nativeGetMemoryUsage()` — the JVM heap, as JSON. |
| @@ -383,10 +379,7 @@ fn nativeGetMemoryUsage(env: jni.JNIEnv, _: jni.jobject) callconv(.c) jni.jstrin | ||
| 383 | 379 | }; |
| 384 | 380 | |
| 385 | 381 | const json = system.renderMemory(allocator, usage) catch return null; |
| 386 | const owned = allocator.allocSentinel(u8, json.len, 0) catch return null; | |
| 387 | @memcpy(owned, json); | |
| 388 | ||
| 389 | return j.newStringUtf(owned.ptr) catch null; | |
| 382 | return j.newStringUtf8(allocator, json) catch null; | |
| 390 | 383 | } |
| 391 | 384 | |
| 392 | 385 | /// `nativeLog(message)` — returns whether Zig wrote the line. |
| @@ -402,10 +395,8 @@ fn nativeLog(env: jni.JNIEnv, _: jni.jobject, message: jni.jstring) callconv(.c) | ||
| 402 | 395 | const allocator = arena.allocator(); |
| 403 | 396 | |
| 404 | 397 | const text = j.stringToUtf8(allocator, message) catch return jni.JNI_FALSE; |
| 405 | const owned = allocator.allocSentinel(u8, text.len, 0) catch return jni.JNI_FALSE; | |
| 406 | @memcpy(owned, text); | |
| 407 | 398 | |
| 408 | system.writeLog(j, owned.ptr) catch |err| { | |
| 399 | system.writeLog(j, allocator, text) catch |err| { | |
| 409 | 400 | std.log.warn("craft: log fell through to the shim ({s})", .{@errorName(err)}); |
| 410 | 401 | return jni.JNI_FALSE; |
| 411 | 402 | }; |
| @@ -429,9 +420,7 @@ fn nativeClipboardRead(env: jni.JNIEnv, _: jni.jobject, activity: jni.jobject) c | ||
| 429 | 420 | return null; |
| 430 | 421 | }; |
| 431 | 422 | |
| 432 | const owned = allocator.allocSentinel(u8, text.len, 0) catch return null; | |
| 433 | @memcpy(owned, text); | |
| 434 | return j.newStringUtf(owned.ptr) catch null; | |
| 423 | return j.newStringUtf8(allocator, text) catch null; | |
| 435 | 424 | } |
| 436 | 425 | |
| 437 | 426 | /// `nativeClipboardWrite(activity, text)`. |
| @@ -448,25 +437,21 @@ fn nativeClipboardWrite( | ||
| 448 | 437 | const allocator = arena.allocator(); |
| 449 | 438 | |
| 450 | 439 | const value = j.stringToUtf8(allocator, text) catch return jni.JNI_FALSE; |
| 451 | const owned = allocator.allocSentinel(u8, value.len, 0) catch return jni.JNI_FALSE; | |
| 452 | @memcpy(owned, value); | |
| 453 | 440 | |
| 454 | clipboard.write(j, activity, owned.ptr) catch |err| { | |
| 441 | clipboard.write(j, allocator, activity, value) catch |err| { | |
| 455 | 442 | std.log.warn("craft: clipboardWrite fell through to the shim ({s})", .{@errorName(err)}); |
| 456 | 443 | return jni.JNI_FALSE; |
| 457 | 444 | }; |
| 458 | 445 | return jni.JNI_TRUE; |
| 459 | 446 | } |
| 460 | 447 | |
| 461 | /// Copy a `jstring` into NUL-terminated arena memory, or null. | |
| 448 | /// A `jstring` as arena-owned UTF-8, or null. | |
| 462 | 449 | /// |
| 463 | 450 | /// Every native below needs this and none of them can share the result, since |
| 464 | /// each arena dies with its call. | |
| 465 | fn ownedUtf8(j: Jni, allocator: std.mem.Allocator, str: jni.jstring) ?[:0]u8 { | |
| 466 | const text = j.stringToUtf8(allocator, str) catch return null; | |
| 467 | const owned = allocator.allocSentinel(u8, text.len, 0) catch return null; | |
| 468 | @memcpy(owned, text); | |
| 469 | return owned; | |
| 451 | /// each arena dies with its call. A plain slice: the JNI side re-encodes on | |
| 452 | /// the way back out, so nothing here has to carry a terminator. | |
| 453 | fn ownedUtf8(j: Jni, allocator: std.mem.Allocator, str: jni.jstring) ?[]u8 { | |
| 454 | return j.stringToUtf8(allocator, str) catch null; | |
| 470 | 455 | } |
| 471 | 456 | |
| 472 | 457 | fn nativeOpenUrl( |
| @@ -479,8 +464,10 @@ fn nativeOpenUrl( | ||
| 479 | 464 | var arena = std.heap.ArenaAllocator.init(backing); |
| 480 | 465 | defer arena.deinit(); |
| 481 | 466 | |
| 482 | const value = ownedUtf8(j, arena.allocator(), url) orelse return jni.JNI_FALSE; | |
| 483 | intents.openUrl(j, activity, value.ptr) catch |err| { | |
| 467 | const allocator = arena.allocator(); | |
| 468 | ||
| 469 | const value = ownedUtf8(j, allocator, url) orelse return jni.JNI_FALSE; | |
| 470 | intents.openUrl(j, allocator, activity, value) catch |err| { | |
| 484 | 471 | // Not a warning. `openURL` answering false is the documented outcome |
| 485 | 472 | // when nothing on the device handles the scheme, and the Kotlin |
| 486 | 473 | // catches exactly this — logging it at warn would put a line in |
| @@ -506,7 +493,7 @@ fn nativeShare( | ||
| 506 | 493 | const body = ownedUtf8(j, allocator, text) orelse return jni.JNI_FALSE; |
| 507 | 494 | const subject = ownedUtf8(j, allocator, title) orelse return jni.JNI_FALSE; |
| 508 | 495 | |
| 509 | intents.share(j, activity, body.ptr, subject.ptr) catch |err| { | |
| 496 | intents.share(j, allocator, activity, body, subject) catch |err| { | |
| 510 | 497 | std.log.warn("craft: share fell through to the shim ({s})", .{@errorName(err)}); |
| 511 | 498 | return jni.JNI_FALSE; |
| 512 | 499 | }; |
| @@ -525,9 +512,7 @@ fn nativeGetNetworkStatus(env: jni.JNIEnv, _: jni.jobject, activity: jni.jobject | ||
| 525 | 512 | }; |
| 526 | 513 | |
| 527 | 514 | const json = network.render(allocator, status) catch return null; |
| 528 | const owned = allocator.allocSentinel(u8, json.len, 0) catch return null; | |
| 529 | @memcpy(owned, json); | |
| 530 | return j.newStringUtf(owned.ptr) catch null; | |
| 515 | return j.newStringUtf8(allocator, json) catch null; | |
| 531 | 516 | } |
| 532 | 517 | |
| 533 | 518 | fn nativeSecureSet( |
| @@ -545,7 +530,7 @@ fn nativeSecureSet( | ||
| 545 | 530 | const k = ownedUtf8(j, allocator, key) orelse return jni.JNI_FALSE; |
| 546 | 531 | const v = ownedUtf8(j, allocator, value) orelse return jni.JNI_FALSE; |
| 547 | 532 | |
| 548 | securestore.set(j, prefs, k.ptr, v.ptr) catch |err| { | |
| 533 | securestore.set(j, allocator, prefs, k, v) catch |err| { | |
| 549 | 534 | std.log.warn("craft: secureSet fell through to the shim ({s})", .{@errorName(err)}); |
| 550 | 535 | return jni.JNI_FALSE; |
| 551 | 536 | }; |
| @@ -569,15 +554,13 @@ fn nativeSecureGet( | ||
| 569 | 554 | const allocator = arena.allocator(); |
| 570 | 555 | |
| 571 | 556 | const k = ownedUtf8(j, allocator, key) orelse return null; |
| 572 | const value = securestore.get(allocator, j, prefs, k.ptr) catch |err| { | |
| 557 | const value = securestore.get(allocator, j, prefs, k) catch |err| { | |
| 573 | 558 | std.log.warn("craft: secureGet fell through to the shim ({s})", .{@errorName(err)}); |
| 574 | 559 | return null; |
| 575 | 560 | }; |
| 576 | 561 | |
| 577 | 562 | const json = securestore.renderRead(allocator, value) catch return null; |
| 578 | const owned = allocator.allocSentinel(u8, json.len, 0) catch return null; | |
| 579 | @memcpy(owned, json); | |
| 580 | return j.newStringUtf(owned.ptr) catch null; | |
| 563 | return j.newStringUtf8(allocator, json) catch null; | |
| 581 | 564 | } |
| 582 | 565 | |
| 583 | 566 | fn nativeSecureRemove( |
| @@ -590,8 +573,10 @@ fn nativeSecureRemove( | ||
| 590 | 573 | var arena = std.heap.ArenaAllocator.init(backing); |
| 591 | 574 | defer arena.deinit(); |
| 592 | 575 | |
| 593 | const k = ownedUtf8(j, arena.allocator(), key) orelse return jni.JNI_FALSE; | |
| 594 | securestore.remove(j, prefs, k.ptr) catch |err| { | |
| 576 | const allocator = arena.allocator(); | |
| 577 | ||
| 578 | const k = ownedUtf8(j, allocator, key) orelse return jni.JNI_FALSE; | |
| 579 | securestore.remove(j, allocator, prefs, k) catch |err| { | |
| 595 | 580 | std.log.warn("craft: secureRemove fell through to the shim ({s})", .{@errorName(err)}); |
| 596 | 581 | return jni.JNI_FALSE; |
| 597 | 582 | }; |