Attempt to fall back to the full megazord if not explicitly initialized

This commit is contained in:
Thom Chiovoloni 2019-06-12 11:26:49 -07:00
Родитель b0b15b0b19
Коммит 3d144a3023
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 31F01AEBD799934A
11 изменённых файлов: 184 добавлений и 39 удалений

1
Cargo.lock сгенерированный
Просмотреть файл

@ -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 {}