Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5eb3bf85 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "More flexible plugin protection layer" into main

parents 7a5293c7 c1a17930
Loading
Loading
Loading
Loading
+32 −11
Original line number Diff line number Diff line
@@ -33,9 +33,10 @@ import kotlin.collections.ArrayDeque

/**
 * [ProtectedPluginProcessor] generates a proxy implementation for interfaces annotated with
 * [ProtectedInterface] which catches [LinkageError]s generated by the proxied target. This protects
 * the plugin host from crashing due to out-of-date plugin code, where some call has changed so that
 * the [ClassLoader] can no longer resolve it correctly.
 * [ProtectedInterface] which catches [Exception]s generated by the proxied target. Production
 * plugin interfaces should use this to catch [LinkagError]s as that protects the plugin host from
 * crashing due to out-of-date plugin code, where some call has changed so that the [ClassLoader] is
 * no longer able to resolve it correctly.
 *
 * [PluginInstance] observes these failures via [ProtectedMethodListener] and unloads the plugin in
 * question to prevent further issues. This persists through further load/unload requests.
@@ -61,6 +62,7 @@ class ProtectedPluginProcessor : AbstractProcessor() {
        val sourcePkg: String,
        val sourceName: String,
        val outputName: String,
        val exTypeAttr: ProtectedInterface,
    )

    override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
@@ -68,10 +70,19 @@ class ProtectedPluginProcessor : AbstractProcessor() {
        val additionalImports = mutableSetOf<String>()
        for (attr in annotations) {
            for (target in roundEnv.getElementsAnnotatedWith(attr)) {
                // Find the target exception types to be used
                var exTypeAttr = target.getAnnotation(ProtectedInterface::class.java)
                if (exTypeAttr == null || exTypeAttr.exTypes.size == 0) {
                    exTypeAttr = ProtectedInterface.Default
                }

                val sourceName = "${target.simpleName}"
                val outputName = "${sourceName}Protector"
                val pkg = (target.getEnclosingElement() as PackageElement).qualifiedName.toString()
                targets.put("$target", TargetData(attr, target, pkg, sourceName, outputName))
                targets.put(
                    "$target",
                    TargetData(attr, target, pkg, sourceName, outputName, exTypeAttr),
                )

                // This creates excessive imports, but it should be fine
                additionalImports.add("$pkg.$sourceName")
@@ -80,7 +91,7 @@ class ProtectedPluginProcessor : AbstractProcessor() {
        }

        if (targets.size <= 0) return false
        for ((_, sourceType, sourcePkg, sourceName, outputName) in targets.values) {
        for ((_, sourceType, sourcePkg, sourceName, outputName, exTypeAttr) in targets.values) {
            // Find all methods in this type and all super types to that need to be implemented
            val types = ArrayDeque<TypeMirror>().apply { addLast(sourceType.asType()) }
            val impAttrs = mutableListOf<GeneratedImport>()
@@ -105,7 +116,6 @@ class ProtectedPluginProcessor : AbstractProcessor() {

                // Imports used by the proxy implementation
                line("import android.util.Log;")
                line("import java.lang.LinkageError;")
                line("import com.android.systemui.plugins.PluginWrapper;")
                line("import com.android.systemui.plugins.ProtectedPluginListener;")
                line()
@@ -118,6 +128,14 @@ class ProtectedPluginProcessor : AbstractProcessor() {
                    line()
                }

                // Imports of caught exceptions
                if (exTypeAttr.exTypes.size > 0) {
                    for (exType in exTypeAttr.exTypes) {
                        line("import $exType;")
                    }
                    line()
                }

                // Imports declared via @GeneratedImport
                if (impAttrs.size > 0) {
                    for (impAttr in impAttrs) {
@@ -232,13 +250,16 @@ class ProtectedPluginProcessor : AbstractProcessor() {
                            // Protect callsite in try/catch block
                            braceBlock("try") { line(callStatement) }

                            // Notify listener when a LinkageError is caught
                            braceBlock("catch (LinkageError ex)") {
                            // Notify listener when a target exception is caught
                            for (exType in exTypeAttr.exTypes) {
                                val simpleName = exType.substringAfterLast(".")
                                braceBlock("catch ($simpleName ex)") {
                                    line("Log.wtf(CLASS, \"Failed to execute: \" + METHOD, ex);")
                                    line("mHasError = mListener.onFail(CLASS, METHOD, ex);")
                                    line(errorStatement)
                                }
                            }
                        }
                        line()
                    }
                }
+3 −4
Original line number Diff line number Diff line
@@ -16,12 +16,11 @@ package com.android.systemui.plugins
/** Listener for events from proxy types generated by [ProtectedPluginProcessor]. */
interface ProtectedPluginListener {
    /**
     * Called when a method call produces a [LinkageError] before returning. This callback is
     * provided so that the host application can terminate the plugin or log the error as
     * appropriate.
     * Called when a method call produces a [Exception] before returning. This callback is provided
     * so that the host application can terminate the plugin or log the error as appropriate.
     *
     * @return true to terminate all methods within this object; false if the error is recoverable
     *   and the proxied plugin should continue to operate as normal.
     */
    fun onFail(className: String, methodName: String, failure: LinkageError): Boolean
    fun onFail(className: String, methodName: String, failure: Throwable): Boolean
}
+9 −5
Original line number Diff line number Diff line
@@ -15,11 +15,15 @@ package com.android.systemui.plugins.annotations

/**
 * This annotation marks denotes that an interface should use a proxy layer to protect the plugin
 * host from crashing due to [LinkageError]s originating within the plugin's implementation.
 * host from crashing due to the [Exception] types originating within the plugin's implementation.
 */
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class ProtectedInterface
annotation class ProtectedInterface(vararg val exTypes: String) {
    companion object {
        val Default = ProtectedInterface("java.lang.Exception", "java.lang.LinkageError")
    }
}

/**
 * This annotation specifies any additional imports that the processor will require when generating
@@ -32,9 +36,9 @@ annotation class ProtectedInterface
annotation class GeneratedImport(val extraImport: String)

/**
 * This annotation provides default values to return when the proxy implementation catches a
 * [LinkageError]. The string specified should be a simple but valid java statement. In most cases
 * it should be a return statement of the appropriate type, but in some cases throwing a known
 * This annotation provides default values to return when the proxy implementation catches a target
 * [Exception]. The string specified should be a simple but valid java statement. In most cases it
 * should be a return statement of the appropriate type, but in some cases throwing a known
 * exception type may be preferred.
 *
 * This annotation is not required for methods that return void, but will behave the same way.
+4 −3
Original line number Diff line number Diff line
@@ -108,7 +108,8 @@ public class PluginInstance<T extends Plugin>
    }

    @Override
    public synchronized boolean onFail(String className, String methodName, LinkageError failure) {
    public synchronized boolean onFail(String className, String methodName, Throwable failure) {
        Log.e(TAG, "Failure from " + mPlugin + ". Disabling Plugin.");
        mHasError = true;
        unloadPlugin();
        mListener.onPluginDetached(this);
@@ -118,7 +119,7 @@ public class PluginInstance<T extends Plugin>
    /** Alerts listener and plugin that the plugin has been created. */
    public synchronized void onCreate() {
        if (mHasError) {
            log("Previous LinkageError detected for plugin class");
            log("Previous Fatal Exception detected for plugin class");
            return;
        }

@@ -175,7 +176,7 @@ public class PluginInstance<T extends Plugin>
     */
    public synchronized void loadPlugin() {
        if (mHasError) {
            log("Previous LinkageError detected for plugin class");
            log("Previous Fatal Exception detected for plugin class");
            return;
        }