1from typing import Type, Dict, Optional, List, TYPE_CHECKING 2 3from angr.errors import AngrNoPluginError 4 5import logging 6l = logging.getLogger(name=__name__) 7 8 9class PluginHub: 10 """ 11 A plugin hub is an object which contains many plugins, as well as the notion of a "preset", or a 12 backer that can provide default implementations of plugins which cater to a certain 13 circumstance. 14 15 Objects in angr like the SimState, the Analyses hub, the SimEngine selector, etc all use this 16 model to unify their mechanisms for automatically collecting and selecting components to use. If 17 you're familiar with design patterns this is a configurable Strategy Pattern. 18 19 Each PluginHub subclass should have a corresponding Plugin subclass, and perhaps a PluginPreset 20 subclass if it wants its presets to be able to specify anything more interesting than a list of 21 defaults. 22 """ 23 24 def __init__(self): 25 super(PluginHub, self).__init__() 26 self._active_plugins = {} # type: Dict[str, SimStatePlugin] 27 self._active_preset = None # type: Optional[PluginPreset] 28 self._provided_by_preset = [] # type: List[int] 29 30 # 31 # Class methods for registration 32 # 33 34 _presets = None # not a dict so different subclasses don't share instances 35 36 @classmethod 37 def register_default(cls, name, plugin_cls, preset='default'): 38 if cls._presets is None or preset not in cls._presets: 39 l.error("Preset %s does not exist yet...", preset) 40 return 41 cls._presets[preset].add_default_plugin(name, plugin_cls) 42 43 @classmethod 44 def register_preset(cls, name, preset): 45 """ 46 Register a preset instance with the class of the hub it corresponds to. This allows individual plugin objects to 47 automatically register themselves with a preset by using a classmethod of their own with only the name of the 48 preset to register with. 49 """ 50 if cls._presets is None: 51 cls._presets = {} 52 cls._presets[name] = preset 53 54 # 55 # Python magic methods 56 # 57 58 def __getstate__(self): 59 return self._active_plugins, self._active_preset, self._provided_by_preset 60 61 def __setstate__(self, s): 62 plugins, preset, provided = s 63 self._active_preset = preset 64 self._active_plugins = {} 65 self._provided_by_preset = provided 66 67 for name, plugin in plugins.items(): 68 if name not in self._active_plugins: 69 self.register_plugin(name, plugin) 70 71 def __getattr__(self, name: str) -> 'SimStatePlugin': 72 try: 73 return self.get_plugin(name) 74 except AngrNoPluginError: 75 raise AttributeError(name) 76 77 def __dir__(self): 78 out = set(self.__dict__) 79 out.update(self._active_plugins) 80 if self.has_plugin_preset: 81 out.update(self._active_preset.list_default_plugins()) 82 83 q = [type(self)] 84 while q: 85 cls = q.pop(0) 86 out.update(cls.__dict__) 87 for base in cls.__bases__: 88 if base is not object: 89 q.append(base) 90 91 return sorted(out) 92 93 # 94 # Methods for managing the current plugin preset 95 # 96 97 @property 98 def plugin_preset(self): 99 """ 100 Get the current active plugin preset 101 """ 102 return self._active_preset 103 104 @property 105 def has_plugin_preset(self) -> bool: 106 """ 107 Check whether or not there is a plugin preset in use on this hub right now 108 """ 109 return self._active_preset is not None 110 111 def use_plugin_preset(self, preset): 112 """ 113 Apply a preset to the hub. If there was a previously active preset, discard it. 114 115 Preset can be either the string name of a preset or a PluginPreset instance. 116 """ 117 if isinstance(preset, str): 118 try: 119 preset = self._presets[preset] 120 except (AttributeError, KeyError): 121 raise AngrNoPluginError("There is no preset named %s" % preset) 122 123 elif not isinstance(preset, PluginPreset): 124 raise ValueError("Argument must be an instance of PluginPreset: %s" % preset) 125 126 if self._active_preset: 127 l.warning("Overriding active preset %s with %s", self._active_preset, preset) 128 self.discard_plugin_preset() 129 130 preset.activate(self) 131 self._active_preset = preset 132 133 def discard_plugin_preset(self): 134 """ 135 Discard the current active preset. Will release any active plugins that could have come from the old preset. 136 """ 137 if self.has_plugin_preset: 138 for name, plugin in list(self._active_plugins.items()): 139 if id(plugin) in self._provided_by_preset: 140 self.release_plugin(name) 141 self._active_preset.deactivate(self) 142 self._active_preset = None 143 144 # 145 # Methods for managing the current active plugins 146 # 147 148 def get_plugin(self, name: str) -> 'SimStatePlugin': 149 """ 150 Get the plugin named ``name``. If no such plugin is currently active, try to activate a new 151 one using the current preset. 152 """ 153 if name in self._active_plugins: 154 return self._active_plugins[name] 155 156 elif self.has_plugin_preset: 157 plugin_cls = self._active_preset.request_plugin(name) 158 plugin = self._init_plugin(plugin_cls) 159 160 # Remember that this plugin was provided by preset. 161 self._provided_by_preset.append(id(plugin)) 162 163 self.register_plugin(name, plugin) 164 return plugin 165 166 else: 167 raise AngrNoPluginError("No such plugin: %s" % name) 168 169 def _init_plugin(self, plugin_cls: Type['SimStatePlugin']) -> 'SimStatePlugin': # pylint: disable=no-self-use 170 """ 171 Perform any initialization actions on plugin before it is added to the list of active plugins. 172 173 :param plugin_cls: 174 """ 175 return plugin_cls() 176 177 def has_plugin(self, name): 178 """ 179 Return whether or not a plugin with the name ``name`` is curently active. 180 """ 181 return name in self._active_plugins 182 183 def register_plugin(self, name: str, plugin): 184 """ 185 Add a new plugin ``plugin`` with name ``name`` to the active plugins. 186 """ 187 if self.has_plugin(name): 188 self.release_plugin(name) 189 self._active_plugins[name] = plugin 190 setattr(self, name, plugin) 191 return plugin 192 193 def release_plugin(self, name): 194 """ 195 Deactivate and remove the plugin with name ``name``. 196 """ 197 plugin = self._active_plugins[name] 198 if id(plugin) in self._provided_by_preset: 199 self._provided_by_preset.remove(id(plugin)) 200 201 del self._active_plugins[name] 202 delattr(self, name) 203 204 205class PluginPreset: 206 """ 207 A plugin preset object contains a mapping from name to a plugin class. 208 A preset can be active on a hub, which will cause it to handle requests for plugins which are not already present on the hub. 209 210 Unlike Plugins and PluginHubs, instances of PluginPresets are defined on the module level for individual presets. 211 You should register the preset instance with a hub to allow plugins to easily add themselves to the preset without an explicit reference to the preset itself. 212 """ 213 214 def __init__(self): 215 self._default_plugins = {} # type: Dict[str, Type['SimStatePlugin']] 216 217 def activate(self, hub): # pylint:disable=no-self-use,unused-argument 218 """ 219 This method is called when the preset becomes active on a hub. 220 """ 221 return 222 223 def deactivate(self, hub): # pylint:disable=no-self-use,unused-argument 224 """ 225 This method is called when the preset is discarded from the hub. 226 """ 227 return 228 229 def add_default_plugin(self, name, plugin_cls): 230 """ 231 Add a plugin to the preset. 232 """ 233 self._default_plugins[name] = plugin_cls 234 235 def list_default_plugins(self): 236 """ 237 Return a list of the names of available default plugins. 238 """ 239 return self._default_plugins.keys() 240 241 def request_plugin(self, name: str) -> Type['SimStatePlugin']: 242 """ 243 Return the plugin class which is registered under the name ``name``, or raise NoPlugin if 244 the name isn't available. 245 """ 246 try: 247 return self._default_plugins[name] 248 except KeyError: 249 raise AngrNoPluginError("There is no plugin named %s" % name) 250 251 def copy(self): 252 """ 253 Return a copy of self. 254 """ 255 cls = self.__class__ 256 result = cls.__new__(cls) 257 result._default_plugins = dict(self._default_plugins) # pylint:disable=protected-access 258 return result 259 260 261class PluginVendor(PluginHub): 262 """ 263 A specialized hub which serves only as a plugin vendor, never having any "active" plugins. 264 It will directly return the plugins provided by the preset instead of instanciating them. 265 """ 266 267 def release_plugin(self, name): 268 pass 269 270 def register_plugin(self, name, plugin): 271 pass 272 273 def __dir__(self): 274 x = super(PluginVendor, self).__dir__() 275 x.remove('release_plugin') 276 x.remove('register_plugin') 277 x.remove('has_plugin') 278 return x 279 280 281class VendorPreset(PluginPreset): 282 """ 283 A specialized preset class for use with the PluginVendor. 284 """ 285 ... 286 287if TYPE_CHECKING: 288 from ..state_plugins import SimStatePlugin 289