Attempt to fall back to the full megazord if not explicitly initialized
This commit is contained in:
Родитель
b0b15b0b19
Коммит
3d144a3023
|
@ -1152,6 +1152,7 @@ name = "megazord"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"fxaclient_ffi 0.1.0",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"logins_ffi 0.1.0",
|
||||
"places-ffi 0.1.0",
|
||||
"push-ffi 0.1.0",
|
||||
|
|
|
@ -15,7 +15,7 @@ import org.mozilla.appservices.fxaclient.BuildConfig
|
|||
internal interface LibFxAFFI : Library {
|
||||
companion object {
|
||||
internal var INSTANCE: LibFxAFFI =
|
||||
loadIndirect(libName = "fxaclient", libVersion = BuildConfig.LIBRARY_VERSION)
|
||||
loadIndirect(componentName = "fxaclient", componentVersion = BuildConfig.LIBRARY_VERSION)
|
||||
}
|
||||
|
||||
fun fxa_new(
|
||||
|
|
|
@ -15,7 +15,7 @@ import org.mozilla.appservices.logins.BuildConfig
|
|||
internal interface PasswordSyncAdapter : Library {
|
||||
companion object {
|
||||
internal var INSTANCE: PasswordSyncAdapter =
|
||||
loadIndirect(libName = "logins", libVersion = BuildConfig.LIBRARY_VERSION)
|
||||
loadIndirect(componentName = "logins", componentVersion = BuildConfig.LIBRARY_VERSION)
|
||||
}
|
||||
|
||||
fun sync15_passwords_state_new(
|
||||
|
|
|
@ -17,7 +17,7 @@ import org.mozilla.appservices.places.BuildConfig
|
|||
internal interface LibPlacesFFI : Library {
|
||||
companion object {
|
||||
internal var INSTANCE: LibPlacesFFI =
|
||||
loadIndirect(libName = "places", libVersion = BuildConfig.LIBRARY_VERSION)
|
||||
loadIndirect(componentName = "places", componentVersion = BuildConfig.LIBRARY_VERSION)
|
||||
}
|
||||
|
||||
// Important: strings returned from rust as *mut char must be Pointers on this end, returning a
|
||||
|
|
|
@ -15,7 +15,7 @@ import org.mozilla.appservices.push.BuildConfig
|
|||
internal interface LibPushFFI : Library {
|
||||
companion object {
|
||||
internal var INSTANCE: LibPushFFI =
|
||||
loadIndirect(libName = "push", libVersion = BuildConfig.LIBRARY_VERSION)
|
||||
loadIndirect(componentName = "push", componentVersion = BuildConfig.LIBRARY_VERSION)
|
||||
}
|
||||
|
||||
// Important: strings returned from rust as *mut char must be Pointers on this end, returning a
|
||||
|
|
|
@ -16,7 +16,7 @@ internal interface LibRustLogAdapter : Library {
|
|||
companion object {
|
||||
// XXX this should be direct binding...
|
||||
internal var INSTANCE: LibRustLogAdapter =
|
||||
loadIndirect(libName = "rustlog", libVersion = BuildConfig.LIBRARY_VERSION)
|
||||
loadIndirect(componentName = "rustlog", componentVersion = BuildConfig.LIBRARY_VERSION)
|
||||
}
|
||||
|
||||
fun rc_log_adapter_create(
|
||||
|
|
|
@ -29,34 +29,85 @@ fun <T : MessageLite> T.toNioDirectBuffer(): Pair<ByteBuffer, Int> {
|
|||
}
|
||||
|
||||
sealed class MegazordError : Exception {
|
||||
val libName: String
|
||||
constructor(libName: String, msg: String) : super(msg) {
|
||||
this.libName = libName
|
||||
/**
|
||||
* The name of the component we were trying to initialize when we had the error.
|
||||
*/
|
||||
val componentName: String
|
||||
constructor(componentName: String, msg: String) : super(msg) {
|
||||
this.componentName = componentName
|
||||
}
|
||||
constructor(libName: String, msg: String, cause: Throwable) : super(msg, cause) {
|
||||
this.libName = libName
|
||||
constructor(componentName: String, msg: String, cause: Throwable) : super(msg, cause) {
|
||||
this.componentName = componentName
|
||||
}
|
||||
}
|
||||
|
||||
class MegazordNotAvailable(
|
||||
libName: String,
|
||||
componentName: String,
|
||||
cause: UnsatisfiedLinkError
|
||||
) : MegazordError(libName, "Failed to locate megazord library: '$libName'", cause)
|
||||
) : MegazordError(componentName, "Failed to locate megazord library: '$componentName'", cause)
|
||||
|
||||
class IncompatibleMegazordVersion(
|
||||
libName: String,
|
||||
val libVersion: String,
|
||||
val mzVersion: String
|
||||
componentName: String,
|
||||
val componentVersion: String,
|
||||
val megazordLibrary: String,
|
||||
val megazordVersion: String?
|
||||
) : MegazordError(
|
||||
libName,
|
||||
"Incompatible megazord version: library \"$libName\" was compiled expecting " +
|
||||
"app-services version \"$libVersion\", but the megazord provides version \"$mzVersion\""
|
||||
componentName,
|
||||
"Incompatible megazord version: library \"$componentName\" was compiled expecting " +
|
||||
"app-services version \"$componentVersion\", but the megazord \"$megazordLibrary\" provides " +
|
||||
"version \"${megazordVersion ?: "unknown"}\""
|
||||
)
|
||||
|
||||
class MegazordNotInitialized(libName: String) : MegazordError(
|
||||
libName,
|
||||
"The application-services megazord has not yet been initialized, but is needed by \"$libName\""
|
||||
class MegazordNotInitialized(componentName: String) : MegazordError(
|
||||
componentName,
|
||||
"The application-services megazord has not yet been initialized, but is needed by \"$componentName\""
|
||||
)
|
||||
|
||||
/**
|
||||
* I think we'd expect this to be caused by the following two things both happening
|
||||
*
|
||||
* 1. Substitution not actually replacing the full megazord
|
||||
* 2. Megazord initialization getting called after the first attempt to load something from the
|
||||
* megazord, causing us to fall back to checking the full-megazord (and finding it, because
|
||||
* of #1).
|
||||
*
|
||||
* It's very unlikely, but if it did happen it could be a memory safety error, so we check.
|
||||
*/
|
||||
class MultipleMegazordsPresent(
|
||||
componentName: String,
|
||||
val loadedMegazord: String,
|
||||
val requestedMegazord: String
|
||||
) : MegazordError(
|
||||
componentName,
|
||||
"Multiple megazords are present, and bindings have already been loaded from " +
|
||||
"\"$loadedMegazord\" when a request to load $componentName from $requestedMegazord " +
|
||||
"is made. (This probably stems from an error in your build configuration)"
|
||||
)
|
||||
|
||||
internal const val FULL_MEGAZORD_LIBRARY: String = "megazord"
|
||||
|
||||
internal fun doMegazordCheck(componentName: String, componentVersion: String): String {
|
||||
val mzLibrary = System.getProperty("mozilla.appservices.megazord.library")
|
||||
if (mzLibrary == null) {
|
||||
// If it's null, then the megazord hasn't been initialized.
|
||||
if (checkFullMegazord(componentName, componentVersion)) {
|
||||
return FULL_MEGAZORD_LIBRARY
|
||||
}
|
||||
throw MegazordNotInitialized(componentName)
|
||||
}
|
||||
|
||||
// Assume it's properly initialized if it's been initialized at all
|
||||
val mzVersion = System.getProperty("mozilla.appservices.megazord.version")!!
|
||||
|
||||
// We require exact equality, since we don't perform a major version
|
||||
// bump if we change the ABI. In practice, this seems unlikely to
|
||||
// cause problems, but we could come up with a scheme if this proves annoying.
|
||||
if (componentVersion != mzVersion) {
|
||||
throw IncompatibleMegazordVersion(componentName, componentVersion, mzLibrary, mzVersion)
|
||||
}
|
||||
return mzLibrary
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the megazord library name, and check that it's version is
|
||||
* compatible with the version of our bindings. Returns the megazord
|
||||
|
@ -65,20 +116,24 @@ class MegazordNotInitialized(libName: String) : MegazordError(
|
|||
* Note: This is only public because it's called by an inline function.
|
||||
* It should not be called by consumers.
|
||||
*/
|
||||
fun megazordCheck(libName: String, libVersion: String): String {
|
||||
val mzLibrary = System.getProperty("mozilla.appservices.megazord.library")
|
||||
?: throw MegazordNotInitialized(libName)
|
||||
@Synchronized
|
||||
fun megazordCheck(componentName: String, componentVersion: String): String {
|
||||
val mzLibraryUsed = System.getProperty("mozilla.appservices.megazord.library.used")
|
||||
val mzLibraryDetermined = doMegazordCheck(componentName, componentVersion)
|
||||
|
||||
// Assume it's properly initialized if it's been initialized at all
|
||||
val mzVersion = System.getProperty("mozilla.appservices.megazord.version")!!
|
||||
|
||||
// We require exact equality, since we don't perform a major version
|
||||
// bump if we change the ABI. In practice, this seems unlikely to
|
||||
// cause problems, but we could come up with a scheme if this proves annoying.
|
||||
if (libVersion != mzVersion) {
|
||||
throw IncompatibleMegazordVersion(libName, libVersion, mzVersion)
|
||||
// If we've already initialized the megazord, that means we've probably already loaded bindings
|
||||
// from it somewhere. It would be a big problem for us to use some bindings from one lib and
|
||||
// some from another, so we just fail.
|
||||
if (mzLibraryUsed != null && mzLibraryDetermined != mzLibraryUsed) {
|
||||
throw MultipleMegazordsPresent(componentName, mzLibraryUsed, mzLibraryDetermined)
|
||||
}
|
||||
return mzLibrary
|
||||
|
||||
// Mark that we're about to load bindings from the specified lib. Note that we don't do this
|
||||
// in the case that the megazord check threw.
|
||||
if (mzLibraryUsed != null) {
|
||||
System.setProperty("mozilla.appservices.megazord.library.used", mzLibraryDetermined)
|
||||
}
|
||||
return mzLibraryDetermined
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,8 +142,8 @@ fun megazordCheck(libName: String, libVersion: String): String {
|
|||
* Indirect as in, we aren't using JNA direct mapping. Eventually we'd
|
||||
* like to (it's faster), but that's a problem for another day.
|
||||
*/
|
||||
inline fun <reified Lib : Library> loadIndirect(libName: String, libVersion: String): Lib {
|
||||
val mzLibrary = megazordCheck(libName, libVersion)
|
||||
inline fun <reified Lib : Library> loadIndirect(componentName: String, componentVersion: String): Lib {
|
||||
val mzLibrary = megazordCheck(componentName, componentVersion)
|
||||
return try {
|
||||
Native.load<Lib>(mzLibrary, Lib::class.java)
|
||||
} catch (e: UnsatisfiedLinkError) {
|
||||
|
@ -96,7 +151,45 @@ inline fun <reified Lib : Library> loadIndirect(libName: String, libVersion: Str
|
|||
Proxy.newProxyInstance(
|
||||
Lib::class.java.classLoader,
|
||||
arrayOf(Lib::class.java)) { _, _, _ ->
|
||||
throw MegazordNotAvailable(libName, e)
|
||||
throw MegazordNotAvailable(componentName, e)
|
||||
} as Lib
|
||||
}
|
||||
}
|
||||
|
||||
// See the comment on full_megazord_get_version for background
|
||||
// on why this exists and what we use it for.
|
||||
@Suppress("FunctionNaming")
|
||||
internal interface LibMegazordFfi : Library {
|
||||
// Note: Rust doesn't want us to free this string (because
|
||||
// it's a pain for us to arrange here), so it is actually
|
||||
// correct for us to return a String over the FFI for this.
|
||||
fun full_megazord_get_version(): String?
|
||||
}
|
||||
|
||||
/**
|
||||
* Try and load the full megazord library, call the function for getting its
|
||||
* version, and check it against componentVersion.
|
||||
*
|
||||
* - If the megazord does not exist, returns false
|
||||
* - If the megazord exists and the version is valid, returns true.
|
||||
* - If the megazord exists and the version is invalid, throws a IncompatibleMegazordVersion error.
|
||||
* (This is done here instead of returning false so that we can provide better info in the error)
|
||||
*/
|
||||
internal fun checkFullMegazord(componentName: String, componentVersion: String): Boolean {
|
||||
return try {
|
||||
// It's not ideal to do this every time, but it should be rare, not too costly,
|
||||
// and the workaround for the app is simple (just init the megazord).
|
||||
val lib = Native.load<LibMegazordFfi>(FULL_MEGAZORD_LIBRARY, LibMegazordFfi::class.java)
|
||||
|
||||
val version = lib.full_megazord_get_version()
|
||||
?: throw IncompatibleMegazordVersion(componentName, componentVersion, FULL_MEGAZORD_LIBRARY, null)
|
||||
|
||||
if (version != componentVersion) {
|
||||
throw IncompatibleMegazordVersion(componentName, componentVersion, FULL_MEGAZORD_LIBRARY, version)
|
||||
}
|
||||
|
||||
true
|
||||
} catch (e: UnsatisfiedLinkError) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
package mozilla.appservices.httpconfig
|
||||
|
||||
import com.sun.jna.Library
|
||||
import com.sun.jna.Callback
|
||||
import com.sun.jna.Library
|
||||
import mozilla.appservices.support.native.RustBuffer
|
||||
import mozilla.appservices.support.native.loadIndirect
|
||||
import org.mozilla.appservices.httpconfig.BuildConfig
|
||||
|
@ -14,7 +14,10 @@ import org.mozilla.appservices.httpconfig.BuildConfig
|
|||
internal interface LibViaduct : Library {
|
||||
companion object {
|
||||
internal var INSTANCE: LibViaduct = {
|
||||
val inst = loadIndirect<LibViaduct>(libName = "viaduct", libVersion = BuildConfig.LIBRARY_VERSION)
|
||||
val inst = loadIndirect<LibViaduct>(
|
||||
componentName = "viaduct",
|
||||
componentVersion = BuildConfig.LIBRARY_VERSION
|
||||
)
|
||||
inst.viaduct_force_enable_ffi_backend(1)
|
||||
inst
|
||||
}()
|
||||
|
|
|
@ -15,3 +15,4 @@ places-ffi = { path = "../../components/places/ffi" }
|
|||
push-ffi = { path = "../../components/push/ffi" }
|
||||
rc_log_ffi = { path = "../../components/rc_log" }
|
||||
viaduct = { path = "../../components/viaduct", default_features = false }
|
||||
lazy_static = "1.3.0"
|
||||
|
|
|
@ -73,7 +73,11 @@ cargo {
|
|||
// `debug = true` in Cargo.toml).
|
||||
profile = "release"
|
||||
|
||||
exec = rootProject.ext.cargoExecWithSQLCipher
|
||||
exec = { spec, toolchain ->
|
||||
rootProject.ext.cargoExecWithSQLCipher(spec, toolchain)
|
||||
// Only used in the full megazord (that is, in this project) at build time.
|
||||
spec.environment("MEGAZORD_VERSION", rootProject.ext.library.version)
|
||||
}
|
||||
|
||||
extraCargoBuildArguments = rootProject.ext.extraCargoBuildArguments
|
||||
}
|
||||
|
|
|
@ -5,9 +5,52 @@
|
|||
#![allow(unknown_lints)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
pub use fxaclient_ffi;
|
||||
pub use logins_ffi;
|
||||
pub use places_ffi;
|
||||
pub use push_ffi;
|
||||
pub use rc_log_ffi;
|
||||
pub use viaduct;
|
||||
|
||||
/// In order to support the use case of consumers who don't know about megazords
|
||||
/// and don't need our e.g. networking or logging, we consider initialization
|
||||
/// optional for the default (full) megazord.
|
||||
///
|
||||
/// This function exists so that the `native_support` code can ensure that we
|
||||
/// still check tthat the version of the functions in the megazord library and
|
||||
/// the version of the code loading them is identical.
|
||||
///
|
||||
/// Critically, that means this function (unlike our other functions) must be
|
||||
/// ABI stable! It needs to take no arguments, and return either null, or a
|
||||
/// NUL-terminated C string.
|
||||
///
|
||||
/// If we ever need to change that (which seems unlikely, since we could encode
|
||||
/// whatever we want in a string if it came to it), we must change the functions
|
||||
/// name too.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn full_megazord_get_version() -> *const c_char {
|
||||
VERSION_PTR.0
|
||||
}
|
||||
|
||||
// This is set by gradle, but wouldn't be set otherwise. If it is unset,
|
||||
// we'll return null from this function, which will cause the megazord
|
||||
// version checker to throw. Separated as a constant to make it clear that
|
||||
// this is a thing determined at compile time.
|
||||
static VERSION: Option<&'static str> = option_env!("MEGAZORD_VERSION");
|
||||
|
||||
// For now it's tricky for this string to get freed, so just allocate one and save its pointer.
|
||||
lazy_static::lazy_static! {
|
||||
static ref VERSION_PTR: StaticCStringPtr = StaticCStringPtr(
|
||||
VERSION.and_then(|s| CString::new(s).ok())
|
||||
.map_or(std::ptr::null(), |cs| cs.into_raw()));
|
||||
}
|
||||
|
||||
// Wrapper that lets us keep a raw pointer in a lazy_static
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct StaticCStringPtr(*const c_char);
|
||||
unsafe impl Send for StaticCStringPtr {}
|
||||
unsafe impl Sync for StaticCStringPtr {}
|
||||
|
|
Загрузка…
Ссылка в новой задаче