0
0
mirror of https://github.com/signalapp/libsignal.git synced 2024-09-20 03:52:17 +02:00

Java: Teach gen_java_decl about Futures for type-safety.

This commit is contained in:
Jordan Rose 2023-09-21 10:40:41 -04:00
parent 2c295f68c9
commit a15fffd058
6 changed files with 58 additions and 11 deletions

View File

@ -50,13 +50,13 @@ public class FutureTest {
@Test
public void testSuccessFromRust() throws Exception {
Future<Integer> future = (Future<Integer>)Native.Future_success();
Future<Integer> future = Native.Future_success();
assertEquals(42, (int) future.get());
}
@Test
public void testFailureFromRust() throws Exception {
Future<Integer> future = (Future<Integer>)Native.Future_failure();
Future<Integer> future = Native.Future_failure();
ExecutionException e = assertThrows(ExecutionException.class, () -> future.get());
assertTrue(e.getCause() instanceof IllegalArgumentException);
}

View File

@ -23,6 +23,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.concurrent.Future;
import java.util.UUID;
import java.util.Map;
@ -199,8 +200,8 @@ public final class Native {
public static native void ExpiringProfileKeyCredential_CheckValidContents(byte[] buffer);
public static native long ExpiringProfileKeyCredential_GetExpirationTime(byte[] credential);
public static native Object Future_failure();
public static native Object Future_success();
public static native Future<Integer> Future_failure();
public static native Future<Integer> Future_success();
public static native void GenericServerPublicParams_CheckValidContents(byte[] paramsBytes);

View File

@ -23,6 +23,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.concurrent.Future;
import java.util.UUID;
import java.util.Map;

View File

@ -40,7 +40,8 @@ ignore_this_warning = re.compile(
r"WARN: Can't find .*\. This usually means that this type was incompatible or not found\.|"
r"WARN: Missing `\[defines\]` entry for `feature = \".*\"` in cbindgen config\.|"
r"WARN: Skip libsignal-bridge::.+ - \(not `(?:pub|no_mangle)`\)\.|"
r"WARN: Couldn't find path for Array\(Path\(GenericPath \{ .+ \}\), Name\(\"LEN\"\)\), skipping associated constants"
r"WARN: Couldn't find path for Array\(Path\(GenericPath \{ .+ \}\), Name\(\"LEN\"\)\), skipping associated constants|"
r"WARN: Cannot find a mangling for generic path GenericPath { path: Path { name: \"JavaFuture\" }.+"
")")
unknown_warning = False
@ -58,7 +59,23 @@ for l in stderr.split('\n'):
if unknown_warning:
sys.exit(1)
java_decl = re.compile(r'([a-zA-Z]+) Java_org_signal_libsignal_internal_Native_([A-Z][a-zA-Z0-9]+)_1([A-Za-z0-9]+)\(JNIEnv .?env, JClass class_(, .*)?\);')
java_decl = re.compile(r'([a-zA-Z]+(?:<.+>)?) Java_org_signal_libsignal_internal_Native_([A-Z][a-zA-Z0-9]+)_1([A-Za-z0-9]+)\(JNIEnv .?env, JClass class_(, .*)?\);')
def box_primitives(typ):
type_map = {
"void": "Void",
"boolean": "Boolean",
"char": "Character",
"byte": "Byte",
"short": "Short",
"int": "Integer",
"long": "Long",
"float": "Float",
"double": "Double",
}
return type_map.get(typ, typ)
def translate_to_java(typ):
@ -78,7 +95,11 @@ def translate_to_java(typ):
if typ in type_map:
return type_map[typ]
# Assume anything prefixed with "Java" refers to an object
if typ.startswith('JavaFuture<'):
assert typ.endswith('>')
return 'Future<' + box_primitives(translate_to_java(typ[11:-1])) + '>'
# Assume anything else prefixed with "Java" refers to an object
if typ.startswith('Java'):
return typ[4:]

View File

@ -7,6 +7,7 @@
#![deny(clippy::unwrap_used)]
use jni::objects::{JByteArray, JClass, JLongArray, JObject};
use jni::sys::jint;
use jni::JNIEnv;
use std::convert::TryFrom;
@ -53,7 +54,7 @@ pub unsafe extern "C" fn Java_org_signal_libsignal_internal_Native_keepAlive(
pub unsafe extern "C" fn Java_org_signal_libsignal_internal_Native_Future_1success<'local>(
mut env: JNIEnv<'local>,
_class: JClass,
) -> JObject<'local> {
) -> JavaFuture<'local, jint> {
run_ffi_safe(&mut env, |env| {
let future = new_object(
env,
@ -62,7 +63,7 @@ pub unsafe extern "C" fn Java_org_signal_libsignal_internal_Native_Future_1succe
)?;
let completer = FutureCompleter::new(env, &future)?;
std::thread::spawn(move || completer.complete(42));
Ok(future)
Ok(future.into())
})
}
@ -70,7 +71,7 @@ pub unsafe extern "C" fn Java_org_signal_libsignal_internal_Native_Future_1succe
pub unsafe extern "C" fn Java_org_signal_libsignal_internal_Native_Future_1failure<'local>(
mut env: JNIEnv<'local>,
_class: JClass,
) -> JObject<'local> {
) -> JavaFuture<'local, jint> {
run_ffi_safe(&mut env, |env| {
let future = new_object(
env,
@ -83,6 +84,6 @@ pub unsafe extern "C" fn Java_org_signal_libsignal_internal_Native_Future_1failu
"failure".to_string(),
)))
});
Ok(future)
Ok(future.into())
})
}

View File

@ -15,6 +15,7 @@ use signal_pin::Error as PinError;
use std::convert::{TryFrom, TryInto};
use std::error::Error;
use std::fmt::Display;
use std::marker::PhantomData;
pub(crate) use jni::objects::{
AutoElements, JByteArray, JClass, JLongArray, JObject, JString, ReleaseMode,
@ -49,6 +50,28 @@ pub type JavaUUID<'a> = JObject<'a>;
pub type JavaCiphertextMessage<'a> = JObject<'a>;
pub type JavaMap<'a> = JObject<'a>;
#[derive(Default)]
#[repr(transparent)] // This ensures that JavaFuture has the same representation as JObject.
pub struct JavaFuture<'a, T> {
future_object: JObject<'a>,
result: PhantomData<fn(T)>,
}
impl<'a, T> From<JObject<'a>> for JavaFuture<'a, T> {
fn from(future_object: JObject<'a>) -> Self {
Self {
future_object,
result: PhantomData,
}
}
}
impl<'a, T> From<JavaFuture<'a, T>> for JObject<'a> {
fn from(value: JavaFuture<'a, T>) -> Self {
value.future_object
}
}
fn convert_to_exception<'a, F>(env: &mut JNIEnv<'a>, error: SignalJniError, consume: F)
where
F: FnOnce(&mut JNIEnv<'a>, SignalJniResult<JThrowable<'a>>, &dyn Display),