|
7 | 7 |
|
8 | 8 | from .. import _binaryninjacore as core |
9 | 9 | from ..enums import MergeConflictDataType |
| 10 | +from ..database import KeyValueStore |
10 | 11 | from . import util |
11 | 12 |
|
12 | 13 | from ..database import Database, Snapshot |
@@ -238,3 +239,160 @@ def handle(self, conflicts: Dict[str, MergeConflict]) -> bool: |
238 | 239 | :return: True if all conflicts were successfully merged |
239 | 240 | """ |
240 | 241 | raise NotImplementedError("Not implemented") |
| 242 | + |
| 243 | + |
| 244 | +class ConflictSplitter: |
| 245 | + """ |
| 246 | + Helper class that takes one merge conflict and splits it into multiple conflicts |
| 247 | + Eg takes conflicts for View/symbols and splits to one conflict per symbol |
| 248 | + """ |
| 249 | + |
| 250 | + def __init__(self, handle=None): |
| 251 | + if handle is not None: |
| 252 | + self._handle = handle |
| 253 | + |
| 254 | + def register(self): |
| 255 | + self._cb = core.BNAnalysisMergeConflictSplitterCallbacks() |
| 256 | + self._cb.context = 0 |
| 257 | + self._cb.getName = self._cb.getName.__class__(self._get_name) |
| 258 | + self._cb.reset = self._cb.reset.__class__(self._reset) |
| 259 | + self._cb.finished = self._cb.finished.__class__(self._finished) |
| 260 | + self._cb.canSplit = self._cb.canSplit.__class__(self._can_split) |
| 261 | + self._cb.split = self._cb.split.__class__(self._split) |
| 262 | + self._cb.freeName = self._cb.freeName.__class__(self._free_name) |
| 263 | + self._cb.freeKeyList = self._cb.freeKeyList.__class__(self._free_key_list) |
| 264 | + self._cb.freeConflictList = self._cb.freeConflictList.__class__(self._free_conflict_list) |
| 265 | + self._handle = core.BNRegisterAnalysisMergeConflictSplitter(self._cb) |
| 266 | + self._split_keys = None |
| 267 | + self._split_conflicts = None |
| 268 | + |
| 269 | + |
| 270 | + def _get_name(self, ctxt: ctypes.c_void_p) -> ctypes.c_char_p: |
| 271 | + try: |
| 272 | + return core.BNAllocString(core.cstr(self.name)) |
| 273 | + except: |
| 274 | + # Not sure why your get_name() would throw but let's handle it anyway |
| 275 | + traceback.print_exc(file=sys.stderr) |
| 276 | + return core.BNAllocString(core.cstr(type(self).__name__)) |
| 277 | + |
| 278 | + def _reset(self, ctxt: ctypes.c_void_p): |
| 279 | + try: |
| 280 | + self.reset() |
| 281 | + except: |
| 282 | + traceback.print_exc(file=sys.stderr) |
| 283 | + |
| 284 | + def _finished(self, ctxt: ctypes.c_void_p): |
| 285 | + try: |
| 286 | + self.finished() |
| 287 | + except: |
| 288 | + traceback.print_exc(file=sys.stderr) |
| 289 | + |
| 290 | + def _can_split(self, ctxt: ctypes.c_void_p, key: ctypes.c_char_p, conflict: core.BNAnalysisMergeConflictHandle) -> bool: |
| 291 | + try: |
| 292 | + py_conflict = MergeConflict(handle=conflict) |
| 293 | + return self.can_split(core.pyNativeStr(key), py_conflict) |
| 294 | + except: |
| 295 | + traceback.print_exc(file=sys.stderr) |
| 296 | + return False |
| 297 | + |
| 298 | + def _split( |
| 299 | + self, |
| 300 | + ctxt: ctypes.c_void_p, |
| 301 | + original_key: ctypes.c_char_p, |
| 302 | + original_conflict: core.BNAnalysisMergeConflictHandle, |
| 303 | + result_kvs: core.BNKeyValueStoreHandle, |
| 304 | + new_keys: ctypes.POINTER(ctypes.POINTER(ctypes.c_char_p)), |
| 305 | + new_conflicts: ctypes.POINTER(core.BNAnalysisMergeConflictHandle), |
| 306 | + new_count: ctypes.POINTER(ctypes.c_size_t) |
| 307 | + ) -> bool: |
| 308 | + try: |
| 309 | + py_original_conflict = MergeConflict(handle=original_conflict) |
| 310 | + py_result_kvs = KeyValueStore(handle=ctypes.cast(result_kvs, core.BNKeyValueStoreHandle)) |
| 311 | + result = self.split(core.pyNativeStr(original_key), py_original_conflict, py_result_kvs) |
| 312 | + |
| 313 | + if result is None: |
| 314 | + return False |
| 315 | + |
| 316 | + new_count[0] = ctypes.c_size_t(len(result)) |
| 317 | + new_keys[0] = (ctypes.c_char_p * len(result))() |
| 318 | + new_conflicts[0] = (core.BNAnalysisMergeConflictHandle * len(result))() |
| 319 | + |
| 320 | + self._split_keys = [] |
| 321 | + self._split_conflicts = [] |
| 322 | + |
| 323 | + for (i, (key, conflict)) in enumerate(result): |
| 324 | + self._split_keys.append(key) |
| 325 | + self._split_conflicts.append(conflict) |
| 326 | + |
| 327 | + new_keys[0][i] = core.cstr(self._split_keys[-1]) |
| 328 | + new_conflicts[0][i] = self._split_conflicts[-1]._handle |
| 329 | + |
| 330 | + return True |
| 331 | + except: |
| 332 | + traceback.print_exc(file=sys.stderr) |
| 333 | + return False |
| 334 | + |
| 335 | + def _free_name(self, ctxt: ctypes.c_void_p, name: ctypes.c_char_p): |
| 336 | + core.BNFreeString(name) |
| 337 | + |
| 338 | + def _free_key_list(self, ctxt: ctypes.c_void_p, key_list: ctypes.POINTER(ctypes.c_char_p), count: ctypes.c_size_t): |
| 339 | + del self._split_keys |
| 340 | + |
| 341 | + def _free_conflict_list(self, ctxt: ctypes.c_void_p, conflict_list: core.BNAnalysisMergeConflictHandle, count: ctypes.c_size_t): |
| 342 | + del self._split_conflicts |
| 343 | + |
| 344 | + @property |
| 345 | + def name(self): |
| 346 | + """ |
| 347 | + Get a friendly name for the splitter |
| 348 | +
|
| 349 | + :return: Name of the splitter |
| 350 | + """ |
| 351 | + return self.get_name() |
| 352 | + |
| 353 | + def get_name(self) -> str: |
| 354 | + """ |
| 355 | + Get a friendly name for the splitter |
| 356 | +
|
| 357 | + :return: Name of the splitter |
| 358 | + """ |
| 359 | + return type(self).__name__ |
| 360 | + |
| 361 | + def reset(self): |
| 362 | + """ |
| 363 | + Reset any internal state the splitter may hold during the merge |
| 364 | + """ |
| 365 | + return |
| 366 | + |
| 367 | + def finished(self): |
| 368 | + """ |
| 369 | + Clean up any internal state after the merge operation has finished |
| 370 | + """ |
| 371 | + return |
| 372 | + |
| 373 | + @abc.abstractmethod |
| 374 | + def can_split(self, key: str, conflict: MergeConflict) -> bool: |
| 375 | + """ |
| 376 | + Test if the splitter applies to a given conflict (by key). |
| 377 | +
|
| 378 | + :param key: Key of the conflicting field |
| 379 | + :param conflict: Conflict data |
| 380 | + :return: True if this splitter should be used on the conflict |
| 381 | + """ |
| 382 | + raise NotImplementedError("Not implemented") |
| 383 | + |
| 384 | + @abc.abstractmethod |
| 385 | + def split(self, key: str, conflict: MergeConflict, result: KeyValueStore) -> Optional[Dict[str, MergeConflict]]: |
| 386 | + """ |
| 387 | + Split a field conflict into any number of alternate conflicts. |
| 388 | + Note: Returned conflicts will also be checked for splitting, beware infinite loops! |
| 389 | + If this function raises, it will be treated as returning None |
| 390 | +
|
| 391 | + :param key: Original conflicting field's key |
| 392 | + :param conflict: Original conflict data |
| 393 | + :param result: Kvs structure containing the result of all splits. You should use the original conflict's |
| 394 | + success() function in most cases unless you specifically want to write a new key to this. |
| 395 | + :return: A collection of conflicts into which the original conflict was split, or None if |
| 396 | + this splitter cannot handle the conflict |
| 397 | + """ |
| 398 | + raise NotImplementedError("Not implemented") |
0 commit comments