Loading packages/SystemUI/plugin_core/processor/src/com/android/systemui/plugins/processor/ProtectedPluginProcessor.kt +32 −11 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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 { Loading @@ -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") Loading @@ -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>() Loading @@ -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() Loading @@ -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) { Loading Loading @@ -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() } } Loading packages/SystemUI/plugin_core/src/com/android/systemui/plugins/ProtectedPluginListener.kt +3 −4 Original line number Diff line number Diff line Loading @@ -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 } packages/SystemUI/plugin_core/src/com/android/systemui/plugins/annotations/ProtectedInterface.kt +9 −5 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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. Loading packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java +4 −3 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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; } Loading Loading @@ -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; } Loading Loading
packages/SystemUI/plugin_core/processor/src/com/android/systemui/plugins/processor/ProtectedPluginProcessor.kt +32 −11 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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 { Loading @@ -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") Loading @@ -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>() Loading @@ -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() Loading @@ -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) { Loading Loading @@ -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() } } Loading
packages/SystemUI/plugin_core/src/com/android/systemui/plugins/ProtectedPluginListener.kt +3 −4 Original line number Diff line number Diff line Loading @@ -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 }
packages/SystemUI/plugin_core/src/com/android/systemui/plugins/annotations/ProtectedInterface.kt +9 −5 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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. Loading
packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java +4 −3 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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; } Loading Loading @@ -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; } Loading