diff options
Diffstat (limited to 'rust/tests')
| -rw-r--r-- | rust/tests/background_task.rs | 49 | ||||
| -rw-r--r-- | rust/tests/binary_reader.rs | 88 | ||||
| -rw-r--r-- | rust/tests/binary_view.rs | 64 | ||||
| -rw-r--r-- | rust/tests/binary_writer.rs | 79 | ||||
| -rw-r--r-- | rust/tests/collaboration.rs | 209 | ||||
| -rw-r--r-- | rust/tests/component.rs | 26 | ||||
| -rw-r--r-- | rust/tests/data_buffer.rs | 88 | ||||
| -rw-r--r-- | rust/tests/demangler.rs | 85 | ||||
| -rw-r--r-- | rust/tests/high_level_il.rs | 41 | ||||
| -rw-r--r-- | rust/tests/initialization.rs | 37 | ||||
| -rw-r--r-- | rust/tests/low_level_il.rs | 230 | ||||
| -rw-r--r-- | rust/tests/main_thread.rs | 29 | ||||
| -rw-r--r-- | rust/tests/medium_level_il.rs | 88 | ||||
| -rw-r--r-- | rust/tests/platform.rs | 32 | ||||
| -rw-r--r-- | rust/tests/project.rs | 350 | ||||
| -rw-r--r-- | rust/tests/type_archive.rs | 31 | ||||
| -rw-r--r-- | rust/tests/type_container.rs | 137 | ||||
| -rw-r--r-- | rust/tests/type_parser.rs | 85 | ||||
| -rw-r--r-- | rust/tests/types.rs | 50 | ||||
| -rw-r--r-- | rust/tests/worker_thread.rs | 45 | ||||
| -rw-r--r-- | rust/tests/workflow.rs | 93 |
21 files changed, 1936 insertions, 0 deletions
diff --git a/rust/tests/background_task.rs b/rust/tests/background_task.rs new file mode 100644 index 00000000..d0c329e3 --- /dev/null +++ b/rust/tests/background_task.rs @@ -0,0 +1,49 @@ +use binaryninja::background_task::*; +use binaryninja::headless::Session; +use rstest::*; + +#[fixture] +#[once] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +#[rstest] +fn test_background_task_registered(_session: &Session) { + let task_progress = "test registered"; + let task = BackgroundTask::new(task_progress, false); + BackgroundTask::running_tasks() + .iter() + .find(|t| t.progress_text().as_str() == task_progress) + .expect("Task not running"); + task.finish(); + let still_running = BackgroundTask::running_tasks() + .iter() + .find(|t| t.progress_text().as_str() == task_progress) + .is_some(); + assert!(!still_running, "Task still running"); +} + +#[rstest] +fn test_background_task_cancellable(_session: &Session) { + let task_progress = "test cancellable"; + let task = BackgroundTask::new(task_progress, false); + BackgroundTask::running_tasks() + .iter() + .find(|t| t.progress_text().as_str() == task_progress) + .expect("Task not running"); + task.cancel(); + assert!(task.is_cancelled()); + task.finish(); +} + +#[rstest] +fn test_background_task_progress(_session: &Session) { + let task = BackgroundTask::new("test progress", false); + let first_progress = task.progress_text().to_string(); + assert_eq!(first_progress, "test progress"); + task.set_progress_text("new progress"); + let second_progress = task.progress_text().to_string(); + assert_eq!(second_progress, "new progress"); + task.finish(); +} diff --git a/rust/tests/binary_reader.rs b/rust/tests/binary_reader.rs new file mode 100644 index 00000000..88017052 --- /dev/null +++ b/rust/tests/binary_reader.rs @@ -0,0 +1,88 @@ +use binaryninja::binary_reader::BinaryReader; +use binaryninja::binary_view::BinaryViewBase; +use binaryninja::headless::Session; +use rstest::*; +use std::io::{Read, Seek, SeekFrom}; +use std::path::PathBuf; + +#[fixture] +#[once] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +#[rstest] +fn test_binary_reader_seek(_session: &Session) { + let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); + let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view"); + let mut reader = BinaryReader::new(&view); + let end_offset = view.len(); + + // Test seeking to a specific position + reader + .seek(SeekFrom::Start(0)) + .expect("Failed to seek to start"); + assert_eq!(reader.offset(), 0, "Reader failed to seek to the start"); + + reader + .seek(SeekFrom::End(0)) + .expect("Failed to seek to end"); + assert_eq!( + reader.offset(), + end_offset, + "Reader failed to seek to the end" + ); + + // Test relative seeking + reader + .seek(SeekFrom::Start(10)) + .expect("Failed to seek to position 10"); + assert_eq!(reader.offset(), 10, "Reader failed to seek to position 10"); + + reader + .seek(SeekFrom::Current(-5)) + .expect("Failed to perform relative seek"); + assert_eq!( + reader.offset(), + 5, + "Reader failed to perform relative seek correctly" + ); +} + +#[rstest] +fn test_binary_reader_read(_session: &Session) { + let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); + let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view"); + let mut reader = BinaryReader::new(&view); + + reader + .seek(SeekFrom::Start(0)) + .expect("Failed to seek to start"); + let mut buffer = [0u8; 4]; + reader + .read_exact(&mut buffer) + .expect("Failed to read 4 bytes"); + assert_eq!( + buffer.len(), + 4, + "Failed to read the correct number of bytes" + ); + + // Validate the buffer. + assert_eq!( + &buffer, + &[0x4c, 0x01, 0x87, 0x00], + "Buffer content does not match the expected bytes" + ); + + // Test attempting to read beyond the end of the file. + reader + .seek(SeekFrom::End(0)) + .expect("Failed to seek to end"); + let mut eof_buffer = [0u8; 1]; + let result = reader.read(&mut eof_buffer); + assert!( + result.is_err(), + "Expected an error when reading past EOF, got success instead" + ); +} diff --git a/rust/tests/binary_view.rs b/rust/tests/binary_view.rs new file mode 100644 index 00000000..65f6b45b --- /dev/null +++ b/rust/tests/binary_view.rs @@ -0,0 +1,64 @@ +use binaryninja::binary_view::{AnalysisState, BinaryViewBase, BinaryViewExt}; +use binaryninja::headless::Session; +use binaryninja::symbol::{SymbolBuilder, SymbolType}; +use rstest::*; +use std::path::PathBuf; + +#[fixture] +#[once] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +#[rstest] +fn test_binary_loading(_session: &Session) { + let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); + let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view"); + assert!(view.has_initial_analysis(), "No initial analysis"); + assert_eq!(view.analysis_progress().state, AnalysisState::IdleState); + assert_eq!(view.file().is_analysis_changed(), true); + assert_eq!(view.file().is_database_backed(), false); +} + +#[rstest] +fn test_binary_saving(_session: &Session) { + let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); + let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view"); + // Verify the contents before we modify. + let original_contents = view.read_vec(0x1560, 4); + assert_eq!(original_contents, [0x00, 0xf1, 0x00, 0x00]); + assert_eq!(view.write(0x1560, &[0xff, 0xff, 0xff, 0xff]), 4); + // Verify that we modified the binary + let modified_contents = view.read_vec(0x1560, 4); + assert_eq!(modified_contents, [0xff, 0xff, 0xff, 0xff]); + // Save the modified file + assert!(view.save_to_path(out_dir.join("atox.obj.new"))); + // Verify that the file exists and is modified. + let new_view = + binaryninja::load(out_dir.join("atox.obj.new")).expect("Failed to load new view"); + assert_eq!(new_view.read_vec(0x1560, 4), [0xff, 0xff, 0xff, 0xff]); +} + +#[rstest] +fn test_binary_saving_database(_session: &Session) { + let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); + let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view"); + // Update a symbol to verify modification + let entry_function = view + .entry_point_function() + .expect("Failed to get entry point function"); + let new_entry_func_symbol = + SymbolBuilder::new(SymbolType::Function, "test", entry_function.start()).create(); + view.define_user_symbol(&new_entry_func_symbol); + // Verify that we modified the binary + assert_eq!(entry_function.symbol().raw_name().as_str(), "test"); + // Save the modified database. + assert!(view.file().create_database(out_dir.join("atox.obj.bndb"))); + // Verify that the file exists and is modified. + let new_view = + binaryninja::load(out_dir.join("atox.obj.bndb")).expect("Failed to load new view"); + let new_entry_function = new_view + .entry_point_function() + .expect("Failed to get entry point function"); + assert_eq!(new_entry_function.symbol().raw_name().as_str(), "test"); +} diff --git a/rust/tests/binary_writer.rs b/rust/tests/binary_writer.rs new file mode 100644 index 00000000..bc9c2165 --- /dev/null +++ b/rust/tests/binary_writer.rs @@ -0,0 +1,79 @@ +use binaryninja::binary_reader::BinaryReader; +use binaryninja::binary_view::BinaryViewBase; +use binaryninja::binary_writer::BinaryWriter; +use binaryninja::headless::Session; +use rstest::*; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::path::PathBuf; + +#[fixture] +#[once] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +#[rstest] +fn test_binary_writer_seek(_session: &Session) { + let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); + let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view"); + let mut writer = BinaryWriter::new(&view); + let end_offset = view.len(); + + // Test seeking to a specific position + writer + .seek(SeekFrom::Start(0)) + .expect("Failed to seek to start"); + assert_eq!(writer.offset(), 0, "Writer failed to seek to the start"); + + writer + .seek(SeekFrom::End(0)) + .expect("Failed to seek to end"); + assert_eq!( + writer.offset(), + end_offset, + "Writer failed to seek to the end" + ); + + // Test relative seeking + writer + .seek(SeekFrom::Start(10)) + .expect("Failed to seek to position 10"); + assert_eq!(writer.offset(), 10, "Writer failed to seek to position 10"); + + writer + .seek(SeekFrom::Current(-5)) + .expect("Failed to perform relative seek"); + assert_eq!( + writer.offset(), + 5, + "Writer failed to perform relative seek correctly" + ); +} + +#[rstest] +fn test_binary_writer_write(_session: &Session) { + let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); + let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view"); + let mut reader = BinaryReader::new(&view); + let mut writer = BinaryWriter::new(&view); + + // Validate bytes are written. + let mut buffer = [5u8; 4]; + let mut verify_buffer = [0u8; 4]; + writer.write(&mut buffer).expect("Failed to write 4 bytes"); + reader + .read_exact(&mut verify_buffer) + .expect("Failed to read 4 bytes"); + assert_eq!(buffer, verify_buffer, "Failed to write the correct bytes"); + + // Test attempting to write beyond the end of the file. + writer + .seek(SeekFrom::End(0)) + .expect("Failed to seek to end"); + let mut eof_buffer = [0u8; 1]; + let result = writer.write(&mut eof_buffer); + assert!( + result.is_err(), + "Expected an error when writing past EOF, got success instead" + ); +} diff --git a/rust/tests/collaboration.rs b/rust/tests/collaboration.rs new file mode 100644 index 00000000..48da859a --- /dev/null +++ b/rust/tests/collaboration.rs @@ -0,0 +1,209 @@ +use binaryninja::binary_view::BinaryViewExt; +use binaryninja::collaboration::{NoNameChangeset, Remote, RemoteFileType, RemoteProject}; +use binaryninja::headless::Session; +use binaryninja::symbol::{SymbolBuilder, SymbolType}; +use rstest::*; +use std::path::PathBuf; + +#[fixture] +#[once] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +// TODO: This cannot run in CI, as headless does not have collaboration, we should gate this. + +fn temp_project_scope<T: Fn(&RemoteProject)>(remote: &Remote, cb: T) { + if !remote.is_connected() { + remote.connect().expect("Failed to connect to remote"); + } + let project = remote + .create_project("Test Project", "Test project for test purposes") + .expect("Failed to create project"); + project.open().expect("Failed to open project"); + assert!(project.is_open(), "Project was not opened"); + // Clear out all the possible entries. This is to insure a clean slate. + let files = project.files().expect("Failed to list files in project"); + for file in &files { + project.delete_file(&file).expect("Failed to delete file"); + } + let folders = project + .folders() + .expect("Failed to list folders in project"); + for folder in &folders { + project + .delete_folder(&folder) + .expect("Failed to delete folder"); + } + // Run task + cb(&project); + // Cleanup. + project.close(); + assert!(!project.is_open(), "Project was not closed"); + remote + .delete_project(&project) + .expect("Failed to delete project"); +} + +#[rstest] +fn test_connection(_session: &Session) { + let remotes = binaryninja::collaboration::known_remotes(); + if remotes.is_empty() { + eprintln!("No known remotes, skipping test..."); + return; + } + let remote = remotes.iter().next().expect("No known remotes!"); + assert!(remote.connect().is_ok(), "Failed to connect to remote"); + remote + .disconnect() + .expect("Failed to disconnect from remote"); + assert!(!remote.is_connected(), "Connection was not disconnected"); +} + +#[rstest] +fn test_project_creation(_session: &Session) { + let remotes = binaryninja::collaboration::known_remotes(); + if remotes.is_empty() { + eprintln!("No known remotes, skipping test..."); + return; + } + let remote = remotes.iter().next().expect("No known remotes!"); + temp_project_scope(&remote, |project| { + // Create the file than verify it by opening and checking contents. + let created_file = project + .create_file( + "test_file", + b"this is my file", + "test_file", + "", + None, + RemoteFileType::UnknownFileType, + ) + .expect("Failed to create file in project"); + let created_file_id = created_file.id(); + assert_eq!(created_file.created_by(), remote.username()); + project + .delete_file(&created_file) + .expect("Failed to delete file"); + assert!( + !project + .get_file_by_id(created_file_id) + .is_ok_and(|f| f.is_some()), + "File was not deleted" + ); + + // Create a folder and verify it was created. + let created_folder = project + .create_folder("test_folder", "test_folder_desc", None) + .unwrap(); + let created_folder_id = created_folder.id(); + assert_eq!(created_folder.name().as_str(), "test_folder"); + assert_eq!(created_folder.description().as_str(), "test_folder_desc"); + + // Create a file in said folder and verify it exists in it. + let created_folder_file = project + .create_file( + "test_folder_file", + b"this is my file", + "test_folder_file", + "", + Some(&created_folder), + RemoteFileType::UnknownFileType, + ) + .expect("Failed to create file in project folder"); + let created_folder_file_id = created_folder_file.id(); + // Verify the file exists in the folder. + let check_folder_file = project + .get_file_by_id(created_folder_file_id) + .expect("Failed to get folder file by id") + .unwrap(); + assert_eq!(check_folder_file.name().as_str(), "test_folder_file"); + assert_eq!( + check_folder_file.folder().unwrap().unwrap().id(), + created_folder_id, + "Folder id does not match" + ); + project + .delete_file(&created_folder_file) + .expect("Failed to delete file"); + + // Verify the folder can be deleted. + project + .delete_folder(&created_folder) + .expect("Failed to delete folder"); + assert!( + !project + .get_folder_by_id(created_folder_id) + .is_ok_and(|f| f.is_some()), + "Folder was not deleted" + ); + }) +} + +#[rstest] +fn test_project_sync(_session: &Session) { + let remotes = binaryninja::collaboration::known_remotes(); + if remotes.is_empty() { + eprintln!("No known remotes, skipping test..."); + return; + } + let remote = remotes.iter().next().expect("No known remotes!"); + temp_project_scope(&remote, |project| { + // Open a view so that we can upload it. + let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); + let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view"); + let view_type = view.view_type(); + // Save the view to local database so that we can upload it + assert!( + view.file().create_database(out_dir.join("atox.obj.bndb")), + "Failed to create local database" + ); + // We should have a single snapshot. + assert_eq!(view.file().database().unwrap().snapshots().len(), 1); + // Update the entry function name. + let entry_function = view + .entry_point_function() + .expect("Failed to get entry point function"); + let new_entry_func_symbol = + SymbolBuilder::new(SymbolType::Function, "test", entry_function.start()).create(); + view.define_user_symbol(&new_entry_func_symbol); + // Verify that we modified the binary + assert_eq!(entry_function.symbol().raw_name().as_str(), "test"); + // Make new snapshot. + assert!(view.file().save_auto_snapshot()); + // We should have two snapshots. + assert_eq!(view.file().database().unwrap().snapshots().len(), 2); + // Upload database and verify its remote file stuff + let remote_file = project + .upload_database(&view.file(), None, NoNameChangeset) + .expect("Failed to upload database"); + assert_eq!(remote_file.name().as_str(), "atox.obj"); + assert_eq!(remote_file.created_by(), remote.username()); + assert_eq!( + remote_file.file_type(), + RemoteFileType::BinaryViewAnalysisFileType + ); + // Delete local database and download remote one to verify changes. + view.file().close(); + drop(view); + // Verify that the remote file exists. + project + .get_file_by_id(remote_file.id()) + .expect("Failed to get remote file by id"); + // Download the remote database with our changes. + let downloaded_file = remote_file + .download_database(out_dir.join("downloaded_atox.obj.bndb")) + .expect("Failed to download database"); + let downloaded_view = downloaded_file + .view_of_type(view_type) + .expect("Failed to open downloaded view"); + // Verify the changes in the entry function. + let entry_function = downloaded_view + .entry_point_function() + .expect("Failed to get entry point function"); + assert_eq!(entry_function.symbol().raw_name().as_str(), "test"); + project + .delete_file(&remote_file) + .expect("Failed to delete file"); + }); +} diff --git a/rust/tests/component.rs b/rust/tests/component.rs new file mode 100644 index 00000000..c57785ce --- /dev/null +++ b/rust/tests/component.rs @@ -0,0 +1,26 @@ +use binaryninja::binary_view::BinaryViewExt; +use binaryninja::component::ComponentBuilder; +use binaryninja::headless::Session; +use rstest::*; +use std::path::PathBuf; + +#[fixture] +#[once] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +#[rstest] +fn test_component_creation(_session: &Session) { + let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); + let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view"); + let component = ComponentBuilder::new(view.clone()).name("test").finalize(); + assert_eq!(component.name().as_str(), "test"); + let root_component = view.root_component().unwrap(); + // We should only have our component. + assert_eq!(root_component.components().len(), 1); + assert!( + root_component.contains_component(&component), + "Component not found in root component" + ); +} diff --git a/rust/tests/data_buffer.rs b/rust/tests/data_buffer.rs new file mode 100644 index 00000000..8480ae71 --- /dev/null +++ b/rust/tests/data_buffer.rs @@ -0,0 +1,88 @@ +use binaryninja::data_buffer::DataBuffer; + +const DUMMY_DATA_0: &[u8] = b"0123456789\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x09\xFF"; +const DUMMY_DATA_1: &[u8] = b"qwertyuiopasdfghjkl\xE7zxcvbnm\x00\x01\x00"; + +#[test] +fn get_slice() { + let data = DataBuffer::new(DUMMY_DATA_0).unwrap(); + let slice = data.get_slice(9, 10).unwrap(); + assert_eq!(slice.get_data(), &DUMMY_DATA_0[9..19]); +} + +#[test] +fn set_len_write() { + let mut data = DataBuffer::default(); + assert_eq!(data.get_data(), &[]); + unsafe { data.set_len(DUMMY_DATA_0.len()) }; + assert_eq!(data.len(), DUMMY_DATA_0.len()); + let mut contents = DUMMY_DATA_0.to_vec(); + data.set_data(&contents); + // modify the orinal contents, to make sure DataBuffer copied the data + // and is not using the original pointer + contents.as_mut_slice().fill(0x55); + drop(contents); + assert_eq!(data.get_data(), DUMMY_DATA_0); + + // make sure the new len truncate the original data + unsafe { data.set_len(13) }; + assert_eq!(data.get_data(), &DUMMY_DATA_0[..13]); + + data.clear(); + assert_eq!(data.get_data(), &[]); +} + +#[test] +fn assign_append() { + let mut dst = DataBuffer::new(DUMMY_DATA_0).unwrap(); + let mut src = DataBuffer::new(DUMMY_DATA_1).unwrap(); + DataBuffer::assign(&mut dst, &src); + + assert_eq!(dst.get_data(), DUMMY_DATA_1); + assert_eq!(src.get_data(), DUMMY_DATA_1); + // overwrite the src, to make sure that src is copied to dst, and not + // moved into it + src.set_data(DUMMY_DATA_0); + assert_eq!(dst.get_data(), DUMMY_DATA_1); + assert_eq!(src.get_data(), DUMMY_DATA_0); + + DataBuffer::append(&mut dst, &src); + let result: Vec<_> = DUMMY_DATA_1.iter().chain(DUMMY_DATA_0).copied().collect(); + assert_eq!(dst.get_data(), &result); + + assert_eq!(src.get_data(), DUMMY_DATA_0); + src.set_data(DUMMY_DATA_1); + assert_eq!(src.get_data(), DUMMY_DATA_1); + assert_eq!(dst.get_data(), &result); +} + +#[test] +fn to_from_formats() { + let data = DataBuffer::new(DUMMY_DATA_0).unwrap(); + let escaped = data.to_escaped_string(false, false); + let unescaped = DataBuffer::from_escaped_string(&escaped); + drop(escaped); + let escaped_part = data.to_escaped_string(true, false); + let unescaped_part = DataBuffer::from_escaped_string(&escaped_part); + drop(escaped_part); + + let part = &DUMMY_DATA_0[0..DUMMY_DATA_0 + .iter() + .position(|x| *x == 0) + .unwrap_or(DUMMY_DATA_0.len())]; + assert_eq!(data.get_data(), DUMMY_DATA_0); + assert_eq!(unescaped.get_data(), DUMMY_DATA_0); + assert_eq!(unescaped_part.get_data(), part); + + let escaped = data.to_base64(); + let unescaped = DataBuffer::from_base64(&escaped); + drop(escaped); + assert_eq!(data.get_data(), DUMMY_DATA_0); + assert_eq!(unescaped.get_data(), DUMMY_DATA_0); + + let compressed = data.zlib_compress(); + let decompressed = compressed.zlib_decompress(); + drop(compressed); + assert_eq!(data.get_data(), DUMMY_DATA_0); + assert_eq!(decompressed.get_data(), DUMMY_DATA_0); +} diff --git a/rust/tests/demangler.rs b/rust/tests/demangler.rs new file mode 100644 index 00000000..da57f67e --- /dev/null +++ b/rust/tests/demangler.rs @@ -0,0 +1,85 @@ +use binaryninja::architecture::CoreArchitecture; +use binaryninja::binary_view::BinaryView; +use binaryninja::demangle::{ + demangle_generic, demangle_gnu3, demangle_llvm, demangle_ms, CustomDemangler, Demangler, +}; +use binaryninja::headless::Session; +use binaryninja::rc::Ref; +use binaryninja::types::{QualifiedName, Type}; +use rstest::*; + +#[fixture] +#[once] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +#[rstest] +fn test_demangler_simple(_session: &Session) { + let placeholder_arch = CoreArchitecture::by_name("x86").expect("x86 exists"); + // Example LLVM-style mangled name + let llvm_mangled = "_Z3fooi"; // "foo(int)" in LLVM mangling + let llvm_demangled = demangle_llvm(llvm_mangled, true).unwrap(); + assert_eq!(llvm_demangled, "foo(int)".into()); + + // Example GNU-style mangled name + let gnu_mangled = "_Z3bari"; // "bar(int)" in GNU mangling + let (gnu_demangled_name, gnu_demangled_type) = + demangle_gnu3(&placeholder_arch, gnu_mangled, true).unwrap(); + assert_eq!(gnu_demangled_name, "bar".into()); + // TODO: We check the type display because other means include things such as confidence which is hard to get 1:1 + assert_eq!( + gnu_demangled_type.unwrap().to_string(), + "int32_t(int32_t)".to_string() + ); + + // Example MSVC-style mangled name + let msvc_mangled = "?baz@@YAHH@Z"; // "int __cdecl baz(int)" in MSVC mangling + let (msvc_demangled_name, msvc_demangled_type) = + demangle_ms(&placeholder_arch, msvc_mangled, true).unwrap(); + assert_eq!(msvc_demangled_name, "baz".into()); + // TODO: We check the type display because other means include things such as confidence which is hard to get 1:1 + assert_eq!( + msvc_demangled_type.unwrap().to_string(), + "int32_t __cdecl(int32_t)".to_string() + ); +} + +#[rstest] +fn test_custom_demangler(_session: &Session) { + struct TestDemangler; + + impl CustomDemangler for TestDemangler { + fn is_mangled_string(&self, name: &str) -> bool { + name == "test_name" || name == "test_name2" + } + + fn demangle( + &self, + _arch: &CoreArchitecture, + name: &str, + _view: Option<Ref<BinaryView>>, + ) -> Option<(QualifiedName, Option<Ref<Type>>)> { + match name { + "test_name" => Some((QualifiedName::from(vec!["test_name"]), Some(Type::bool()))), + "test_name2" => Some((QualifiedName::from(vec!["test_name2", "aaa"]), None)), + _ => None, + } + } + } + + Demangler::register("Test", TestDemangler); + + let placeholder_arch = CoreArchitecture::by_name("x86_64").expect("x86_64 exists"); + + let demangled = demangle_generic(&placeholder_arch, "test_name", None, true).unwrap(); + assert_eq!( + demangled, + (QualifiedName::from(vec!["test_name"]), Some(Type::bool())) + ); + let demangled2 = demangle_generic(&placeholder_arch, "test_name2", None, true).unwrap(); + assert_eq!( + demangled2, + (QualifiedName::from(vec!["test_name2", "aaa"]), None) + ); +} diff --git a/rust/tests/high_level_il.rs b/rust/tests/high_level_il.rs new file mode 100644 index 00000000..3a2b7209 --- /dev/null +++ b/rust/tests/high_level_il.rs @@ -0,0 +1,41 @@ +use binaryninja::binary_view::BinaryViewExt; +use binaryninja::headless::Session; +use binaryninja::high_level_il::{HighLevelILInstructionKind, HighLevelInstructionIndex}; +use rstest::*; +use std::path::PathBuf; + +#[fixture] +#[once] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +#[rstest] +fn test_hlil_info(_session: &Session) { + let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); + let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view"); + + let entry_function = view.entry_point_function().unwrap(); + let hlil_function = entry_function.high_level_il(false).unwrap(); + let hlil_basic_blocks = hlil_function.basic_blocks(); + let mut hlil_basic_block_iter = hlil_basic_blocks.iter(); + let first_basic_block = hlil_basic_block_iter.next().unwrap(); + let mut hlil_instr_iter = first_basic_block.iter(); + + // NOTE: I guess this is right? + // 00025f10 (HLIL_BLOCK + // 00025f22 (HLIL_RET return ( + // 00025f22 HLIL_CALL (HLIL_CONST_PTR.d __crt_interlocked_read_32)((HLIL_VAR.d arg1)))) + // 00025f10 ) + let instr_0 = hlil_instr_iter.next().unwrap(); + assert_eq!(instr_0.expr_index, HighLevelInstructionIndex(5)); + assert_eq!(instr_0.address, 0x00025f22); + println!("{:?}", instr_0.kind); + match instr_0.kind { + HighLevelILInstructionKind::Ret(op) => { + assert_eq!(op.first_src, 4); + assert_eq!(op.num_srcs, 1); + } + _ => panic!("Expected Ret"), + } +} diff --git a/rust/tests/initialization.rs b/rust/tests/initialization.rs new file mode 100644 index 00000000..35de73ee --- /dev/null +++ b/rust/tests/initialization.rs @@ -0,0 +1,37 @@ +use binaryninja::binary_view::BinaryView; +use binaryninja::enterprise::release_license; +use binaryninja::file_metadata::FileMetadata; +use binaryninja::headless::{ + init, init_with_opts, shutdown, InitializationError, InitializationOptions, +}; +use binaryninja::set_license; +use rstest::rstest; + +// NOTE: Do not add any tests here, behavior will change (i.e. a failure might pass) if we initialize +// NOTE: The core in another test. The only test here should be `test_license_validation`. + +#[rstest] +fn test_license_validation() { + // Release floating license if we already have one, otherwise the failure will succeed. + release_license(); + // Make sure we properly report invalid license. + let options = InitializationOptions::default() + .with_checkout_license(false) + .with_license("blah blag"); + match init_with_opts(options) { + Ok(_) => panic!("Expected license validation to fail, but it succeeded!"), + Err(InitializationError::InvalidLicense) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + } + // Reset the license so that it actually can validate license. + set_license::<String>(None); + // Actually make sure we can initialize. + init().expect("Failed to initialize, make sure you have a license before trying to run tests!"); + // Open an empty binary and make sure it succeeds. + let view = BinaryView::from_data(&FileMetadata::new(), &[]); + assert!( + view.is_ok(), + "Failed to open empty binary, core initialization failed!" + ); + shutdown(); +} diff --git a/rust/tests/low_level_il.rs b/rust/tests/low_level_il.rs new file mode 100644 index 00000000..bbd31fc9 --- /dev/null +++ b/rust/tests/low_level_il.rs @@ -0,0 +1,230 @@ +use binaryninja::architecture::Register; +use binaryninja::binary_view::BinaryViewExt; +use binaryninja::headless::Session; +use binaryninja::low_level_il::expression::{ + ExpressionHandler, LowLevelExpressionIndex, LowLevelILExpressionKind, +}; +use binaryninja::low_level_il::instruction::{ + InstructionHandler, LowLevelILInstructionKind, LowLevelInstructionIndex, +}; +use binaryninja::low_level_il::{LowLevelILRegister, VisitorAction}; +use rstest::*; +use std::path::PathBuf; + +#[fixture] +#[once] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +#[rstest] +fn test_llil_info(_session: &Session) { + let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); + let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view"); + + let entry_function = view.entry_point_function().unwrap(); + let llil_function = entry_function.low_level_il().unwrap(); + let llil_basic_blocks = llil_function.basic_blocks(); + let mut llil_basic_block_iter = llil_basic_blocks.iter(); + let first_basic_block = llil_basic_block_iter.next().unwrap(); + let mut llil_instr_iter = first_basic_block.iter(); + + // 0 @ 00025f10 (LLIL_SET_REG.d edi = (LLIL_REG.d edi)) + let instr_0 = llil_instr_iter.next().unwrap(); + assert_eq!(instr_0.index, LowLevelInstructionIndex(0)); + assert_eq!(instr_0.address(), 0x00025f10); + println!("{:?}", instr_0); + println!("{:?}", instr_0.kind()); + match instr_0.kind() { + LowLevelILInstructionKind::SetReg(op) => { + assert_eq!(op.size(), 4); + match op.dest_reg() { + LowLevelILRegister::ArchReg(reg) => assert_eq!(reg.name(), "edi"), + _ => panic!("Expected Register::ArchReg"), + } + assert_eq!(op.source_expr().index, LowLevelExpressionIndex(0)); + } + _ => panic!("Expected SetReg"), + } + // 1 @ 00025f12 (LLIL_PUSH.d push((LLIL_REG.d ebp))) + let instr_1 = llil_instr_iter.next().unwrap(); + assert_eq!(instr_1.index, LowLevelInstructionIndex(1)); + assert_eq!(instr_1.address(), 0x00025f12); + println!("{:?}", instr_1.kind()); + match instr_1.kind() { + LowLevelILInstructionKind::Push(op) => { + assert_eq!(op.size(), 4); + assert_eq!(op.operand().index, LowLevelExpressionIndex(2)); + println!("{:?}", op.operand().kind()); + match op.operand().kind() { + LowLevelILExpressionKind::Reg(op) => { + assert_eq!(op.size(), 4); + match op.source_reg() { + LowLevelILRegister::ArchReg(reg) => assert_eq!(reg.name(), "ebp"), + _ => panic!("Expected Register::ArchReg"), + } + } + _ => panic!("Expected Reg"), + } + } + _ => panic!("Expected Push"), + } + // 2 @ 00025f13 (LLIL_SET_REG.d ebp = (LLIL_REG.d esp) {__saved_ebp}) + let instr_2 = llil_instr_iter.next().unwrap(); + assert_eq!(instr_2.index, LowLevelInstructionIndex(2)); + assert_eq!(instr_2.address(), 0x00025f13); + println!("{:?}", instr_2.kind()); + match instr_2.kind() { + LowLevelILInstructionKind::SetReg(op) => { + assert_eq!(op.size(), 4); + match op.dest_reg() { + LowLevelILRegister::ArchReg(reg) => assert_eq!(reg.name(), "ebp"), + _ => panic!("Expected Register::ArchReg"), + } + assert_eq!(op.source_expr().index, LowLevelExpressionIndex(4)); + } + _ => panic!("Expected SetReg"), + } + // 3 @ 00025f15 (LLIL_SET_REG.d eax = (LLIL_LOAD.d [(LLIL_ADD.d (LLIL_REG.d ebp) + (LLIL_CONST.d 8)) {arg1}].d)) + let instr_3 = llil_instr_iter.next().unwrap(); + assert_eq!(instr_3.index, LowLevelInstructionIndex(3)); + assert_eq!(instr_3.address(), 0x00025f15); + println!("{:?}", instr_3.kind()); + match instr_3.kind() { + LowLevelILInstructionKind::SetReg(op) => { + assert_eq!(op.size(), 4); + match op.dest_reg() { + LowLevelILRegister::ArchReg(reg) => assert_eq!(reg.name(), "eax"), + _ => panic!("Expected Register::ArchReg"), + } + assert_eq!(op.source_expr().index, LowLevelExpressionIndex(9)); + } + _ => panic!("Expected SetReg"), + } + // 4 @ 00025f18 (LLIL_PUSH.d push((LLIL_REG.d eax))) + let instr_4 = llil_instr_iter.next().unwrap(); + assert_eq!(instr_4.index, LowLevelInstructionIndex(4)); + assert_eq!(instr_4.address(), 0x00025f18); + println!("{:?}", instr_4.kind()); + match instr_4.kind() { + LowLevelILInstructionKind::Push(op) => { + assert_eq!(op.size(), 4); + assert_eq!(op.operand().index, LowLevelExpressionIndex(11)); + } + _ => panic!("Expected Push"), + } + // 5 @ 00025f19 (LLIL_CALL call((LLIL_CONST_PTR.d __crt_interlocked_read_32))) + let instr_5 = llil_instr_iter.next().unwrap(); + assert_eq!(instr_5.index, LowLevelInstructionIndex(5)); + assert_eq!(instr_5.address(), 0x00025f19); + println!("{:?}", instr_5.kind()); + match instr_5.kind() { + LowLevelILInstructionKind::Call(op) => { + assert_eq!(op.target().index, LowLevelExpressionIndex(13)); + } + _ => panic!("Expected Call"), + } + // 6 @ 00025f1e (LLIL_SET_REG.d esp = (LLIL_ADD.d (LLIL_REG.d esp) + (LLIL_CONST.d 4))) + let instr_6 = llil_instr_iter.next().unwrap(); + assert_eq!(instr_6.index, LowLevelInstructionIndex(6)); + assert_eq!(instr_6.address(), 0x00025f1e); + println!("{:?}", instr_6.kind()); + match instr_6.kind() { + LowLevelILInstructionKind::SetReg(op) => { + assert_eq!(op.size(), 4); + match op.dest_reg() { + LowLevelILRegister::ArchReg(reg) => assert_eq!(reg.name(), "esp"), + _ => panic!("Expected Register::ArchReg"), + } + assert_eq!(op.source_expr().index, LowLevelExpressionIndex(17)); + } + _ => panic!("Expected SetReg"), + } + // 7 @ 00025f21 (LLIL_SET_REG.d ebp = (LLIL_POP.d pop)) + let instr_7 = llil_instr_iter.next().unwrap(); + assert_eq!(instr_7.index, LowLevelInstructionIndex(7)); + assert_eq!(instr_7.address(), 0x00025f21); + println!("{:?}", instr_7.kind()); + match instr_7.kind() { + LowLevelILInstructionKind::SetReg(op) => { + assert_eq!(op.size(), 4); + match op.dest_reg() { + LowLevelILRegister::ArchReg(reg) => assert_eq!(reg.name(), "ebp"), + _ => panic!("Expected Register::ArchReg"), + } + assert_eq!(op.source_expr().index, LowLevelExpressionIndex(19)); + } + _ => panic!("Expected SetReg"), + } + // 8 @ 00025f22 (LLIL_RET <return> jump((LLIL_POP.d pop))) + let instr_8 = llil_instr_iter.next().unwrap(); + assert_eq!(instr_8.index, LowLevelInstructionIndex(8)); + assert_eq!(instr_8.address(), 0x00025f22); + println!("{:?}", instr_8.kind()); + match instr_8.kind() { + LowLevelILInstructionKind::Ret(op) => { + assert_eq!(op.target().index, LowLevelExpressionIndex(21)); + } + _ => panic!("Expected Ret"), + } +} + +#[rstest] +fn test_llil_visitor(_session: &Session) { + let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); + let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view"); + let platform = view.default_platform().unwrap(); + + // Sample function: __crt_strtox::c_string_character_source<char>::validate + let sample_function = view.function_at(&platform, 0x2bd80).unwrap(); + let llil_function = sample_function.low_level_il().unwrap(); + let llil_basic_blocks = llil_function.basic_blocks(); + let llil_basic_block_iter = llil_basic_blocks.iter(); + + let mut basic_blocks_visited = 0; + let mut instructions_visited: Vec<LowLevelInstructionIndex> = vec![]; + let mut expressions_visited: Vec<LowLevelExpressionIndex> = vec![]; + for basic_block in llil_basic_block_iter { + basic_blocks_visited += 1; + for instr in basic_block.iter() { + instructions_visited.push(instr.index); + expressions_visited.push(instr.expr_idx()); + instr.visit_tree(&mut |expr| { + expressions_visited.push(expr.index); + VisitorAction::Descend + }); + } + } + + assert_eq!(basic_blocks_visited, 10); + // This is a flag instruction removed in LLIL. + instructions_visited.push(LowLevelInstructionIndex(38)); + for instr_idx in 0..41 { + if instructions_visited + .iter() + .find(|x| x.0 == instr_idx) + .is_none() + { + panic!("Instruction with index {:?} not visited", instr_idx); + }; + } + // These are NOP's + expressions_visited.push(LowLevelExpressionIndex(24)); + expressions_visited.push(LowLevelExpressionIndex(54)); + expressions_visited.push(LowLevelExpressionIndex(62)); + expressions_visited.push(LowLevelExpressionIndex(87)); + // These are some flag things + expressions_visited.push(LowLevelExpressionIndex(114)); + expressions_visited.push(LowLevelExpressionIndex(115)); + expressions_visited.push(LowLevelExpressionIndex(116)); + expressions_visited.push(LowLevelExpressionIndex(121)); + for expr_idx in 0..127 { + if expressions_visited + .iter() + .find(|x| x.0 == expr_idx) + .is_none() + { + panic!("Expression with index {:?} not visited", expr_idx); + }; + } +} diff --git a/rust/tests/main_thread.rs b/rust/tests/main_thread.rs new file mode 100644 index 00000000..f74414d5 --- /dev/null +++ b/rust/tests/main_thread.rs @@ -0,0 +1,29 @@ +use binaryninja::headless::Session; +use rstest::*; + +// TODO: Add a test for MainThreadHandler + +#[fixture] +#[once] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +#[rstest] +fn test_not_main_thread(_session: &Session) { + // We should never be the main thread. + assert!(!binaryninja::is_main_thread()) +} + +#[rstest] +fn test_main_thread_different(_session: &Session) { + let calling_thread = std::thread::current(); + binaryninja::main_thread::execute_on_main_thread_and_wait(move || { + let main_thread = std::thread::current(); + assert_ne!( + calling_thread.id(), + main_thread.id(), + "Expected calling thread to be the different from the main thread" + ) + }); +} diff --git a/rust/tests/medium_level_il.rs b/rust/tests/medium_level_il.rs new file mode 100644 index 00000000..7d38797b --- /dev/null +++ b/rust/tests/medium_level_il.rs @@ -0,0 +1,88 @@ +use binaryninja::binary_view::BinaryViewExt; +use binaryninja::headless::Session; +use binaryninja::medium_level_il::{MediumLevelILInstructionKind, MediumLevelInstructionIndex}; +use rstest::*; +use std::path::PathBuf; + +#[fixture] +#[once] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +#[rstest] +fn test_mlil_info(_session: &Session) { + let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); + let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view"); + + let entry_function = view.entry_point_function().unwrap(); + let mlil_function = entry_function.medium_level_il().unwrap(); + let mlil_basic_blocks = mlil_function.basic_blocks(); + let mut mlil_basic_block_iter = mlil_basic_blocks.iter(); + let first_basic_block = mlil_basic_block_iter.next().unwrap(); + let mut mlil_instr_iter = first_basic_block.iter(); + + // 0 @ 00025f10 (MLIL_SET_VAR.d edi_1 = (MLIL_VAR.d edi)) + let instr_0 = mlil_instr_iter.next().unwrap(); + assert_eq!(instr_0.expr_index, MediumLevelInstructionIndex(1)); + assert_eq!(instr_0.address, 0x00025f10); + println!("{:?}", instr_0.kind); + match instr_0.kind { + MediumLevelILInstructionKind::SetVar(op) => { + assert_eq!(op.dest.index, 524288); + assert_eq!(op.src, 0); + } + _ => panic!("Expected SetVar"), + } + // 1 @ 00025f15 (MLIL_SET_VAR.d eax = (MLIL_VAR.d arg1)) + let instr_1 = mlil_instr_iter.next().unwrap(); + assert_eq!(instr_1.expr_index, MediumLevelInstructionIndex(3)); + assert_eq!(instr_1.address, 0x00025f15); + println!("{:?}", instr_1.kind); + match instr_1.kind { + MediumLevelILInstructionKind::SetVar(op) => { + assert_eq!(op.dest.index, 5); + assert_eq!(op.src, 2); + } + _ => panic!("Expected SetVar"), + } + // 2 @ 00025f18 (MLIL_SET_VAR.d var_8 = (MLIL_VAR.d eax)) + let instr_2 = mlil_instr_iter.next().unwrap(); + assert_eq!(instr_2.expr_index, MediumLevelInstructionIndex(5)); + assert_eq!(instr_2.address, 0x00025f18); + println!("{:?}", instr_2.kind); + match instr_2.kind { + MediumLevelILInstructionKind::SetVar(op) => { + assert_eq!(op.dest.index, 8); + assert_eq!(op.src, 4); + } + _ => panic!("Expected SetVar"), + } + // 3 @ 00025f19 (MLIL_CALL eax_1 = (MLIL_CONST_PTR.d __crt_interlocked_read_32)((MLIL_VAR.d var_8))) + let instr_3 = mlil_instr_iter.next().unwrap(); + assert_eq!(instr_3.expr_index, MediumLevelInstructionIndex(10)); + assert_eq!(instr_3.address, 0x00025f19); + println!("{:?}", instr_3.kind); + match instr_3.kind { + MediumLevelILInstructionKind::Call(op) => { + assert_eq!(op.first_output, 8); + assert_eq!(op.num_outputs, 1); + assert_eq!(op.dest, 7); + assert_eq!(op.first_param, 9); + assert_eq!(op.num_params, 1); + } + _ => panic!("Expected Call"), + } + // 4 @ 00025f22 (MLIL_RET return (MLIL_VAR.d eax_1)) + let instr_4 = mlil_instr_iter.next().unwrap(); + assert_eq!(instr_4.expr_index, MediumLevelInstructionIndex(13)); + assert_eq!(instr_4.address, 0x00025f22); + println!("{:?}", instr_4.kind); + match instr_4.kind { + MediumLevelILInstructionKind::Ret(op) => { + assert_eq!(op.first_operand, 12); + assert_eq!(op.num_operands, 1); + } + _ => panic!("Expected Ret"), + } +} diff --git a/rust/tests/platform.rs b/rust/tests/platform.rs new file mode 100644 index 00000000..c6c861fb --- /dev/null +++ b/rust/tests/platform.rs @@ -0,0 +1,32 @@ +use binaryninja::headless::Session; +use binaryninja::platform::Platform; +use rstest::*; + +#[fixture] +#[once] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +#[rstest] +fn test_platform_lifetime(_session: &Session) { + let platform_0 = Platform::by_name("windows-x86_64").expect("windows-x86_64 exists"); + let platform_types_0 = platform_0.types(); + let platform_1 = Platform::by_name("windows-x86_64").expect("windows-x86_64 exists"); + let platform_types_1 = platform_1.types(); + assert_eq!(platform_types_0.len(), platform_types_1.len()); +} + +#[rstest] +fn test_platform_types(_session: &Session) { + let platform = Platform::by_name("windows-x86_64").expect("windows-x86_64 exists"); + let platform_types = platform.types(); + // windows-x86_64 has a few thousand, not zero. + assert_ne!(platform_types.len(), 0); +} + +#[rstest] +fn test_platform_calling_conventions(_session: &Session) { + let platform = Platform::by_name("windows-x86_64").expect("windows-x86_64 exists"); + assert_eq!(platform.calling_conventions().len(), 1); +} diff --git a/rust/tests/project.rs b/rust/tests/project.rs new file mode 100644 index 00000000..cc41c11e --- /dev/null +++ b/rust/tests/project.rs @@ -0,0 +1,350 @@ +use binaryninja::headless::Session; +use binaryninja::metadata::Metadata; +use binaryninja::project::Project; +use binaryninja::rc::Ref; +use rstest::*; +use std::time::SystemTime; + +// TODO: We should use tempdir to manage the project directory. + +#[fixture] +#[once] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +fn unique_project(name: &str) -> String { + format!("{}/{}", std::env::temp_dir().to_str().unwrap(), name) +} + +#[rstest] +fn create_delete_empty(_session: &Session) { + use std::fs::canonicalize; + + let project_name = "create_delete_empty_project"; + let project_path = unique_project(project_name); + // create the project + let project = Project::create(&project_path, project_name).expect("Failed to create project"); + project.open().unwrap(); + assert!(project.is_open()); + + // check project data + let project_path_received = project.path(); + assert_eq!( + canonicalize(&project_path).unwrap(), + canonicalize(project_path_received.to_string()).unwrap() + ); + let project_name_received = project.name(); + assert_eq!(project_name, project_name_received.as_str()); + + // close the project + project.close().unwrap(); + assert!(!project.is_open()); + drop(project); + + // delete the project + std::fs::remove_dir_all(project_path).unwrap(); +} + +#[rstest] +fn create_close_open_close(_session: &Session) { + let project_name = "create_close_open_close"; + let project_path = unique_project(project_name); + // create the project + let project = Project::create(&project_path, project_name).expect("Failed to create project"); + project.open().unwrap(); + + // get the project id + let id = project.id(); + + // close the project + project.close().unwrap(); + drop(project); + + let project = Project::open_project(&project_path).expect("Failed to open project"); + // assert same id + let new_id = project.id(); + assert_eq!(id, new_id); + + // close the project + project.close().unwrap(); + drop(project); + + // delete the project + std::fs::remove_dir_all(project_path).unwrap(); +} + +#[rstest] +fn modify_project(_session: &Session) { + let project_name = "modify_project_project"; + let project_path = unique_project(project_name); + // create the project + let project = Project::create(&project_path, project_name).expect("Failed to create project"); + project.open().unwrap(); + + // get project id + let id = project.id(); + + // create data and verify that data was created + let data_1: Ref<Metadata> = "data1".into(); + let data_2: Ref<Metadata> = "data2".into(); + assert!(project.store_metadata("key", data_1.as_ref())); + assert_eq!( + data_1.get_string().unwrap(), + project.query_metadata("key").get_string().unwrap() + ); + project.remove_metadata("key"); + assert!(project.store_metadata("key", data_2.as_ref())); + assert_eq!( + data_2.get_string().unwrap(), + project.query_metadata("key").get_string().unwrap() + ); + + // create file that will be imported to the project + let tmp_folder_1_name = format!( + "tmp_folder_{}", + SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() + ); + let tmp_folder_2_name = format!("{tmp_folder_1_name }_2"); + let tmp_folder_1 = format!( + "{}/{tmp_folder_1_name}", + std::env::temp_dir().to_str().unwrap() + ); + let tmp_folder_2 = format!( + "{}/{tmp_folder_2_name}", + std::env::temp_dir().to_str().unwrap() + ); + std::fs::create_dir(&tmp_folder_1).unwrap(); + std::fs::create_dir(&tmp_folder_2).unwrap(); + let input_file_1 = format!("{tmp_folder_2}/input_1"); + let input_file_2 = format!("{tmp_folder_2}/input_2"); + let input_file_1_data = b"input_1_data"; + let input_file_2_data = b"input_1_data"; + std::fs::write(&input_file_1, input_file_1_data).unwrap(); + std::fs::write(&input_file_2, input_file_2_data).unwrap(); + + // create and delete folders + let folder_1_desc = "desc_folder_1"; + let folder_1 = project + .create_folder(None, "folder_1", folder_1_desc) + .unwrap(); + let folder_2_desc = "AAAAA"; + let folder_2_id = "1717416787371"; + let folder_2 = unsafe { + project + .create_folder_unsafe(Some(&folder_1), "folder_2", folder_2_desc, folder_2_id) + .unwrap() + }; + let folder_3_desc = ""; // TODO "çàáÁÀ"; + let folder_3 = project + .create_folder_from_path(&tmp_folder_1, None, folder_3_desc) + .unwrap(); + let folder_4_desc = ""; + let _folder_4 = project + .create_folder_from_path_with_progress( + &tmp_folder_2, + Some(&folder_3), + folder_4_desc, + |_, _| true, + ) + .unwrap(); + let folder_5 = project + .create_folder(None, "deleted_folder", folder_4_desc) + .unwrap(); + + assert_eq!(project.folders().unwrap().len(), 5); + let last_folder = project.folder_by_id(folder_5.id()).unwrap(); + project.delete_folder(&last_folder).unwrap(); + assert_eq!(project.folders().unwrap().len(), 4); + drop(folder_5); + + // create, import and delete file + let file_1_data = b"data_1"; + let file_1_desc = "desc_file_1"; + let _file_1 = project + .create_file(file_1_data, None, "file_1", file_1_desc) + .unwrap(); + let file_2_data = b"data_2"; + let file_2_desc = "my desc"; + let file_2_id = "12334545"; + let _file_2 = unsafe { + project.create_file_unsafe( + file_2_data, + Some(&folder_2), + "file_2", + file_2_desc, + file_2_id, + SystemTime::UNIX_EPOCH, + ) + } + .unwrap(); + let file_3_data = b"data\x023"; + let file_3_desc = "!"; + let _file_3 = project + .create_file_with_progress( + file_3_data, + Some(&folder_1), + "file_3", + file_3_desc, + |_, _| true, + ) + .unwrap(); + let file_4_time = SystemTime::now(); + let file_4_data = b"data_4\x00_4"; + let file_4_desc = ""; + let file_4_id = "123123123"; + let _file_4 = unsafe { + project.create_file_unsafe_with_progress( + file_4_data, + Some(&folder_3), + "file_4", + file_4_desc, + file_4_id, + file_4_time, + |_, _| true, + ) + } + .unwrap(); + let file_5_desc = "desc"; + let _file_5 = project + .create_file_from_path(&input_file_1, None, "file_5", file_5_desc) + .unwrap(); + let file_6_time = SystemTime::now(); + let file_6_desc = "de"; + let file_6_id = "90218347"; + let _file_6 = unsafe { + project.create_file_from_path_unsafe( + &input_file_2, + Some(&folder_3), + "file_6", + file_6_desc, + file_6_id, + file_6_time, + ) + } + .unwrap(); + let file_7 = project + .create_file_from_path_with_progress( + &input_file_2, + Some(&folder_2), + "file_7", + "no", + |_, _| true, + ) + .unwrap(); + let file_8 = unsafe { + project.create_file_from_path_unsafe_with_progress( + &input_file_1, + None, + "file_7", + "no", + "92736528", + SystemTime::now(), + |_, _| true, + ) + } + .unwrap(); + + assert_eq!(project.files().len(), 10); + let file_a = project.file_by_id(file_8.id()).unwrap(); + let file_b = project.file_by_path(file_7.path_on_disk()).unwrap(); + project.delete_file(&file_a); + project.delete_file(&file_b); + assert_eq!(project.files().len(), 8); + drop(file_8); + drop(file_7); + + project.set_name("project_name"); + project.set_description("project_description"); + + // close the project + project.close().unwrap(); + drop(project); + drop(folder_1); + drop(folder_2); + drop(folder_3); + + // reopen the project and verify the information store on it + let project = Project::open_project(&project_path).expect("Failed to open project"); + + // assert same id + assert_eq!(id, project.id()); + + // verify metadata + assert_eq!( + data_2.get_string().unwrap(), + project.query_metadata("key").get_string().unwrap() + ); + + // check folders + let folders = [ + ("folder_1", None), + ("folder_2", Some(folder_2_id)), + (&tmp_folder_1_name, None), + (&tmp_folder_2_name, None), + ]; + for folder in project.folders().unwrap().iter() { + let found = folders + .iter() + .find(|f| folder.name().as_str() == f.0) + .unwrap(); + if let Some(id) = found.1 { + assert_eq!(folder.id().as_str(), id); + } + } + + // check files + let files = [ + ("file_1", &file_1_data[..], None, None), + ("file_2", &file_2_data[..], Some(file_2_id), None), + ("file_3", &file_3_data[..], None, None), + ( + "file_4", + &file_4_data[..], + Some(file_4_id), + Some(file_4_time), + ), + ("file_5", &input_file_1_data[..], None, None), + ( + "file_6", + &input_file_2_data[..], + Some(file_6_id), + Some(file_6_time), + ), + ("input_1", &input_file_1_data[..], None, None), + ("input_2", &input_file_2_data[..], None, None), + ]; + for file in project.files().iter() { + let found = files.iter().find(|f| file.name().as_str() == f.0).unwrap(); + if let Some(id) = found.2 { + assert_eq!(file.id().as_str(), id); + } + if let Some(time) = found.3 { + assert_eq!( + file.creation_time() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(), + time.duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() + ); + } + let content = std::fs::read(file.path_on_disk().as_str()).unwrap(); + assert_eq!(content, found.1); + } + + assert_eq!(project.name().as_str(), "project_name"); + assert_eq!(project.description().as_str(), "project_description"); + + // close the project + project.close().unwrap(); + + // delete the project + std::fs::remove_dir_all(project_path).unwrap(); + std::fs::remove_dir_all(tmp_folder_1).unwrap(); + std::fs::remove_dir_all(tmp_folder_2).unwrap(); +} diff --git a/rust/tests/type_archive.rs b/rust/tests/type_archive.rs new file mode 100644 index 00000000..dafb7178 --- /dev/null +++ b/rust/tests/type_archive.rs @@ -0,0 +1,31 @@ +use binaryninja::binary_view::BinaryView; +use binaryninja::file_metadata::FileMetadata; +use binaryninja::headless::Session; +use binaryninja::platform::Platform; +use binaryninja::rc::Ref; +use binaryninja::type_archive::TypeArchive; +use rstest::*; + +#[fixture] +#[once] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +#[fixture] +#[once] +fn empty_view() -> Ref<BinaryView> { + BinaryView::from_data(&FileMetadata::new(), &[]).expect("Failed to create view") +} + +#[rstest] +fn test_create_archive(_session: &Session) { + let placeholder_platform = Platform::by_name("x86_64").expect("Failed to get platform"); + + let temp_dir = tempfile::tempdir().unwrap(); + let type_archive_path = temp_dir.path().with_file_name("type_archive_0"); + let type_archive = TypeArchive::create(type_archive_path, &placeholder_platform).unwrap(); + println!("{:?}", type_archive); + // TODO: It seems that type archives have to be closed. + type_archive.close(); +} diff --git a/rust/tests/type_container.rs b/rust/tests/type_container.rs new file mode 100644 index 00000000..d5a72031 --- /dev/null +++ b/rust/tests/type_container.rs @@ -0,0 +1,137 @@ +use binaryninja::binary_view::{BinaryView, BinaryViewExt}; +use binaryninja::file_metadata::FileMetadata; +use binaryninja::headless::Session; +use binaryninja::platform::Platform; +use binaryninja::rc::Ref; +use binaryninja::types::Type; +use rstest::*; + +#[fixture] +#[once] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +#[fixture] +#[once] +fn platform() -> Ref<Platform> { + // TODO: Because some behavior might be platform specific we might need to move this back into each test. + // TODO: See test_parse_type + Platform::by_name("windows-x86_64").expect("windows-x86_64 exists") +} + +#[fixture] +#[once] +fn empty_view() -> Ref<BinaryView> { + BinaryView::from_data(&FileMetadata::new(), &[]).expect("Failed to create view") +} + +#[rstest] +fn test_types(_session: &Session, platform: &Platform) { + let type_container = platform.type_container(); + let types = type_container.types().unwrap(); + // windows-x86_64 has a few thousand, not zero. + assert_eq!(types.len(), platform.types().len()); +} + +#[rstest] +fn test_type_id(_session: &Session, platform: &Platform) { + let type_container = platform.type_container(); + let type_ids = type_container.type_ids().unwrap(); + let first_type_id = type_ids.iter().next().unwrap(); + let found_type = type_container + .type_by_id(first_type_id) + .expect("Type ID not valid!"); + let found_type_name = type_container + .type_name(first_type_id) + .expect("Type name not found for Type ID"); + let found_type_for_type_name = type_container + .type_by_name(found_type_name) + .expect("Found type name not valid!"); + // These _should_ be the same type. + assert_eq!(found_type, found_type_for_type_name); +} + +#[rstest] +fn test_add_delete_type(_session: &Session, empty_view: &BinaryView) { + let view_type_container = empty_view.type_container(); + let test_type = Type::int(4, true); + assert!( + view_type_container.add_types([("mytype", test_type)]), + "Failed to add types!" + ); + let my_type_id = view_type_container + .type_id("mytype") + .expect("mytype not found"); + assert!( + view_type_container.delete_type(my_type_id), + "Type was deleted!" + ); + // There should be no type ids if the type was actually deleted + assert_eq!(view_type_container.type_ids().unwrap().len(), 0) +} + +#[rstest] +fn test_immutable_container(_session: &Session, platform: &Platform) { + // Platform type containers are immutable, so we shouldn't be able to delete/add/rename types. + let plat_type_container = platform.type_container(); + assert!( + !plat_type_container.is_mutable(), + "Platform should NOT be mutable!" + ); + assert_ne!( + platform.types().len(), + 0, + "Something deleted all the platform types!" + ); + let type_ids = plat_type_container.type_ids().unwrap(); + let first_type_id = type_ids.iter().next().unwrap(); + // Platform type containers are immutable so these should be false! + assert!( + !plat_type_container.delete_type(first_type_id), + "Type was deleted!" + ); + assert!( + !plat_type_container.add_types([("new_type", Type::int(4, true))]), + "Type was added!" + ); + assert!( + !plat_type_container.rename_type(first_type_id, "renamed_type"), + "Type was renamed!" + ); +} + +#[rstest] +fn test_parse_type(_session: &Session, platform: &Platform) { + let type_container = platform.type_container(); + // HANDLE will be pulled in from the platform, which is `windows-x86_64`. + let parsed_type = type_container + .parse_type_string("typedef HANDLE test;", false) + .map_err(|e| e.to_vec()) + .expect("Failed to parse type"); + assert_eq!(parsed_type.name, "test".into()); + assert_eq!(parsed_type.ty.to_string(), "HANDLE"); +} + +#[rstest] +fn test_container_lifetime(_session: &Session, platform: &Platform, empty_view: &BinaryView) { + let plat_type_container_dropped = platform.type_container(); + let view_type_container_dropped = empty_view.type_container(); + let _plat_types_dropped = plat_type_container_dropped.types(); + let _view_types_dropped = view_type_container_dropped.types(); + drop(plat_type_container_dropped); + drop(view_type_container_dropped); + let plat_type_container_0 = platform.type_container(); + let view_type_container_0 = empty_view.type_container(); + let test_type = Type::int(4, true); + view_type_container_0.add_types([("mytype", test_type)]); + let plat_types_0 = plat_type_container_0.types(); + let view_types_0 = view_type_container_0.types(); + let plat_type_container_1 = platform.type_container(); + let view_type_container_1 = empty_view.type_container(); + let plat_types_1 = plat_type_container_1.types(); + let view_types_1 = view_type_container_1.types(); + // If the types do not equal the container is being freed from the first set of calls. + assert_eq!(plat_types_0, plat_types_1); + assert_eq!(view_types_0, view_types_1); +} diff --git a/rust/tests/type_parser.rs b/rust/tests/type_parser.rs new file mode 100644 index 00000000..5726fb0a --- /dev/null +++ b/rust/tests/type_parser.rs @@ -0,0 +1,85 @@ +use binaryninja::headless::Session; +use binaryninja::platform::Platform; +use binaryninja::type_parser::{CoreTypeParser, TypeParser, TypeParserError}; +use binaryninja::types::Type; +use binaryninjacore_sys::BNTypeParserErrorSeverity::ErrorSeverity; +use rstest::*; + +const TEST_TYPES: &str = r#" +typedef int int32_t; +typedef unsigned int uint32_t; +typedef float float_t; +typedef double double_t; +typedef char char_t; +typedef unsigned char uchar_t; +typedef short short_t; +typedef unsigned short ushort_t; +typedef long long_t; +typedef unsigned long ulong_t; +typedef long long longlong_t; +typedef unsigned long long ulonglong_t; +typedef void* void_ptr_t; +typedef int (*function_type)(int arg1, float arg2); +// This should be 2 types +typedef struct { + int a; + float b; +} struct_type; +"#; + +#[fixture] +#[once] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +#[rstest] +fn test_string_to_type(_session: &Session) { + let platform = Platform::by_name("windows-x86_64").expect("windows-x86_64 exists"); + let plat_type_container = platform.type_container(); + let parser = CoreTypeParser::default(); + let parsed_type = parser + .parse_type_string("int32_t", &platform, &plat_type_container) + .expect("Parsed int32_t"); + let test_type = Type::int(4, true); + assert_eq!(test_type, parsed_type.ty); +} + +#[rstest] +fn test_string_to_types(_session: &Session) { + let platform = Platform::by_name("windows-x86_64").expect("windows-x86_64 exists"); + let plat_type_container = platform.type_container(); + let parser = CoreTypeParser::default(); + let parsed_type = parser + .parse_types_from_source( + TEST_TYPES, + "test_file.h", + &platform, + &plat_type_container, + &[], + &[], + "", + ) + .expect("Parsed types"); + assert_eq!(14, parsed_type.types.len()); +} + +#[rstest] +fn test_parse_error(_session: &Session) { + let platform = Platform::by_name("windows-x86_64").expect("windows-x86_64 exists"); + let plat_type_container = platform.type_container(); + let parser = CoreTypeParser::default(); + let parser_error = parser + .parse_type_string("AAAAA", &platform, &plat_type_container) + .expect_err("Parsing should fail!"); + assert_eq!( + parser_error, + vec![TypeParserError { + severity: ErrorSeverity, + message: "a type specifier is required for all declarations".to_string(), + file_name: "string.hpp".to_string(), + line: 1, + column: 1 + }] + ); +} diff --git a/rust/tests/types.rs b/rust/tests/types.rs new file mode 100644 index 00000000..f745de0c --- /dev/null +++ b/rust/tests/types.rs @@ -0,0 +1,50 @@ +use binaryninja::headless::Session; +use binaryninja::types::{MemberAccess, MemberScope, StructureBuilder, StructureMember, Type}; +use rstest::*; + +#[fixture] +#[once] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +#[rstest] +fn test_type_to_string(_session: &Session) { + let test_type = Type::int(4, true); + assert_eq!(test_type.to_string(), "int32_t".to_string()); +} + +#[rstest] +fn test_structure_builder(_session: &Session) { + let mut builder = StructureBuilder::new(); + builder.insert( + &Type::int(4, true), + "field_1", + 0, + false, + MemberAccess::PrivateAccess, + MemberScope::FriendScope, + ); + builder.insert( + &Type::float(8), + "field_2", + 4, + false, + MemberAccess::PublicAccess, + MemberScope::NoScope, + ); + + let structure = builder.finalize(); + let members = structure.members(); + assert_eq!(members.len(), 2); + assert_eq!( + members[0], + StructureMember { + name: "field_1".to_string(), + ty: Type::int(4, true).into(), + offset: 0, + access: MemberAccess::PrivateAccess, + scope: MemberScope::FriendScope, + } + ); +} diff --git a/rust/tests/worker_thread.rs b/rust/tests/worker_thread.rs new file mode 100644 index 00000000..666e85ba --- /dev/null +++ b/rust/tests/worker_thread.rs @@ -0,0 +1,45 @@ +use binaryninja::headless::Session; +use rstest::*; +use std::sync::{Arc, Barrier}; + +#[fixture] +#[once] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +#[rstest] +fn test_setting_worker_thread(_session: &Session) { + let original_count = binaryninja::worker_thread::worker_thread_count(); + binaryninja::worker_thread::set_worker_thread_count(original_count - 1); + assert_eq!( + binaryninja::worker_thread::worker_thread_count(), + original_count - 1 + ); + binaryninja::worker_thread::set_worker_thread_count(original_count); + assert_eq!( + binaryninja::worker_thread::worker_thread_count(), + original_count + ); +} + +#[rstest] +fn test_worker_thread_different(_session: &Session) { + let calling_thread = std::thread::current(); + + // We need both (2) threads to synchronize + let barrier = Arc::new(Barrier::new(2)); + let barrier_clone = Arc::clone(&barrier); + binaryninja::worker_thread::execute_on_worker_thread("test", move || { + let worker_thread = std::thread::current(); + assert_ne!( + calling_thread.id(), + worker_thread.id(), + "Expected calling thread to be different from the worker thread" + ); + barrier_clone.wait(); + }); + + // Wait until worker thread has finished. + barrier.wait(); +} diff --git a/rust/tests/workflow.rs b/rust/tests/workflow.rs new file mode 100644 index 00000000..fcede59c --- /dev/null +++ b/rust/tests/workflow.rs @@ -0,0 +1,93 @@ +use binaryninja::headless::Session; +use binaryninja::settings::Settings; +use binaryninja::workflow::Workflow; +use rstest::*; + +#[fixture] +#[once] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +// TODO: Test running a workflow activity +// TODO: Test activity insertion and removal + +#[rstest] +fn test_workflow_clone(_session: &Session) { + let original_workflow = Workflow::new("core.function.baseAnalysis"); + let mut cloned_workflow = original_workflow.clone("clone_workflow"); + + assert_eq!(cloned_workflow.name().as_str(), "clone_workflow"); + assert_eq!( + cloned_workflow.configuration(), + original_workflow.configuration() + ); + + cloned_workflow = original_workflow.clone(""); + assert_eq!( + cloned_workflow.name().as_str(), + "core.function.baseAnalysis" + ); +} + +#[rstest] +fn test_workflow_registration(_session: &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("test_workflow"); + + assert_eq!(test_workflow.name().as_str(), "test_workflow"); + assert!(!test_workflow.registered()); + test_workflow + .register() + .expect("Failed to register workflow"); + assert!(test_workflow.registered()); + assert_eq!( + test_workflow.size(), + Workflow::instance("core.function.baseAnalysis").size() + ); + Workflow::list() + .iter() + .find(|w| w.name() == test_workflow.name()) + .expect("Workflow not found in list"); + Settings::new() + .get_property_string_list("analysis.workflows.functionWorkflow", "enum") + .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()); + + // Validate re-registration of baseAnalysis is not allowed + let base_workflow_clone = Workflow::instance("core.function.baseAnalysis").clone(""); + + assert_eq!( + base_workflow_clone.name().as_str(), + "core.function.baseAnalysis" + ); + assert!(!base_workflow_clone.registered()); + base_workflow_clone + .register() + .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()); +} |
