use crate::{
cilassembly::{ChangeRefRc, CilAssembly},
metadata::{
tables::{CodedIndex, ImplMapRaw, TableDataOwned, TableId},
token::Token,
},
Error, Result,
};
pub struct ImplMapBuilder {
mapping_flags: Option<u32>,
member_forwarded: Option<CodedIndex>,
import_name: Option<String>,
import_scope: Option<u32>,
}
impl Default for ImplMapBuilder {
fn default() -> Self {
Self::new()
}
}
impl ImplMapBuilder {
#[must_use]
pub fn new() -> Self {
Self {
mapping_flags: None,
member_forwarded: None,
import_name: None,
import_scope: None,
}
}
#[must_use]
pub fn mapping_flags(mut self, flags: u32) -> Self {
self.mapping_flags = Some(flags);
self
}
#[must_use]
pub fn member_forwarded(mut self, member: CodedIndex) -> Self {
self.member_forwarded = Some(member);
self
}
#[must_use]
pub fn import_name(mut self, name: impl Into<String>) -> Self {
self.import_name = Some(name.into());
self
}
#[must_use]
pub fn import_scope(mut self, scope: u32) -> Self {
self.import_scope = Some(scope);
self
}
pub fn build(self, assembly: &mut CilAssembly) -> Result<ChangeRefRc> {
let member_forwarded = self.member_forwarded.ok_or_else(|| {
Error::ModificationInvalid("member_forwarded field is required".to_string())
})?;
let import_name = self.import_name.ok_or_else(|| {
Error::ModificationInvalid("import_name field is required".to_string())
})?;
let import_scope = self.import_scope.ok_or_else(|| {
Error::ModificationInvalid("import_scope field is required".to_string())
})?;
if !matches!(member_forwarded.tag, TableId::Field | TableId::MethodDef) {
return Err(Error::ModificationInvalid(
"MemberForwarded must reference Field or MethodDef table".to_string(),
));
}
let import_name_index = assembly.string_add(&import_name)?.placeholder();
let implmap_raw = ImplMapRaw {
rid: 0,
token: Token::new(0),
offset: 0,
mapping_flags: self.mapping_flags.unwrap_or(0),
member_forwarded,
import_name: import_name_index,
import_scope,
};
assembly.table_row_add(TableId::ImplMap, TableDataOwned::ImplMap(implmap_raw))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cilassembly::ChangeRefKind, metadata::tables::implmap::PInvokeAttributes, prelude::*,
test::factories::table::assemblyref::get_test_assembly,
};
#[test]
fn test_implmap_builder_basic() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = ImplMapBuilder::new()
.member_forwarded(CodedIndex::new(
TableId::MethodDef,
1,
CodedIndexType::MemberForwarded,
))
.import_name("MessageBoxW")
.import_scope(1)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::ImplMap));
Ok(())
}
#[test]
fn test_implmap_builder_with_flags() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = ImplMapBuilder::new()
.member_forwarded(CodedIndex::new(
TableId::MethodDef,
1,
CodedIndexType::MemberForwarded,
))
.import_name("GetModuleFileNameW")
.import_scope(2)
.mapping_flags(
PInvokeAttributes::CALL_CONV_STDCALL
| PInvokeAttributes::CHAR_SET_UNICODE
| PInvokeAttributes::SUPPORTS_LAST_ERROR,
)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::ImplMap));
Ok(())
}
#[test]
fn test_implmap_builder_no_mangle() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = ImplMapBuilder::new()
.member_forwarded(CodedIndex::new(
TableId::MethodDef,
3,
CodedIndexType::MemberForwarded,
))
.import_name("my_custom_function")
.import_scope(3)
.mapping_flags(PInvokeAttributes::NO_MANGLE | PInvokeAttributes::CALL_CONV_CDECL)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::ImplMap));
Ok(())
}
#[test]
fn test_implmap_builder_field_reference() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = ImplMapBuilder::new()
.member_forwarded(CodedIndex::new(
TableId::Field,
1,
CodedIndexType::MemberForwarded,
))
.import_name("global_variable")
.import_scope(1)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::ImplMap));
Ok(())
}
#[test]
fn test_implmap_builder_missing_member_forwarded() {
let mut assembly = get_test_assembly().unwrap();
let result = ImplMapBuilder::new()
.import_name("MessageBoxW")
.import_scope(1)
.build(&mut assembly);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("member_forwarded"));
}
#[test]
fn test_implmap_builder_missing_import_name() {
let mut assembly = get_test_assembly().unwrap();
let result = ImplMapBuilder::new()
.member_forwarded(CodedIndex::new(
TableId::MethodDef,
1,
CodedIndexType::MemberForwarded,
))
.import_scope(1)
.build(&mut assembly);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("import_name"));
}
#[test]
fn test_implmap_builder_missing_import_scope() {
let mut assembly = get_test_assembly().unwrap();
let result = ImplMapBuilder::new()
.member_forwarded(CodedIndex::new(
TableId::MethodDef,
1,
CodedIndexType::MemberForwarded,
))
.import_name("MessageBoxW")
.build(&mut assembly);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("import_scope"));
}
#[test]
fn test_implmap_builder_invalid_coded_index() {
let mut assembly = get_test_assembly().unwrap();
let result = ImplMapBuilder::new()
.member_forwarded(CodedIndex::new(
TableId::TypeDef,
1,
CodedIndexType::MemberForwarded,
)) .import_name("MessageBoxW")
.import_scope(1)
.build(&mut assembly);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("MemberForwarded must reference Field or MethodDef"));
}
#[test]
fn test_implmap_builder_multiple_flags() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = ImplMapBuilder::new()
.member_forwarded(CodedIndex::new(
TableId::MethodDef,
4,
CodedIndexType::MemberForwarded,
))
.import_name("ProcessStringData")
.import_scope(4)
.mapping_flags(
PInvokeAttributes::CHAR_SET_AUTO
| PInvokeAttributes::BEST_FIT_DISABLED
| PInvokeAttributes::THROW_ON_UNMAPPABLE_ENABLED,
)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::ImplMap));
Ok(())
}
#[test]
fn test_implmap_builder_default() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = ImplMapBuilder::default()
.member_forwarded(CodedIndex::new(
TableId::MethodDef,
1,
CodedIndexType::MemberForwarded,
))
.import_name("TestFunction")
.import_scope(1)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::ImplMap));
Ok(())
}
}