summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Rowe <mark@vector35.com>2025-08-07 22:53:00 -0700
committerMark Rowe <mark@vector35.com>2025-08-13 19:25:43 -0700
commit395a6dc683bcb687ad5fd1c5b1b63205ffd1af79 (patch)
tree735fa7374b5b91c989e10e0155670360e1c5440e
parente13a82d8a007aa3c2492eaf906f92a8e2adf04e6 (diff)
[Rust] Add builder API for creating workflows
`Workflow::new` is replaced by `Workflow::build` that returns a `Builder` type that supports the operations that mutate a workflow, such as registering activities. `Workflow::instance` is replaced by `Workflow::get` to make clear that it is intended to look up an existing workflow. `Workflow::cloned` is introduced to wrap the common pattern of retrieving an existing workflow and cloning it with the same name in order to modify it. `Builder::activity_before` / `Builder::activity_after` are introduced to wrap the common pattern of registering an activity then inserting it before or after a given activity.
-rw-r--r--plugins/svd/src/lib.rs25
-rw-r--r--plugins/warp/src/plugin.rs5
-rw-r--r--plugins/warp/src/plugin/workflow.rs41
-rw-r--r--rust/examples/workflow.rs2
-rw-r--r--rust/src/workflow.rs376
-rw-r--r--rust/tests/workflow.rs55
6 files changed, 279 insertions, 225 deletions
diff --git a/plugins/svd/src/lib.rs b/plugins/svd/src/lib.rs
index b02b1ec2..3a40d2c0 100644
--- a/plugins/svd/src/lib.rs
+++ b/plugins/svd/src/lib.rs
@@ -9,7 +9,6 @@ use binaryninja::logger::Logger;
use binaryninja::workflow::{Activity, AnalysisContext, Workflow};
use log::LevelFilter;
-pub const LOADER_ACTIVITY_NAME: &str = "analysis.svd.loader";
const LOADER_ACTIVITY_CONFIG: &str = r#"{
"name": "analysis.svd.loader",
"title" : "SVD Loader",
@@ -62,7 +61,10 @@ impl Command for LoadSVDFile {
#[allow(non_snake_case)]
#[cfg(not(feature = "demo"))]
pub extern "C" fn CorePluginInit() -> bool {
- plugin_init();
+ if plugin_init().is_err() {
+ log::error!("Failed to initialize SVD plug-in");
+ return false;
+ }
true
}
@@ -70,11 +72,14 @@ pub extern "C" fn CorePluginInit() -> bool {
#[allow(non_snake_case)]
#[cfg(feature = "demo")]
pub extern "C" fn SVDPluginInit() -> bool {
- plugin_init();
+ if plugin_init().is_err() {
+ log::error!("Failed to initialize SVD plug-in");
+ return false;
+ }
true
}
-fn plugin_init() {
+fn plugin_init() -> Result<(), ()> {
Logger::new("SVD").with_level(LevelFilter::Debug).init();
binaryninja::command::register_command(
@@ -113,12 +118,10 @@ fn plugin_init() {
};
// Register new workflow activity to load svd information.
- let old_module_meta_workflow = Workflow::instance("core.module.metaAnalysis");
- let module_meta_workflow = old_module_meta_workflow.clone_to("core.module.metaAnalysis");
let loader_activity = Activity::new_with_action(LOADER_ACTIVITY_CONFIG, loader_activity);
- module_meta_workflow
- .register_activity(&loader_activity)
- .unwrap();
- module_meta_workflow.insert("core.module.loadDebugInfo", [LOADER_ACTIVITY_NAME]);
- module_meta_workflow.register().unwrap();
+ Workflow::cloned("core.module.metaAnalysis")
+ .ok_or(())?
+ .activity_before(&loader_activity, "core.module.loadDebugInfo")?
+ .register()?;
+ Ok(())
}
diff --git a/plugins/warp/src/plugin.rs b/plugins/warp/src/plugin.rs
index 3306801d..ee778062 100644
--- a/plugins/warp/src/plugin.rs
+++ b/plugins/warp/src/plugin.rs
@@ -102,7 +102,10 @@ pub extern "C" fn CorePluginInit() -> bool {
// Register our highlight render layer.
HighlightRenderLayer::register();
- workflow::insert_workflow();
+ if workflow::insert_workflow().is_err() {
+ log::error!("Failed to register WARP workflow");
+ return false;
+ }
// TODO: Make the retrieval of containers wait on this to be done.
// TODO: We could also have a mechanism for lazily loading the files using the chunk header target.
diff --git a/plugins/warp/src/plugin/workflow.rs b/plugins/warp/src/plugin/workflow.rs
index 2a8f6287..8302b29c 100644
--- a/plugins/warp/src/plugin/workflow.rs
+++ b/plugins/warp/src/plugin/workflow.rs
@@ -19,7 +19,6 @@ use warp::r#type::class::function::{Location, RegisterLocation, StackLocation};
use warp::signature::function::{Function, FunctionGUID};
use warp::target::Target;
-pub const APPLY_ACTIVITY_NAME: &str = "analysis.warp.apply";
const APPLY_ACTIVITY_CONFIG: &str = r#"{
"name": "analysis.warp.apply",
"title" : "WARP Apply Matched",
@@ -30,7 +29,6 @@ const APPLY_ACTIVITY_CONFIG: &str = r#"{
}
}"#;
-pub const MATCHER_ACTIVITY_NAME: &str = "analysis.warp.matcher";
const MATCHER_ACTIVITY_CONFIG: &str = r#"{
"name": "analysis.warp.matcher",
"title" : "WARP Matcher",
@@ -189,7 +187,7 @@ pub fn run_matcher(view: &BinaryView) {
view.update_analysis();
}
-pub fn insert_workflow() {
+pub fn insert_workflow() -> Result<(), ()> {
// TODO: Note: because of symbol persistence function symbol is applied in `insert_cached_function_match`.
// TODO: Comments are also applied there, they are "user" like, persisted and make undo actions.
// "Hey look, it's a plier" ~ Josh 2025
@@ -262,26 +260,29 @@ pub fn insert_workflow() {
let guid_activity = Activity::new_with_action(GUID_ACTIVITY_CONFIG, guid_activity);
let apply_activity = Activity::new_with_action(APPLY_ACTIVITY_CONFIG, apply_activity);
- let add_function_activities = |workflow: &Workflow| {
- let new_workflow = workflow.clone_to(&workflow.name());
- new_workflow.register_activity(&guid_activity).unwrap();
- new_workflow.register_activity(&apply_activity).unwrap();
- new_workflow.insert_after("core.function.runFunctionRecognizers", [GUID_ACTIVITY_NAME]);
- new_workflow.insert_after("core.function.generateMediumLevelIL", [APPLY_ACTIVITY_NAME]);
- new_workflow.register().unwrap();
+ let add_function_activities = |workflow: Option<Ref<Workflow>>| -> Result<(), ()> {
+ let Some(workflow) = workflow else {
+ return Ok(());
+ };
+
+ workflow
+ .clone_to(&workflow.name())
+ .activity_after(&guid_activity, "core.function.runFunctionRecognizers")?
+ .activity_after(&apply_activity, "core.function.generateMediumLevelIL")?
+ .register()?;
+ Ok(())
};
- add_function_activities(&Workflow::instance("core.function.metaAnalysis"));
+ add_function_activities(Workflow::get("core.function.metaAnalysis"))?;
// TODO: Remove this once the objectivec workflow is registered on the meta workflow.
- add_function_activities(&Workflow::instance("core.function.objectiveC"));
+ add_function_activities(Workflow::get("core.function.objectiveC"))?;
- let old_module_meta_workflow = Workflow::instance("core.module.metaAnalysis");
- let module_meta_workflow = old_module_meta_workflow.clone_to("core.module.metaAnalysis");
- let matcher_activity = Activity::new_with_action(MATCHER_ACTIVITY_CONFIG, matcher_activity);
// Matcher activity must have core.module.update as subactivity otherwise analysis will sometimes never retrigger.
- module_meta_workflow
- .register_activity(&matcher_activity)
- .unwrap();
- module_meta_workflow.insert("core.module.finishUpdate", [MATCHER_ACTIVITY_NAME]);
- module_meta_workflow.register().unwrap();
+ let matcher_activity = Activity::new_with_action(MATCHER_ACTIVITY_CONFIG, matcher_activity);
+ Workflow::cloned("core.module.metaAnalysis")
+ .ok_or(())?
+ .activity_before(&matcher_activity, "core.module.finishUpdate")?
+ .register()?;
+
+ Ok(())
}
diff --git a/rust/examples/workflow.rs b/rust/examples/workflow.rs
index 49560579..db0bf599 100644
--- a/rust/examples/workflow.rs
+++ b/rust/examples/workflow.rs
@@ -52,7 +52,7 @@ pub fn main() {
binaryninja::headless::Session::new().expect("Failed to initialize session");
println!("Registering workflow...");
- let old_meta_workflow = Workflow::instance("core.function.metaAnalysis");
+ let old_meta_workflow = Workflow::get("core.function.metaAnalysis");
let meta_workflow = old_meta_workflow.clone_to("core.function.metaAnalysis");
let activity = Activity::new_with_action(RUST_ACTIVITY_CONFIG, example_activity);
meta_workflow.register_activity(&activity).unwrap();
diff --git a/rust/src/workflow.rs b/rust/src/workflow.rs
index d937987a..2c03a482 100644
--- a/rust/src/workflow.rs
+++ b/rust/src/workflow.rs
@@ -148,7 +148,6 @@ unsafe impl RefCountable for AnalysisContext {
}
}
-// TODO: We need to hide the JSON here behind a sensible/typed API.
#[repr(transparent)]
pub struct Workflow {
handle: NonNull<BNWorkflow>,
@@ -164,19 +163,22 @@ impl Workflow {
}
/// Create a new unregistered [Workflow] with no activities.
+ /// Returns a [WorkflowBuilder] that can be used to configure and register the new [Workflow].
///
/// To get a copy of an existing registered [Workflow] use [Workflow::clone_to].
- pub fn new(name: &str) -> Ref<Self> {
+ pub fn build(name: &str) -> WorkflowBuilder {
let name = name.to_cstr();
let result = unsafe { BNCreateWorkflow(name.as_ptr()) };
- unsafe { Workflow::ref_from_raw(NonNull::new(result).unwrap()) }
+ WorkflowBuilder {
+ handle: unsafe { Workflow::ref_from_raw(NonNull::new(result).unwrap()) },
+ }
}
/// Make a new unregistered [Workflow], copying all activities and the execution strategy.
+ /// Returns a [WorkflowBuilder] that can be used to configure and register the new [Workflow].
///
/// * `name` - the name for the new [Workflow]
- #[must_use]
- pub fn clone_to(&self, name: &str) -> Ref<Workflow> {
+ pub fn clone_to(&self, name: &str) -> WorkflowBuilder {
self.clone_to_with_root(name, "")
}
@@ -184,11 +186,10 @@ impl Workflow {
///
/// * `name` - the name for the new [Workflow]
/// * `root_activity` - perform the clone operation with this activity as the root
- #[must_use]
- pub fn clone_to_with_root(&self, name: &str, root_activity: &str) -> Ref<Workflow> {
+ pub fn clone_to_with_root(&self, name: &str, root_activity: &str) -> WorkflowBuilder {
let raw_name = name.to_cstr();
let activity = root_activity.to_cstr();
- unsafe {
+ let workflow = unsafe {
Self::ref_from_raw(
NonNull::new(BNWorkflowClone(
self.handle.as_ptr(),
@@ -197,13 +198,23 @@ impl Workflow {
))
.unwrap(),
)
- }
+ };
+ WorkflowBuilder { handle: workflow }
}
- pub fn instance(name: &str) -> Ref<Workflow> {
+ /// Get an existing [Workflow] by name.
+ pub fn get(name: &str) -> Option<Ref<Workflow>> {
+ // TODO: BNWorkflowInstance has get-or-create semantics. There is currently no way to just get.
let name = name.to_cstr();
let result = unsafe { BNWorkflowInstance(name.as_ptr()) };
- unsafe { Workflow::ref_from_raw(NonNull::new(result).unwrap()) }
+ let handle = NonNull::new(result)?;
+ Some(unsafe { Workflow::ref_from_raw(handle) })
+ }
+
+ /// Clone the existing [Workflow] named `name`.
+ /// Returns a [WorkflowBuilder] that can be used to configure and register the new [Workflow].
+ pub fn cloned(name: &str) -> Option<WorkflowBuilder> {
+ Self::get(name).map(|workflow| workflow.clone_to(name))
}
/// List of all registered [Workflow]'s
@@ -220,58 +231,6 @@ impl Workflow {
unsafe { BnString::into_string(result) }
}
- /// Register this [Workflow], making it immutable and available for use.
- pub fn register(&self) -> Result<(), ()> {
- self.register_with_config("")
- }
-
- /// Register this [Workflow], making it immutable and available for use.
- ///
- /// * `configuration` - a JSON representation of the workflow configuration
- pub fn register_with_config(&self, config: &str) -> Result<(), ()> {
- let config = config.to_cstr();
- if unsafe { BNRegisterWorkflow(self.handle.as_ptr(), config.as_ptr()) } {
- Ok(())
- } else {
- Err(())
- }
- }
-
- /// Register an [Activity] with this Workflow.
- ///
- /// * `activity` - the [Activity] to register
- pub fn register_activity(&self, activity: &Activity) -> Result<Ref<Activity>, ()> {
- self.register_activity_with_subactivities::<Vec<String>>(activity, vec![])
- }
-
- /// Register an [Activity] with this Workflow.
- ///
- /// * `activity` - the [Activity] to register
- /// * `subactivities` - the list of Activities to assign
- pub fn register_activity_with_subactivities<I>(
- &self,
- activity: &Activity,
- subactivities: I,
- ) -> Result<Ref<Activity>, ()>
- where
- I: IntoIterator,
- I::Item: IntoCStr,
- {
- let subactivities_raw: Vec<_> = subactivities.into_iter().map(|x| x.to_cstr()).collect();
- let mut subactivities_ptr: Vec<*const _> =
- subactivities_raw.iter().map(|x| x.as_ptr()).collect();
- let result = unsafe {
- BNWorkflowRegisterActivity(
- self.handle.as_ptr(),
- activity.handle.as_ptr(),
- subactivities_ptr.as_mut_ptr(),
- subactivities_ptr.len(),
- )
- };
- let activity_ptr = NonNull::new(result).ok_or(())?;
- unsafe { Ok(Activity::ref_from_raw(activity_ptr)) }
- }
-
/// Determine if an Activity exists in this [Workflow].
pub fn contains(&self, activity: &str) -> bool {
let activity = activity.to_cstr();
@@ -343,11 +302,166 @@ impl Workflow {
unsafe { Array::new(result as *mut *mut c_char, count, ()) }
}
+ /// Generate a FlowGraph object for the current [Workflow] and optionally show it in the UI.
+ ///
+ /// * `activity` - if specified, generate the Flowgraph using `activity` as the root
+ /// * `sequential` - whether to generate a **Composite** or **Sequential** style graph
+ pub fn graph(&self, activity: &str, sequential: Option<bool>) -> Option<Ref<FlowGraph>> {
+ let sequential = sequential.unwrap_or(false);
+ let activity = activity.to_cstr();
+ let graph =
+ unsafe { BNWorkflowGetGraph(self.handle.as_ptr(), activity.as_ptr(), sequential) };
+ if graph.is_null() {
+ return None;
+ }
+ Some(unsafe { FlowGraph::ref_from_raw(graph) })
+ }
+
+ /// Not yet implemented.
+ pub fn show_metrics(&self) {
+ unsafe { BNWorkflowShowReport(self.handle.as_ptr(), c"metrics".as_ptr()) }
+ }
+
+ /// Show the Workflow topology in the UI.
+ pub fn show_topology(&self) {
+ unsafe { BNWorkflowShowReport(self.handle.as_ptr(), c"topology".as_ptr()) }
+ }
+
+ /// Not yet implemented.
+ pub fn show_trace(&self) {
+ unsafe { BNWorkflowShowReport(self.handle.as_ptr(), c"trace".as_ptr()) }
+ }
+}
+
+impl ToOwned for Workflow {
+ type Owned = Ref<Self>;
+
+ fn to_owned(&self) -> Self::Owned {
+ unsafe { RefCountable::inc_ref(self) }
+ }
+}
+
+unsafe impl RefCountable for Workflow {
+ unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
+ Ref::new(Self {
+ handle: NonNull::new(BNNewWorkflowReference(handle.handle.as_ptr()))
+ .expect("valid handle"),
+ })
+ }
+
+ unsafe fn dec_ref(handle: &Self) {
+ BNFreeWorkflow(handle.handle.as_ptr());
+ }
+}
+
+impl CoreArrayProvider for Workflow {
+ type Raw = *mut BNWorkflow;
+ type Context = ();
+ type Wrapped<'a> = Guard<'a, Workflow>;
+}
+
+unsafe impl CoreArrayProviderInner for Workflow {
+ unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
+ BNFreeWorkflowList(raw, count)
+ }
+
+ unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> {
+ Guard::new(
+ Workflow::from_raw(NonNull::new(*raw).expect("valid handle")),
+ context,
+ )
+ }
+}
+
+#[must_use = "Workflow is not registered until `register` is called"]
+pub struct WorkflowBuilder {
+ handle: Ref<Workflow>,
+}
+
+impl WorkflowBuilder {
+ fn raw_handle(&self) -> *mut BNWorkflow {
+ self.handle.handle.as_ptr()
+ }
+
+ /// Register an [Activity] with this Workflow and insert it before the designated position.
+ ///
+ /// * `activity` - the [Activity] to register
+ /// * `sibling` - the activity to insert the new activity before
+ pub fn activity_before(self, activity: &Activity, sibling: &str) -> Result<Self, ()> {
+ self.register_activity(activity)?
+ .insert(sibling, vec![activity.name()])
+ }
+
+ /// Register an [Activity] with this Workflow and insert it in the designated position.
+ ///
+ /// * `activity` - the [Activity] to register
+ /// * `sibling` - the activity to insert the new activity after
+ pub fn activity_after(self, activity: &Activity, sibling: &str) -> Result<Self, ()> {
+ self.register_activity(activity)?
+ .insert_after(sibling, vec![activity.name()])
+ }
+
+ /// Register an [Activity] with this Workflow.
+ ///
+ /// * `activity` - the [Activity] to register
+ pub fn register_activity(self, activity: &Activity) -> Result<Self, ()> {
+ self.register_activity_with_subactivities::<Vec<String>>(activity, vec![])
+ }
+
+ /// Register an [Activity] with this Workflow.
+ ///
+ /// * `activity` - the [Activity] to register
+ /// * `subactivities` - the list of Activities to assign
+ pub fn register_activity_with_subactivities<I>(
+ self,
+ activity: &Activity,
+ subactivities: I,
+ ) -> Result<Self, ()>
+ where
+ I: IntoIterator,
+ I::Item: IntoCStr,
+ {
+ let subactivities_raw: Vec<_> = subactivities.into_iter().map(|x| x.to_cstr()).collect();
+ let mut subactivities_ptr: Vec<*const _> =
+ subactivities_raw.iter().map(|x| x.as_ptr()).collect();
+ let result = unsafe {
+ BNWorkflowRegisterActivity(
+ self.raw_handle(),
+ activity.handle.as_ptr(),
+ subactivities_ptr.as_mut_ptr(),
+ subactivities_ptr.len(),
+ )
+ };
+ let Some(activity_ptr) = NonNull::new(result) else {
+ return Err(());
+ };
+ let _ = unsafe { Activity::ref_from_raw(activity_ptr) };
+ Ok(self)
+ }
+
+ /// Register this [Workflow], making it immutable and available for use.
+ pub fn register(self) -> Result<Ref<Workflow>, ()> {
+ self.register_with_config("")
+ }
+
+ /// Register this [Workflow], making it immutable and available for use.
+ ///
+ /// * `configuration` - a JSON representation of the workflow configuration
+ pub fn register_with_config(self, config: &str) -> Result<Ref<Workflow>, ()> {
+ // TODO: We need to hide the JSON here behind a sensible/typed API.
+ let config = config.to_cstr();
+ if unsafe { BNRegisterWorkflow(self.raw_handle(), config.as_ptr()) } {
+ Ok(self.handle)
+ } else {
+ Err(())
+ }
+ }
+
/// Assign the list of `activities` as the new set of children for the specified `activity`.
///
/// * `activity` - the Activity node to assign children
/// * `activities` - the list of Activities to assign
- pub fn assign_subactivities<I>(&self, activity: &str, activities: I) -> bool
+ pub fn subactivities<I>(self, activity: &str, activities: I) -> Result<Self, ()>
where
I: IntoIterator,
I::Item: IntoCStr,
@@ -355,26 +469,36 @@ impl Workflow {
let activity = activity.to_cstr();
let input_list: Vec<_> = activities.into_iter().map(|a| a.to_cstr()).collect();
let mut input_list_ptr: Vec<*const _> = input_list.iter().map(|x| x.as_ptr()).collect();
- unsafe {
+ let result = unsafe {
BNWorkflowAssignSubactivities(
- self.handle.as_ptr(),
+ self.raw_handle(),
activity.as_ptr(),
input_list_ptr.as_mut_ptr(),
input_list.len(),
)
+ };
+ if result {
+ Ok(self)
+ } else {
+ Err(())
}
}
/// Remove all Activity nodes from this [Workflow].
- pub fn clear(&self) -> bool {
- unsafe { BNWorkflowClear(self.handle.as_ptr()) }
+ pub fn clear(self) -> Result<Self, ()> {
+ let result = unsafe { BNWorkflowClear(self.raw_handle()) };
+ if result {
+ Ok(self)
+ } else {
+ Err(())
+ }
}
/// Insert the list of `activities` before the specified `activity` and at the same level.
///
/// * `activity` - the Activity node for which to insert `activities` before
/// * `activities` - the list of Activities to insert
- pub fn insert<I>(&self, activity: &str, activities: I) -> bool
+ pub fn insert<I>(self, activity: &str, activities: I) -> Result<Self, ()>
where
I: IntoIterator,
I::Item: IntoCStr,
@@ -382,13 +506,18 @@ impl Workflow {
let activity = activity.to_cstr();
let input_list: Vec<_> = activities.into_iter().map(|a| a.to_cstr()).collect();
let mut input_list_ptr: Vec<*const _> = input_list.iter().map(|x| x.as_ptr()).collect();
- unsafe {
+ let result = unsafe {
BNWorkflowInsert(
- self.handle.as_ptr(),
+ self.raw_handle(),
activity.as_ptr(),
input_list_ptr.as_mut_ptr(),
input_list.len(),
)
+ };
+ if result {
+ Ok(self)
+ } else {
+ Err(())
}
}
@@ -396,7 +525,7 @@ impl Workflow {
///
/// * `activity` - the Activity node for which to insert `activities` after
/// * `activities` - the list of Activities to insert
- pub fn insert_after<I>(&self, activity: &str, activities: I) -> bool
+ pub fn insert_after<I>(self, activity: &str, activities: I) -> Result<Self, ()>
where
I: IntoIterator,
I::Item: IntoCStr,
@@ -404,105 +533,46 @@ impl Workflow {
let activity = activity.to_cstr();
let input_list: Vec<_> = activities.into_iter().map(|a| a.to_cstr()).collect();
let mut input_list_ptr: Vec<*const _> = input_list.iter().map(|x| x.as_ptr()).collect();
- unsafe {
+ let result = unsafe {
BNWorkflowInsertAfter(
- self.handle.as_ptr(),
+ self.raw_handle(),
activity.as_ptr(),
input_list_ptr.as_mut_ptr(),
input_list.len(),
)
+ };
+ if result {
+ Ok(self)
+ } else {
+ Err(())
}
}
/// Remove the specified `activity`
- pub fn remove(&self, activity: &str) -> bool {
+ pub fn remove(self, activity: &str) -> Result<Self, ()> {
let activity = activity.to_cstr();
- unsafe { BNWorkflowRemove(self.handle.as_ptr(), activity.as_ptr()) }
+ let result = unsafe { BNWorkflowRemove(self.raw_handle(), activity.as_ptr()) };
+ if result {
+ Ok(self)
+ } else {
+ Err(())
+ }
}
/// Replace the specified `activity`.
///
/// * `activity` - the Activity to replace
/// * `new_activity` - the replacement Activity
- pub fn replace(&self, activity: &str, new_activity: &str) -> bool {
+ pub fn replace(self, activity: &str, new_activity: &str) -> Result<Self, ()> {
let activity = activity.to_cstr();
let new_activity = new_activity.to_cstr();
- unsafe {
- BNWorkflowReplace(
- self.handle.as_ptr(),
- activity.as_ptr(),
- new_activity.as_ptr(),
- )
- }
- }
-
- /// Generate a FlowGraph object for the current [Workflow] and optionally show it in the UI.
- ///
- /// * `activity` - if specified, generate the Flowgraph using `activity` as the root
- /// * `sequential` - whether to generate a **Composite** or **Sequential** style graph
- pub fn graph(&self, activity: &str, sequential: Option<bool>) -> Option<Ref<FlowGraph>> {
- let sequential = sequential.unwrap_or(false);
- let activity = activity.to_cstr();
- let graph =
- unsafe { BNWorkflowGetGraph(self.handle.as_ptr(), activity.as_ptr(), sequential) };
- if graph.is_null() {
- return None;
+ let result = unsafe {
+ BNWorkflowReplace(self.raw_handle(), activity.as_ptr(), new_activity.as_ptr())
+ };
+ if result {
+ Ok(self)
+ } else {
+ Err(())
}
- Some(unsafe { FlowGraph::ref_from_raw(graph) })
- }
-
- /// Not yet implemented.
- pub fn show_metrics(&self) {
- unsafe { BNWorkflowShowReport(self.handle.as_ptr(), c"metrics".as_ptr()) }
- }
-
- /// Show the Workflow topology in the UI.
- pub fn show_topology(&self) {
- unsafe { BNWorkflowShowReport(self.handle.as_ptr(), c"topology".as_ptr()) }
- }
-
- /// Not yet implemented.
- pub fn show_trace(&self) {
- unsafe { BNWorkflowShowReport(self.handle.as_ptr(), c"trace".as_ptr()) }
- }
-}
-
-impl ToOwned for Workflow {
- type Owned = Ref<Self>;
-
- fn to_owned(&self) -> Self::Owned {
- unsafe { RefCountable::inc_ref(self) }
- }
-}
-
-unsafe impl RefCountable for Workflow {
- unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
- Ref::new(Self {
- handle: NonNull::new(BNNewWorkflowReference(handle.handle.as_ptr()))
- .expect("valid handle"),
- })
- }
-
- unsafe fn dec_ref(handle: &Self) {
- BNFreeWorkflow(handle.handle.as_ptr());
- }
-}
-
-impl CoreArrayProvider for Workflow {
- type Raw = *mut BNWorkflow;
- type Context = ();
- type Wrapped<'a> = Guard<'a, Workflow>;
-}
-
-unsafe impl CoreArrayProviderInner for Workflow {
- unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
- BNFreeWorkflowList(raw, count)
- }
-
- unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> {
- Guard::new(
- Workflow::from_raw(NonNull::new(*raw).expect("valid handle")),
- context,
- )
}
}
diff --git a/rust/tests/workflow.rs b/rust/tests/workflow.rs
index bf8818c8..8fbe0240 100644
--- a/rust/tests/workflow.rs
+++ b/rust/tests/workflow.rs
@@ -8,8 +8,11 @@ use binaryninja::workflow::Workflow;
#[test]
fn test_workflow_clone() {
let _session = Session::new().expect("Failed to initialize session");
- let original_workflow = Workflow::new("core.function.baseAnalysis");
- let mut cloned_workflow = original_workflow.clone_to("clone_workflow");
+ let original_workflow = Workflow::get("core.function.metaAnalysis").unwrap();
+ let mut cloned_workflow = original_workflow
+ .clone_to("clone_workflow")
+ .register()
+ .unwrap();
assert_eq!(cloned_workflow.name().as_str(), "clone_workflow");
assert_eq!(
@@ -17,36 +20,30 @@ fn test_workflow_clone() {
original_workflow.configuration()
);
- cloned_workflow = original_workflow.clone_to("");
+ // `clone_to` with an empty name should re-use the original workflow's name.
+ cloned_workflow = original_workflow.clone_to("").register().unwrap();
assert_eq!(
cloned_workflow.name().as_str(),
- "core.function.baseAnalysis"
+ original_workflow.name().as_str()
);
}
#[test]
fn test_workflow_registration() {
let _session = Session::new().expect("Failed to initialize session");
- // Validate NULL workflows cannot be registered
- let workflow = Workflow::new("null");
- assert_eq!(workflow.name().as_str(), "null");
- assert!(!workflow.registered());
- workflow
- .register()
- .expect_err("Re-registration of null is allowed");
// Validate new workflows can be registered
- let test_workflow = Workflow::instance("core.function.baseAnalysis").clone_to("test_workflow");
+ let test_workflow = Workflow::get("core.function.baseAnalysis")
+ .unwrap()
+ .clone_to("test_workflow");
- assert_eq!(test_workflow.name().as_str(), "test_workflow");
- assert!(!test_workflow.registered());
- test_workflow
+ let test_workflow = test_workflow
.register()
.expect("Failed to register workflow");
assert!(test_workflow.registered());
assert_eq!(
test_workflow.size(),
- Workflow::instance("core.function.baseAnalysis").size()
+ Workflow::get("core.function.baseAnalysis").unwrap().size()
);
Workflow::list()
.iter()
@@ -57,32 +54,12 @@ fn test_workflow_registration() {
.iter()
.find(|&w| w == "test_workflow")
.expect("Workflow not found in settings");
-
- // Validate that registered workflows are immutable
- let immutable_workflow = Workflow::instance("test_workflow");
- assert!(!immutable_workflow.clear());
- assert!(immutable_workflow.contains("core.function.advancedAnalysis"));
- assert!(!immutable_workflow.remove("core.function.advancedAnalysis"));
- assert!(!Workflow::instance("core.function.baseAnalysis").clear());
+ assert!(Workflow::get("test_workflow").is_some());
// Validate re-registration of baseAnalysis is not allowed
- let base_workflow_clone = Workflow::instance("core.function.baseAnalysis").clone_to("");
-
- assert_eq!(
- base_workflow_clone.name().as_str(),
- "core.function.baseAnalysis"
- );
- assert!(!base_workflow_clone.registered());
+ let base_workflow_clone = Workflow::cloned("core.function.baseAnalysis").unwrap();
base_workflow_clone
.register()
+ .map(|_| ())
.expect_err("Re-registration of baseAnalysis is allowed");
- assert_eq!(
- base_workflow_clone.size(),
- Workflow::instance("core.function.baseAnalysis").size()
- );
- assert_eq!(
- base_workflow_clone.configuration(),
- Workflow::instance("core.function.baseAnalysis").configuration()
- );
- assert!(!base_workflow_clone.registered());
}