Skip to content

Commit 9d9c230

Browse files
committed
[WIP] Rust API: Sketch ConflictSplitter API
Nearly entirely vibed by codex. Please have a human review this before merge
1 parent 9601e9b commit 9d9c230

1 file changed

Lines changed: 300 additions & 7 deletions

File tree

rust/src/collaboration/merge.rs

Lines changed: 300 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use binaryninjacore_sys::*;
2+
use std::collections::HashMap;
3+
use std::ffi::{c_char, c_void};
24
use std::ptr::NonNull;
35

4-
use crate::database::{snapshot::Snapshot, Database};
6+
use crate::database::{kvs::KeyValueStore, snapshot::Snapshot, Database};
57
use crate::file_metadata::FileMetadata;
6-
use crate::rc::{CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
7-
use crate::string::{BnString, IntoCStr};
8+
use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
9+
use crate::string::{raw_to_string, BnString, IntoCStr};
810

911
pub type MergeConflictDataType = BNMergeConflictDataType;
1012

@@ -119,10 +121,17 @@ impl MergeConflict {
119121
}
120122

121123
/// Call this when you've resolved the conflict to save the result
122-
pub fn success(&self, value: &str) -> Result<(), ()> {
123-
let value = value.to_cstr();
124-
let success =
125-
unsafe { BNAnalysisMergeConflictSuccess(self.handle.as_ptr(), value.as_ptr()) };
124+
pub fn success(&self, value: Option<&str>) -> Result<(), ()> {
125+
let value = value.map(|v| v.to_cstr());
126+
let success = unsafe {
127+
BNAnalysisMergeConflictSuccess(
128+
self.handle.as_ptr(),
129+
value
130+
.as_ref()
131+
.map(|v| v.as_ptr())
132+
.unwrap_or(std::ptr::null()),
133+
)
134+
};
126135
success.then_some(()).ok_or(())
127136
}
128137

@@ -187,3 +196,287 @@ unsafe impl CoreArrayProviderInner for MergeConflict {
187196
Guard::new(Self::from_raw(raw_ptr), context)
188197
}
189198
}
199+
200+
/// Register a custom [`ConflictSplitter`] with the API.
201+
pub fn register_conflict_splitter<C: ConflictSplitter>(
202+
splitter: C,
203+
) -> (&'static mut C, CoreConflictSplitter) {
204+
let splitter = Box::leak(Box::new(splitter));
205+
let mut callbacks = BNAnalysisMergeConflictSplitterCallbacks {
206+
context: splitter as *mut C as *mut c_void,
207+
getName: Some(cb_get_name::<C>),
208+
reset: Some(cb_reset::<C>),
209+
finished: Some(cb_finished::<C>),
210+
canSplit: Some(cb_can_split::<C>),
211+
split: Some(cb_split::<C>),
212+
freeName: Some(cb_free_name),
213+
freeKeyList: Some(cb_free_key_list),
214+
freeConflictList: Some(cb_free_conflict_list),
215+
};
216+
let handle = unsafe { BNRegisterAnalysisMergeConflictSplitter(&mut callbacks) };
217+
let core = unsafe { CoreConflictSplitter::from_raw(NonNull::new(handle).unwrap()) };
218+
(splitter, core)
219+
}
220+
221+
/// Helper trait that takes one merge conflict and splits it into multiple conflicts.
222+
///
223+
/// This is used to take a large conflict and subdivide it into smaller conflicts that can be
224+
/// resolved independently.
225+
pub trait ConflictSplitter: Sized {
226+
/// Get a friendly name for the splitter.
227+
fn name(&self) -> String;
228+
229+
/// Reset any internal state the splitter may hold during the merge.
230+
fn reset(&mut self) {}
231+
232+
/// Clean up any internal state after the merge operation has finished.
233+
fn finished(&mut self) {}
234+
235+
/// Test if the splitter applies to a given conflict.
236+
fn can_split(&mut self, key: &str, conflict: &MergeConflict) -> bool;
237+
238+
/// Split a field conflict into any number of alternate conflicts.
239+
///
240+
/// Returned conflicts will also be checked for splitting, so implementations must avoid
241+
/// producing infinite split loops.
242+
fn split(
243+
&mut self,
244+
original_key: &str,
245+
original_conflict: &MergeConflict,
246+
result: &KeyValueStore,
247+
) -> Option<HashMap<String, Ref<MergeConflict>>>;
248+
}
249+
250+
#[repr(transparent)]
251+
pub struct CoreConflictSplitter {
252+
pub(crate) handle: NonNull<BNAnalysisMergeConflictSplitter>,
253+
}
254+
255+
impl CoreConflictSplitter {
256+
pub(crate) unsafe fn from_raw(handle: NonNull<BNAnalysisMergeConflictSplitter>) -> Self {
257+
Self { handle }
258+
}
259+
260+
/// Get a list of all active conflict splitters.
261+
pub fn all() -> Array<CoreConflictSplitter> {
262+
let mut count = 0;
263+
let result = unsafe { BNGetAnalysisMergeConflictSplitterList(&mut count) };
264+
unsafe { Array::new(result, count, ()) }
265+
}
266+
267+
/// Get a friendly name for the splitter.
268+
pub fn name(&self) -> String {
269+
let result = unsafe { BNAnalysisMergeConflictSplitterGetName(self.handle.as_ptr()) };
270+
assert!(!result.is_null());
271+
unsafe { BnString::into_string(result) }
272+
}
273+
274+
/// Test if the splitter applies to a given conflict.
275+
pub fn can_split(&self, key: &str, conflict: &MergeConflict) -> bool {
276+
let key = key.to_cstr();
277+
unsafe {
278+
BNAnalysisMergeConflictSplitterCanSplit(
279+
self.handle.as_ptr(),
280+
key.as_ptr(),
281+
conflict.handle.as_ptr(),
282+
)
283+
}
284+
}
285+
286+
/// Split a field conflict into any number of alternate conflicts.
287+
pub fn split(
288+
&self,
289+
original_key: &str,
290+
original_conflict: &MergeConflict,
291+
result: &KeyValueStore,
292+
) -> Option<HashMap<String, Ref<MergeConflict>>> {
293+
let original_key = original_key.to_cstr();
294+
let mut new_keys = std::ptr::null_mut();
295+
let mut new_conflicts = std::ptr::null_mut();
296+
let mut new_count = 0;
297+
let success = unsafe {
298+
BNAnalysisMergeConflictSplitterSplit(
299+
self.handle.as_ptr(),
300+
original_key.as_ptr(),
301+
original_conflict.handle.as_ptr(),
302+
result.handle.as_ptr(),
303+
&mut new_keys,
304+
&mut new_conflicts,
305+
&mut new_count,
306+
)
307+
};
308+
if !success {
309+
return None;
310+
}
311+
if new_count == 0 {
312+
if !new_keys.is_null() {
313+
unsafe { BNFreeStringList(new_keys, 0) };
314+
}
315+
if !new_conflicts.is_null() {
316+
unsafe { BNFreeAnalysisMergeConflictList(new_conflicts, 0) };
317+
}
318+
return Some(HashMap::new());
319+
}
320+
321+
assert!(!new_keys.is_null());
322+
assert!(!new_conflicts.is_null());
323+
324+
let keys: Array<BnString> = unsafe { Array::new(new_keys, new_count, ()) };
325+
let conflicts: Array<MergeConflict> = unsafe { Array::new(new_conflicts, new_count, ()) };
326+
Some(
327+
keys.iter()
328+
.zip(conflicts.iter())
329+
.map(|(key, conflict)| (key.to_string(), conflict.clone()))
330+
.collect(),
331+
)
332+
}
333+
}
334+
335+
impl CoreArrayProvider for CoreConflictSplitter {
336+
type Raw = *mut BNAnalysisMergeConflictSplitter;
337+
type Context = ();
338+
type Wrapped<'a> = Self;
339+
}
340+
341+
unsafe impl CoreArrayProviderInner for CoreConflictSplitter {
342+
unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
343+
BNFreeAnalysisMergeConflictSplitterList(raw, count)
344+
}
345+
346+
unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
347+
let raw_ptr = NonNull::new(*raw).unwrap();
348+
Self::from_raw(raw_ptr)
349+
}
350+
}
351+
352+
unsafe extern "C" fn cb_get_name<C: ConflictSplitter>(ctxt: *mut c_void) -> *mut c_char {
353+
ffi_wrap!("ConflictSplitter::get_name", unsafe {
354+
let ctxt: &mut C = &mut *(ctxt as *mut C);
355+
BnString::into_raw(BnString::new(ctxt.name()))
356+
})
357+
}
358+
359+
unsafe extern "C" fn cb_reset<C: ConflictSplitter>(ctxt: *mut c_void) {
360+
ffi_wrap!("ConflictSplitter::reset", unsafe {
361+
let ctxt: &mut C = &mut *(ctxt as *mut C);
362+
ctxt.reset();
363+
})
364+
}
365+
366+
unsafe extern "C" fn cb_finished<C: ConflictSplitter>(ctxt: *mut c_void) {
367+
ffi_wrap!("ConflictSplitter::finished", unsafe {
368+
let ctxt: &mut C = &mut *(ctxt as *mut C);
369+
ctxt.finished();
370+
})
371+
}
372+
373+
unsafe extern "C" fn cb_can_split<C: ConflictSplitter>(
374+
ctxt: *mut c_void,
375+
key: *const c_char,
376+
conflict: *const BNAnalysisMergeConflict,
377+
) -> bool {
378+
ffi_wrap!("ConflictSplitter::can_split", unsafe {
379+
let ctxt: &mut C = &mut *(ctxt as *mut C);
380+
let Some(key) = raw_to_string(key) else {
381+
return false;
382+
};
383+
let Some(conflict) = NonNull::new(conflict as *mut BNAnalysisMergeConflict) else {
384+
return false;
385+
};
386+
let conflict = MergeConflict::from_raw(conflict);
387+
ctxt.can_split(&key, &conflict)
388+
})
389+
}
390+
391+
unsafe extern "C" fn cb_split<C: ConflictSplitter>(
392+
ctxt: *mut c_void,
393+
original_key: *const c_char,
394+
original_conflict: *const BNAnalysisMergeConflict,
395+
result: *mut BNKeyValueStore,
396+
new_keys: *mut *mut *mut c_char,
397+
new_conflicts: *mut *mut *mut BNAnalysisMergeConflict,
398+
new_count: *mut usize,
399+
) -> bool {
400+
ffi_wrap!("ConflictSplitter::split", unsafe {
401+
let ctxt: &mut C = &mut *(ctxt as *mut C);
402+
*new_keys = std::ptr::null_mut();
403+
*new_conflicts = std::ptr::null_mut();
404+
*new_count = 0;
405+
406+
let Some(original_key) = raw_to_string(original_key) else {
407+
return false;
408+
};
409+
let Some(original_conflict) =
410+
NonNull::new(original_conflict as *mut BNAnalysisMergeConflict)
411+
else {
412+
return false;
413+
};
414+
let Some(result) = NonNull::new(result) else {
415+
return false;
416+
};
417+
418+
let original_conflict = MergeConflict::from_raw(original_conflict);
419+
let result = KeyValueStore { handle: result };
420+
let Some(split_result) = ctxt.split(&original_key, &original_conflict, &result) else {
421+
return false;
422+
};
423+
if split_result.is_empty() {
424+
return true;
425+
}
426+
427+
let mut keys = Vec::with_capacity(split_result.len());
428+
let mut conflicts = Vec::with_capacity(split_result.len());
429+
for (key, conflict) in split_result {
430+
keys.push(BnString::into_raw(BnString::new(key)));
431+
conflicts.push(BNNewAnalysisMergeConflictReference(
432+
conflict.handle.as_ptr(),
433+
));
434+
}
435+
436+
*new_count = keys.len();
437+
*new_keys = Box::into_raw(keys.into_boxed_slice()) as *mut *mut c_char;
438+
*new_conflicts =
439+
Box::into_raw(conflicts.into_boxed_slice()) as *mut *mut BNAnalysisMergeConflict;
440+
true
441+
})
442+
}
443+
444+
unsafe extern "C" fn cb_free_name(_ctxt: *mut c_void, name: *mut c_char) {
445+
ffi_wrap!("ConflictSplitter::free_name", unsafe {
446+
BnString::free_raw(name);
447+
})
448+
}
449+
450+
unsafe extern "C" fn cb_free_key_list(
451+
_ctxt: *mut c_void,
452+
key_list: *mut *mut c_char,
453+
count: usize,
454+
) {
455+
ffi_wrap!("ConflictSplitter::free_key_list", unsafe {
456+
if key_list.is_null() {
457+
return;
458+
}
459+
460+
let key_list = Box::from_raw(std::ptr::slice_from_raw_parts_mut(key_list, count));
461+
for key in key_list.into_vec() {
462+
BnString::free_raw(key);
463+
}
464+
})
465+
}
466+
467+
unsafe extern "C" fn cb_free_conflict_list(
468+
_ctxt: *mut c_void,
469+
conflict_list: *mut *mut BNAnalysisMergeConflict,
470+
count: usize,
471+
) {
472+
ffi_wrap!("ConflictSplitter::free_conflict_list", unsafe {
473+
if conflict_list.is_null() {
474+
return;
475+
}
476+
477+
let conflict_list = Box::from_raw(std::ptr::slice_from_raw_parts_mut(conflict_list, count));
478+
for conflict in conflict_list.into_vec() {
479+
BNFreeAnalysisMergeConflict(conflict);
480+
}
481+
})
482+
}

0 commit comments

Comments
 (0)