mentat/ffi/src/utils.rs

176 lines
6.6 KiB
Rust

// Copyright 2018 Mozilla
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
pub mod strings {
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use mentat::Keyword;
/// # Safety
///
/// This function TODO
pub unsafe fn c_char_to_string(cchar: *const c_char) -> &'static str {
assert!(!cchar.is_null());
let c_str = CStr::from_ptr(cchar);
c_str.to_str().unwrap_or("")
}
pub fn string_to_c_char<T>(r_string: T) -> *mut c_char
where
T: Into<String>,
{
CString::new(r_string.into()).unwrap().into_raw()
}
pub fn kw_from_string(keyword_string: &'static str) -> Keyword {
// TODO: validate. The input might not be a keyword!
let attr_name = keyword_string.trim_start_matches(':');
let parts: Vec<&str> = attr_name.split('/').collect();
Keyword::namespaced(parts[0], parts[1])
}
}
pub mod log {
#[cfg(all(target_os = "android", not(test)))]
use std::ffi::CString;
#[cfg(all(target_os = "android", not(test)))]
use android;
// TODO far from ideal. And, we might actually want to println in tests.
#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
pub fn d(_: &str) {}
#[cfg(all(target_os = "ios", not(test)))]
pub fn d(message: &str) {
eprintln!("{}", message);
}
#[cfg(all(target_os = "android", not(test)))]
pub fn d(message: &str) {
let message = CString::new(message).unwrap();
let message = message.as_ptr();
let tag = CString::new("Mentat").unwrap();
let tag = tag.as_ptr();
unsafe { android::__android_log_write(android::LogLevel::Debug as i32, tag, message) };
}
}
pub mod error {
use super::strings::string_to_c_char;
use std::boxed::Box;
use std::fmt::Display;
use std::os::raw::c_char;
use std::ptr;
/// Represents an error that occurred on the mentat side. Many mentat FFI functions take a
/// `*mut ExternError` as the last argument. This is an out parameter that indicates an
/// error that occurred during that function's execution (if any).
///
/// For functions that use this pattern, if the ExternError's message property is null, then no
/// error occurred. If the message is non-null then it contains a string description of the
/// error that occurred.
///
/// Important: This message is allocated on the heap and it is the consumer's responsibility to
/// free it using `destroy_mentat_string`!
///
/// While this pattern is not ergonomic in Rust, it offers two main benefits:
///
/// 1. It avoids defining a large number of `Result`-shaped types in the FFI consumer, as would
/// be required with something like an `struct ExternResult<T> { ok: *mut T, err:... }`
/// 2. It offers additional type safety over `struct ExternResult { ok: *mut c_void, err:... }`,
/// which helps avoid memory safety errors.
#[repr(C)]
#[derive(Debug)]
pub struct ExternError {
pub message: *mut c_char,
// TODO: Include an error code here.
}
impl Default for ExternError {
fn default() -> ExternError {
ExternError {
message: ptr::null_mut(),
}
}
}
/// Translate Result<T, E>, into something C can understand, when T is not `#[repr(C)]`
///
/// - If `result` is `Ok(v)`, moves `v` to the heap and returns a pointer to it, and sets
/// `error` to a state indicating that no error occurred (`message` is null).
/// - If `result` is `Err(e)`, returns a null pointer and stores a string representing the error
/// message (which was allocated on the heap and should eventually be freed) into
/// `error.message`
/// # Safety
/// Be afraid... TODO
pub unsafe fn translate_result<T, E>(result: Result<T, E>, error: *mut ExternError) -> *mut T
where
E: Display,
{
// TODO: can't unwind across FFI...
assert!(!error.is_null(), "Error output parameter is not optional");
let error = &mut *error;
error.message = ptr::null_mut();
match result {
Ok(val) => Box::into_raw(Box::new(val)),
Err(e) => {
error.message = string_to_c_char(e.to_string());
ptr::null_mut()
}
}
}
/// Translate Result<Option<T>, E> into something C can understand, when T is not `#[repr(C)]`.
///
/// - If `result` is `Ok(Some(v))`, moves `v` to the heap and returns a pointer to it, and
/// sets `error` to a state indicating that no error occurred (`message` is null).
/// - If `result` is `Ok(None)` returns a null pointer, but sets `error` to a state indicating
/// that no error occurred (`message` is null).
/// - If `result` is `Err(e)`, returns a null pointer and stores a string representing the error
/// message (which was allocated on the heap and should eventually be freed) into
/// `error.message`
/// # Safety
/// Be afraid... TODO
pub unsafe fn translate_opt_result<T, E>(
result: Result<Option<T>, E>,
error: *mut ExternError,
) -> *mut T
where
E: Display,
{
assert!(!error.is_null(), "Error output parameter is not optional");
let error = &mut *error;
error.message = ptr::null_mut();
match result {
Ok(Some(val)) => Box::into_raw(Box::new(val)),
Ok(None) => ptr::null_mut(),
Err(e) => {
error.message = string_to_c_char(e.to_string());
ptr::null_mut()
}
}
}
/// Identical to `translate_result`, but with additional type checking for the case that we have
/// a `Result<(), E>` (which we're about to drop on the floor).
/// # Safety
/// Be afraid... TODO
pub unsafe fn translate_void_result<E>(result: Result<(), E>, error: *mut ExternError)
where
E: Display,
{
// Note that Box<T> guarantees that if T is zero sized, it's not heap allocated. So not
// only do we never need to free the return value of this, it would be a problem if someone did.
translate_result(result, error);
}
}