use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum AttributeKind {
NoInline,
AlwaysInline,
OptimizeNone,
OptimizeForSize,
OptimizeForSpeed,
ReadNone,
ReadOnly,
WriteOnly,
ArgMemOnly,
InaccessibleMemOnly,
InaccessibleMemOrArgMemOnly,
NoUnwind,
NoReturn,
WillReturn,
Ssp,
SspReq,
SspStrong,
SanitizeAddress,
SanitizeThread,
SanitizeMemory,
SanitizeHwAddress,
NoSanitize,
SanitizeCoverage,
ShadowCallStack,
SpeculativeLoadHardening,
NoCfCheck,
AlignStack(u32),
Naked,
NoImplicitFloat,
NoRedZone,
UWTable,
FramePointer(FramePointerKind),
InlineHint,
NoMerge,
ReturnsTwice,
JumpTable,
MustProgress,
Convergent,
NoDivergenceSource,
NoProfile,
SkipProfile,
OptDebug,
OptForFuzzing,
AllocSize(u32, Option<u32>),
AllocPtr,
NoBuiltin,
NoCallback,
NonLazyBind,
SafeStack,
SanitizeMemTag,
DisableSanitizerInstrumentation,
Hotpatch,
FnRetThunkExtern,
StrictFP,
NoFPClass(u32),
VScaleRange(u32, u32),
Speculatable,
NoDuplicate,
NoRecurse,
NoSync,
NoFree,
Cold,
Hot,
Minsize,
NullPointerIsValid,
UseSampleProfile,
StringAttribute(String, String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FramePointerKind {
All,
NonLeaf,
None,
}
impl AttributeKind {
pub fn from_string(s: &str) -> Option<Self> {
match s {
"noinline" => Some(AttributeKind::NoInline),
"alwaysinline" => Some(AttributeKind::AlwaysInline),
"optnone" => Some(AttributeKind::OptimizeNone),
"optsize" => Some(AttributeKind::OptimizeForSize),
"optforspeed" => Some(AttributeKind::OptimizeForSpeed),
"readnone" | "memory(none)" => Some(AttributeKind::ReadNone),
"readonly" | "memory(read)" => Some(AttributeKind::ReadOnly),
"writeonly" | "memory(write)" => Some(AttributeKind::WriteOnly),
"argmemonly" => Some(AttributeKind::ArgMemOnly),
"inaccessiblememonly" => Some(AttributeKind::InaccessibleMemOnly),
"inaccessiblemem_or_argmemonly" => Some(AttributeKind::InaccessibleMemOrArgMemOnly),
"nounwind" => Some(AttributeKind::NoUnwind),
"noreturn" => Some(AttributeKind::NoReturn),
"willreturn" => Some(AttributeKind::WillReturn),
"ssp" => Some(AttributeKind::Ssp),
"sspreq" => Some(AttributeKind::SspReq),
"sspstrong" => Some(AttributeKind::SspStrong),
"sanitize_address" => Some(AttributeKind::SanitizeAddress),
"sanitize_thread" => Some(AttributeKind::SanitizeThread),
"sanitize_memory" => Some(AttributeKind::SanitizeMemory),
"sanitize_hwaddress" => Some(AttributeKind::SanitizeHwAddress),
"nosanitize" => Some(AttributeKind::NoSanitize),
"sanitize_coverage" => Some(AttributeKind::SanitizeCoverage),
"shadowcallstack" => Some(AttributeKind::ShadowCallStack),
"speculative_load_hardening" => Some(AttributeKind::SpeculativeLoadHardening),
"nocf_check" => Some(AttributeKind::NoCfCheck),
"noduplicate" => Some(AttributeKind::NoDuplicate),
"norecurse" => Some(AttributeKind::NoRecurse),
"nosync" => Some(AttributeKind::NoSync),
"nofree" => Some(AttributeKind::NoFree),
"cold" => Some(AttributeKind::Cold),
"hot" => Some(AttributeKind::Hot),
"minsize" => Some(AttributeKind::Minsize),
"null_pointer_is_valid" => Some(AttributeKind::NullPointerIsValid),
"use_sample_profile" => Some(AttributeKind::UseSampleProfile),
"naked" => Some(AttributeKind::Naked),
"noimplicitfloat" => Some(AttributeKind::NoImplicitFloat),
"noredzone" => Some(AttributeKind::NoRedZone),
"uwtable" => Some(AttributeKind::UWTable),
s if s.starts_with("alignstack(") => {
let inner = &s["alignstack(".len()..s.len() - 1];
inner.parse::<u32>().ok().map(AttributeKind::AlignStack)
}
s if s.starts_with("frame-pointer=") => {
let kind = &s["frame-pointer=".len()..];
Some(AttributeKind::FramePointer(match kind {
"all" => FramePointerKind::All,
"non-leaf" => FramePointerKind::NonLeaf,
_ => FramePointerKind::None,
}))
}
"inlinehint" => Some(AttributeKind::InlineHint),
"nomerge" => Some(AttributeKind::NoMerge),
"returns_twice" => Some(AttributeKind::ReturnsTwice),
"jumptable" => Some(AttributeKind::JumpTable),
"mustprogress" => Some(AttributeKind::MustProgress),
"convergent" => Some(AttributeKind::Convergent),
"noduplicatesource" => Some(AttributeKind::NoDivergenceSource),
"noprofile" => Some(AttributeKind::NoProfile),
"skipprofile" => Some(AttributeKind::SkipProfile),
"optdebug" => Some(AttributeKind::OptDebug),
"optforfuzzing" => Some(AttributeKind::OptForFuzzing),
s if s.starts_with("allocsize(") => {
let inner = &s["allocsize(".len()..s.len() - 1];
let parts: Vec<&str> = inner.split(',').collect();
let p0 = parts[0].trim().parse::<u32>().ok()?;
let p1 = if parts.len() > 1 {
parts[1].trim().parse::<u32>().ok()
} else {
None
};
Some(AttributeKind::AllocSize(p0, p1))
}
"allocptr" => Some(AttributeKind::AllocPtr),
"nobuiltin" => Some(AttributeKind::NoBuiltin),
"nocallback" => Some(AttributeKind::NoCallback),
"nonlazybind" => Some(AttributeKind::NonLazyBind),
"safestack" => Some(AttributeKind::SafeStack),
"sanitize_memtag" => Some(AttributeKind::SanitizeMemTag),
"disable_sanitizer_instrumentation" => {
Some(AttributeKind::DisableSanitizerInstrumentation)
}
"hotpatch" => Some(AttributeKind::Hotpatch),
"fn_ret_thunk_extern" => Some(AttributeKind::FnRetThunkExtern),
"strictfp" => Some(AttributeKind::StrictFP),
s if s.starts_with("nofpclass(") => {
let inner = &s["nofpclass(".len()..s.len() - 1];
inner.parse::<u32>().ok().map(AttributeKind::NoFPClass)
}
s if s.starts_with("vscale_range(") => {
let inner = &s["vscale_range(".len()..s.len() - 1];
let parts: Vec<&str> = inner.split(',').collect();
let min = parts[0].trim().parse::<u32>().ok()?;
let max = if parts.len() > 1 {
parts[1].trim().parse::<u32>().ok()?
} else {
min
};
Some(AttributeKind::VScaleRange(min, max))
}
"speculatable" => Some(AttributeKind::Speculatable),
_ => {
if let Some(eq_pos) = s.find('=') {
let key = s[..eq_pos].to_string();
let value = s[eq_pos + 1..].to_string();
Some(AttributeKind::StringAttribute(key, value))
} else {
None
}
}
}
}
pub fn to_string(&self) -> String {
match self {
AttributeKind::NoInline => "noinline".to_string(),
AttributeKind::AlwaysInline => "alwaysinline".to_string(),
AttributeKind::OptimizeNone => "optnone".to_string(),
AttributeKind::OptimizeForSize => "optsize".to_string(),
AttributeKind::OptimizeForSpeed => "optforspeed".to_string(),
AttributeKind::ReadNone => "readnone".to_string(),
AttributeKind::ReadOnly => "readonly".to_string(),
AttributeKind::WriteOnly => "writeonly".to_string(),
AttributeKind::ArgMemOnly => "argmemonly".to_string(),
AttributeKind::InaccessibleMemOnly => "inaccessiblememonly".to_string(),
AttributeKind::InaccessibleMemOrArgMemOnly => {
"inaccessiblemem_or_argmemonly".to_string()
}
AttributeKind::NoUnwind => "nounwind".to_string(),
AttributeKind::NoReturn => "noreturn".to_string(),
AttributeKind::WillReturn => "willreturn".to_string(),
AttributeKind::Ssp => "ssp".to_string(),
AttributeKind::SspReq => "sspreq".to_string(),
AttributeKind::SspStrong => "sspstrong".to_string(),
AttributeKind::SanitizeAddress => "sanitize_address".to_string(),
AttributeKind::SanitizeThread => "sanitize_thread".to_string(),
AttributeKind::SanitizeMemory => "sanitize_memory".to_string(),
AttributeKind::SanitizeHwAddress => "sanitize_hwaddress".to_string(),
AttributeKind::NoSanitize => "nosanitize".to_string(),
AttributeKind::SanitizeCoverage => "sanitize_coverage".to_string(),
AttributeKind::ShadowCallStack => "shadowcallstack".to_string(),
AttributeKind::SpeculativeLoadHardening => "speculative_load_hardening".to_string(),
AttributeKind::NoCfCheck => "nocf_check".to_string(),
AttributeKind::NoDuplicate => "noduplicate".to_string(),
AttributeKind::NoRecurse => "norecurse".to_string(),
AttributeKind::NoSync => "nosync".to_string(),
AttributeKind::NoFree => "nofree".to_string(),
AttributeKind::Cold => "cold".to_string(),
AttributeKind::Hot => "hot".to_string(),
AttributeKind::Minsize => "minsize".to_string(),
AttributeKind::NullPointerIsValid => "null_pointer_is_valid".to_string(),
AttributeKind::UseSampleProfile => "use_sample_profile".to_string(),
AttributeKind::AlignStack(n) => format!("alignstack({})", n),
AttributeKind::Naked => "naked".to_string(),
AttributeKind::NoImplicitFloat => "noimplicitfloat".to_string(),
AttributeKind::NoRedZone => "noredzone".to_string(),
AttributeKind::UWTable => "uwtable".to_string(),
AttributeKind::FramePointer(kind) => format!(
"frame-pointer={}",
match kind {
FramePointerKind::All => "all",
FramePointerKind::NonLeaf => "non-leaf",
FramePointerKind::None => "none",
}
),
AttributeKind::InlineHint => "inlinehint".to_string(),
AttributeKind::NoMerge => "nomerge".to_string(),
AttributeKind::ReturnsTwice => "returns_twice".to_string(),
AttributeKind::JumpTable => "jumptable".to_string(),
AttributeKind::MustProgress => "mustprogress".to_string(),
AttributeKind::Convergent => "convergent".to_string(),
AttributeKind::NoDivergenceSource => "noduplicatesource".to_string(),
AttributeKind::NoProfile => "noprofile".to_string(),
AttributeKind::SkipProfile => "skipprofile".to_string(),
AttributeKind::OptDebug => "optdebug".to_string(),
AttributeKind::OptForFuzzing => "optforfuzzing".to_string(),
AttributeKind::AllocSize(p0, Some(p1)) => format!("allocsize({}, {})", p0, p1),
AttributeKind::AllocSize(p0, None) => format!("allocsize({})", p0),
AttributeKind::AllocPtr => "allocptr".to_string(),
AttributeKind::NoBuiltin => "nobuiltin".to_string(),
AttributeKind::NoCallback => "nocallback".to_string(),
AttributeKind::NonLazyBind => "nonlazybind".to_string(),
AttributeKind::SafeStack => "safestack".to_string(),
AttributeKind::SanitizeMemTag => "sanitize_memtag".to_string(),
AttributeKind::DisableSanitizerInstrumentation => {
"disable_sanitizer_instrumentation".to_string()
}
AttributeKind::Hotpatch => "hotpatch".to_string(),
AttributeKind::FnRetThunkExtern => "fn_ret_thunk_extern".to_string(),
AttributeKind::StrictFP => "strictfp".to_string(),
AttributeKind::NoFPClass(n) => format!("nofpclass({})", n),
AttributeKind::VScaleRange(min, max) => format!("vscale_range({}, {})", min, max),
AttributeKind::Speculatable => "speculatable".to_string(),
AttributeKind::StringAttribute(key, val) => format!("\"{}\"=\"{}\"", key, val),
}
}
pub fn is_memory_attr(&self) -> bool {
matches!(
self,
AttributeKind::ReadNone
| AttributeKind::ReadOnly
| AttributeKind::WriteOnly
| AttributeKind::ArgMemOnly
| AttributeKind::InaccessibleMemOnly
| AttributeKind::InaccessibleMemOrArgMemOnly
)
}
pub fn is_sanitizer_attr(&self) -> bool {
matches!(
self,
AttributeKind::SanitizeAddress
| AttributeKind::SanitizeThread
| AttributeKind::SanitizeMemory
| AttributeKind::SanitizeHwAddress
| AttributeKind::NoSanitize
| AttributeKind::SanitizeCoverage
| AttributeKind::SanitizeMemTag
| AttributeKind::DisableSanitizerInstrumentation
)
}
pub fn is_security_attr(&self) -> bool {
matches!(
self,
AttributeKind::Ssp
| AttributeKind::SspReq
| AttributeKind::SspStrong
| AttributeKind::ShadowCallStack
| AttributeKind::SpeculativeLoadHardening
| AttributeKind::NoCfCheck
| AttributeKind::SafeStack
| AttributeKind::Hotpatch
| AttributeKind::FnRetThunkExtern
)
}
pub fn is_inline_attr(&self) -> bool {
matches!(
self,
AttributeKind::NoInline
| AttributeKind::AlwaysInline
| AttributeKind::InlineHint
| AttributeKind::NoMerge
)
}
pub fn is_fp_attr(&self) -> bool {
matches!(self, AttributeKind::StrictFP | AttributeKind::NoFPClass(_))
}
pub fn has_numeric_arg(&self) -> bool {
matches!(
self,
AttributeKind::AlignStack(_)
| AttributeKind::AllocSize(_, _)
| AttributeKind::NoFPClass(_)
| AttributeKind::VScaleRange(_, _)
)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ParamAttrKind {
ZeroExt,
SignExt,
NoAlias,
NoCapture,
NoFree,
NonNull,
ReadOnly,
WriteOnly,
Dereferenceable(u64),
DereferenceableOrNull(u64),
Align(u32),
ByVal(crate::types::Type),
StructRet(crate::types::Type),
InReg,
Returned,
ImmArg,
SExt,
NoUndef,
ByRef(crate::types::Type),
Preallocated(crate::types::Type),
InAlloca(crate::types::Type),
Nest,
ElementType(crate::types::Type),
AllocPtr,
NoFPClass(u32),
Range(u64, u64),
InitialExec,
SwiftAsync,
SwiftError,
SwiftSelf,
SwiftError2,
}
impl ParamAttrKind {
pub fn from_string(s: &str) -> Option<Self> {
if let Some(rest) = s.strip_prefix("align ") {
return rest.parse::<u32>().ok().map(ParamAttrKind::Align);
}
if let Some(rest) = s.strip_prefix("alignstack ") {
return rest.parse::<u32>().ok().map(ParamAttrKind::Align);
}
if let Some(rest) = s.strip_prefix("dereferenceable(") {
if let Some(val_str) = rest.strip_suffix(')') {
return val_str
.parse::<u64>()
.ok()
.map(ParamAttrKind::Dereferenceable);
}
}
if let Some(rest) = s.strip_prefix("dereferenceable_or_null(") {
if let Some(val_str) = rest.strip_suffix(')') {
return val_str
.parse::<u64>()
.ok()
.map(ParamAttrKind::DereferenceableOrNull);
}
}
match s {
"zeroext" => Some(ParamAttrKind::ZeroExt),
"signext" => Some(ParamAttrKind::SignExt),
"noalias" => Some(ParamAttrKind::NoAlias),
"nocapture" => Some(ParamAttrKind::NoCapture),
"nofree" => Some(ParamAttrKind::NoFree),
"nonnull" => Some(ParamAttrKind::NonNull),
"readonly" => Some(ParamAttrKind::ReadOnly),
"writeonly" => Some(ParamAttrKind::WriteOnly),
"inreg" => Some(ParamAttrKind::InReg),
"returned" => Some(ParamAttrKind::Returned),
"immarg" => Some(ParamAttrKind::ImmArg),
"sext" => Some(ParamAttrKind::SExt),
"noundef" => Some(ParamAttrKind::NoUndef),
"nest" => Some(ParamAttrKind::Nest),
"allocptr" => Some(ParamAttrKind::AllocPtr),
"initialexec" => Some(ParamAttrKind::InitialExec),
"swiftasync" => Some(ParamAttrKind::SwiftAsync),
"swifterror" => Some(ParamAttrKind::SwiftError),
"swiftself" => Some(ParamAttrKind::SwiftSelf),
"swifterror2" => Some(ParamAttrKind::SwiftError2),
s if s.starts_with("nofpclass(") => {
let inner = &s["nofpclass(".len()..s.len() - 1];
inner.parse::<u32>().ok().map(ParamAttrKind::NoFPClass)
}
s if s.starts_with("range(") => {
let inner = &s["range(".len()..s.len() - 1];
let parts: Vec<&str> = inner.split(',').collect();
let min = parts[0].trim().parse::<u64>().ok()?;
let max = if parts.len() > 1 {
parts[1].trim().parse::<u64>().ok()?
} else {
min
};
Some(ParamAttrKind::Range(min, max))
}
_ => None,
}
}
pub fn to_string(&self) -> String {
match self {
ParamAttrKind::ZeroExt => "zeroext".to_string(),
ParamAttrKind::SignExt => "signext".to_string(),
ParamAttrKind::NoAlias => "noalias".to_string(),
ParamAttrKind::NoCapture => "nocapture".to_string(),
ParamAttrKind::NoFree => "nofree".to_string(),
ParamAttrKind::NonNull => "nonnull".to_string(),
ParamAttrKind::ReadOnly => "readonly".to_string(),
ParamAttrKind::WriteOnly => "writeonly".to_string(),
ParamAttrKind::Dereferenceable(n) => format!("dereferenceable({})", n),
ParamAttrKind::DereferenceableOrNull(n) => format!("dereferenceable_or_null({})", n),
ParamAttrKind::Align(n) => format!("align {}", n),
ParamAttrKind::ByVal(_) => "byval".to_string(),
ParamAttrKind::StructRet(_) => "sret".to_string(),
ParamAttrKind::InReg => "inreg".to_string(),
ParamAttrKind::Returned => "returned".to_string(),
ParamAttrKind::ImmArg => "immarg".to_string(),
ParamAttrKind::SExt => "sext".to_string(),
ParamAttrKind::NoUndef => "noundef".to_string(),
ParamAttrKind::ByRef(_) => "byref".to_string(),
ParamAttrKind::Preallocated(_) => "preallocated".to_string(),
ParamAttrKind::InAlloca(_) => "inalloca".to_string(),
ParamAttrKind::Nest => "nest".to_string(),
ParamAttrKind::ElementType(_) => "elementtype".to_string(),
ParamAttrKind::AllocPtr => "allocptr".to_string(),
ParamAttrKind::NoFPClass(n) => format!("nofpclass({})", n),
ParamAttrKind::Range(min, max) => format!("range({}, {})", min, max),
ParamAttrKind::InitialExec => "initialexec".to_string(),
ParamAttrKind::SwiftAsync => "swiftasync".to_string(),
ParamAttrKind::SwiftError => "swifterror".to_string(),
ParamAttrKind::SwiftSelf => "swiftself".to_string(),
ParamAttrKind::SwiftError2 => "swifterror2".to_string(),
}
}
pub fn is_type_attr(&self) -> bool {
matches!(
self,
ParamAttrKind::ByVal(_)
| ParamAttrKind::StructRet(_)
| ParamAttrKind::ByRef(_)
| ParamAttrKind::Preallocated(_)
| ParamAttrKind::InAlloca(_)
| ParamAttrKind::ElementType(_)
)
}
pub fn is_numeric_attr(&self) -> bool {
matches!(
self,
ParamAttrKind::Dereferenceable(_)
| ParamAttrKind::DereferenceableOrNull(_)
| ParamAttrKind::Align(_)
| ParamAttrKind::NoFPClass(_)
| ParamAttrKind::Range(_, _)
)
}
pub fn kind_discriminant(&self) -> &'static str {
match self {
ParamAttrKind::ZeroExt => "zeroext",
ParamAttrKind::SignExt => "signext",
ParamAttrKind::NoAlias => "noalias",
ParamAttrKind::NoCapture => "nocapture",
ParamAttrKind::NoFree => "nofree",
ParamAttrKind::NonNull => "nonnull",
ParamAttrKind::ReadOnly => "readonly",
ParamAttrKind::WriteOnly => "writeonly",
ParamAttrKind::Dereferenceable(_) => "dereferenceable",
ParamAttrKind::DereferenceableOrNull(_) => "dereferenceable_or_null",
ParamAttrKind::Align(_) => "align",
ParamAttrKind::ByVal(_) => "byval",
ParamAttrKind::StructRet(_) => "sret",
ParamAttrKind::InReg => "inreg",
ParamAttrKind::Returned => "returned",
ParamAttrKind::ImmArg => "immarg",
ParamAttrKind::SExt => "sext",
ParamAttrKind::NoUndef => "noundef",
ParamAttrKind::ByRef(_) => "byref",
ParamAttrKind::Preallocated(_) => "preallocated",
ParamAttrKind::InAlloca(_) => "inalloca",
ParamAttrKind::Nest => "nest",
ParamAttrKind::ElementType(_) => "elementtype",
ParamAttrKind::AllocPtr => "allocptr",
ParamAttrKind::NoFPClass(_) => "nofpclass",
ParamAttrKind::Range(_, _) => "range",
ParamAttrKind::InitialExec => "initialexec",
ParamAttrKind::SwiftAsync => "swiftasync",
ParamAttrKind::SwiftError => "swifterror",
ParamAttrKind::SwiftSelf => "swiftself",
ParamAttrKind::SwiftError2 => "swifterror2",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct AttrGroup {
pub id: u32,
pub attrs: Vec<AttributeKind>,
}
impl AttrGroup {
pub fn new(id: u32) -> Self {
Self {
id,
attrs: Vec::new(),
}
}
pub fn add_attr(&mut self, attr: AttributeKind) {
if !self.attrs.contains(&attr) {
self.attrs.push(attr);
}
}
pub fn remove_attr(&mut self, attr: &AttributeKind) {
self.attrs.retain(|a| a != attr);
}
pub fn has_attr(&self, attr: &AttributeKind) -> bool {
self.attrs.contains(attr)
}
pub fn is_empty(&self) -> bool {
self.attrs.is_empty()
}
pub fn len(&self) -> usize {
self.attrs.len()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct AttributeList {
pub fn_attrs: Vec<AttributeKind>,
pub ret_attrs: Vec<ParamAttrKind>,
pub param_attrs: Vec<Vec<ParamAttrKind>>,
pub groups: HashMap<u32, AttrGroup>,
}
impl AttributeList {
pub fn new() -> Self {
Self {
fn_attrs: Vec::new(),
ret_attrs: Vec::new(),
param_attrs: Vec::new(),
groups: HashMap::new(),
}
}
pub fn add_fn_attr(&mut self, attr: AttributeKind) {
if !self.fn_attrs.contains(&attr) {
self.fn_attrs.push(attr);
}
}
pub fn remove_fn_attr(&mut self, attr: &AttributeKind) {
self.fn_attrs.retain(|a| a != attr);
}
pub fn add_ret_attr(&mut self, attr: ParamAttrKind) {
if !self.ret_attrs.contains(&attr) {
self.ret_attrs.push(attr);
}
}
pub fn remove_ret_attr(&mut self, attr: &ParamAttrKind) {
self.ret_attrs.retain(|a| a != attr);
}
pub fn add_param_attr(&mut self, param_idx: u32, attr: ParamAttrKind) {
let idx = param_idx as usize;
while self.param_attrs.len() <= idx {
self.param_attrs.push(Vec::new());
}
if !self.param_attrs[idx].contains(&attr) {
self.param_attrs[idx].push(attr);
}
}
pub fn remove_param_attr(&mut self, param_idx: u32, attr: &ParamAttrKind) {
let idx = param_idx as usize;
if idx < self.param_attrs.len() {
self.param_attrs[idx].retain(|a| a != attr);
}
}
pub fn has_fn_attr(&self, kind: &AttributeKind) -> bool {
self.fn_attrs.contains(kind)
}
pub fn has_memory_attr(&self) -> bool {
self.fn_attrs.iter().any(|a| a.is_memory_attr())
}
pub fn has_ret_attr(&self, kind: &ParamAttrKind) -> bool {
self.ret_attrs.contains(kind)
}
pub fn get_param_attrs(&self, idx: u32) -> &[ParamAttrKind] {
let idx = idx as usize;
if idx < self.param_attrs.len() {
&self.param_attrs[idx]
} else {
&[]
}
}
pub fn set_param_attrs(&mut self, param_idx: u32, attrs: Vec<ParamAttrKind>) {
let idx = param_idx as usize;
while self.param_attrs.len() <= idx {
self.param_attrs.push(Vec::new());
}
self.param_attrs[idx] = attrs;
}
pub fn num_param_slots(&self) -> usize {
self.param_attrs.len()
}
pub fn is_empty(&self) -> bool {
self.fn_attrs.is_empty()
&& self.ret_attrs.is_empty()
&& self.param_attrs.iter().all(|v| v.is_empty())
&& self.groups.is_empty()
}
pub fn add_group(&mut self, group: AttrGroup) {
self.groups.insert(group.id, group);
}
pub fn get_group(&self, id: u32) -> Option<&AttrGroup> {
self.groups.get(&id)
}
pub fn remove_group(&mut self, id: u32) {
self.groups.remove(&id);
}
pub fn create_group(&mut self, attrs: Vec<AttributeKind>) -> u32 {
let id = self.groups.len() as u32 + 1;
let group = AttrGroup { id, attrs };
self.groups.insert(id, group);
id
}
pub fn from_fn_attr_strings(attr_strings: &[&str]) -> Self {
let mut list = Self::new();
for s in attr_strings {
if let Some(attr) = AttributeKind::from_string(s) {
list.add_fn_attr(attr);
}
}
list
}
pub fn from_parts(
fn_attr_strs: &[&str],
ret_attr_strs: &[&str],
param_attr_strs: &[Vec<&str>],
) -> Self {
let mut list = Self::new();
for s in fn_attr_strs {
if let Some(attr) = AttributeKind::from_string(s) {
list.add_fn_attr(attr);
}
}
for s in ret_attr_strs {
if let Some(attr) = ParamAttrKind::from_string(s) {
list.add_ret_attr(attr);
}
}
for (i, attrs) in param_attr_strs.iter().enumerate() {
for s in attrs {
if let Some(attr) = ParamAttrKind::from_string(s) {
list.add_param_attr(i as u32, attr);
}
}
}
list
}
pub fn emit_fn_attr_string(&self) -> String {
if self.fn_attrs.is_empty() {
return String::new();
}
self.fn_attrs
.iter()
.map(|a| a.to_string())
.collect::<Vec<_>>()
.join(" ")
}
pub fn emit_ret_attr_string(&self) -> String {
if self.ret_attrs.is_empty() {
return String::new();
}
self.ret_attrs
.iter()
.map(|a| a.to_string())
.collect::<Vec<_>>()
.join(" ")
}
pub fn emit_param_attr_string(&self, param_idx: u32) -> String {
let attrs = self.get_param_attrs(param_idx);
if attrs.is_empty() {
return String::new();
}
attrs
.iter()
.map(|a| a.to_string())
.collect::<Vec<_>>()
.join(" ")
}
pub fn merge(&mut self, other: &AttributeList) {
for attr in &other.fn_attrs {
self.add_fn_attr(attr.clone());
}
for attr in &other.ret_attrs {
self.add_ret_attr(attr.clone());
}
for (i, attrs) in other.param_attrs.iter().enumerate() {
for attr in attrs {
self.add_param_attr(i as u32, attr.clone());
}
}
for (id, group) in &other.groups {
self.groups.insert(*id, group.clone());
}
}
}
impl Default for AttributeList {
fn default() -> Self {
Self::new()
}
}
pub struct AttrIndex;
impl AttrIndex {
pub const RETURN: u32 = 0;
pub const FUNCTION: u32 = u32::MAX; pub const FIRST_PARAM: u32 = 1;
pub fn is_return(idx: u32) -> bool {
idx == Self::RETURN
}
pub fn is_function(idx: u32) -> bool {
idx == Self::FUNCTION
}
pub fn is_param(idx: u32) -> bool {
idx != Self::RETURN && idx != Self::FUNCTION
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct AttributeSet {
pub index: u32,
pub fn_attrs: Vec<AttributeKind>,
pub param_attrs: Vec<ParamAttrKind>,
}
impl AttributeSet {
pub fn new(index: u32) -> Self {
Self {
index,
fn_attrs: Vec::new(),
param_attrs: Vec::new(),
}
}
pub fn for_function(attrs: Vec<AttributeKind>) -> Self {
Self {
index: AttrIndex::FUNCTION,
fn_attrs: attrs,
param_attrs: Vec::new(),
}
}
pub fn for_return(param_attrs: Vec<ParamAttrKind>) -> Self {
Self {
index: AttrIndex::RETURN,
fn_attrs: Vec::new(),
param_attrs,
}
}
pub fn for_param(param_idx: u32, param_attrs: Vec<ParamAttrKind>) -> Self {
Self {
index: param_idx,
fn_attrs: Vec::new(),
param_attrs,
}
}
pub fn add_fn_attr(&mut self, attr: AttributeKind) {
if !self.fn_attrs.contains(&attr) {
self.fn_attrs.push(attr);
}
}
pub fn add_param_attr(&mut self, attr: ParamAttrKind) {
if !self.param_attrs.contains(&attr) {
self.param_attrs.push(attr);
}
}
pub fn has_fn_attr(&self, attr: &AttributeKind) -> bool {
self.fn_attrs.contains(attr)
}
pub fn has_param_attr(&self, attr: &ParamAttrKind) -> bool {
self.param_attrs.contains(attr)
}
pub fn has_fn_attr_kind(&self, name: &str) -> bool {
self.fn_attrs
.iter()
.any(|a| a.to_string().starts_with(name))
}
pub fn get_alignment(&self) -> Option<u32> {
self.param_attrs.iter().find_map(|a| {
if let ParamAttrKind::Align(n) = a {
Some(*n)
} else {
None
}
})
}
pub fn get_dereferenceable_bytes(&self) -> Option<u64> {
self.param_attrs.iter().find_map(|a| {
if let ParamAttrKind::Dereferenceable(n) = a {
Some(*n)
} else {
None
}
})
}
pub fn is_non_null(&self) -> bool {
self.has_param_attr(&ParamAttrKind::NonNull)
}
pub fn is_no_capture(&self) -> bool {
self.has_param_attr(&ParamAttrKind::NoCapture)
}
pub fn is_no_alias(&self) -> bool {
self.has_param_attr(&ParamAttrKind::NoAlias)
}
pub fn is_sign_ext(&self) -> bool {
self.has_param_attr(&ParamAttrKind::SignExt) || self.has_param_attr(&ParamAttrKind::SExt)
}
pub fn is_zero_ext(&self) -> bool {
self.has_param_attr(&ParamAttrKind::ZeroExt)
}
pub fn num_fn_attrs(&self) -> usize {
self.fn_attrs.len()
}
pub fn num_param_attrs(&self) -> usize {
self.param_attrs.len()
}
pub fn is_empty(&self) -> bool {
self.fn_attrs.is_empty() && self.param_attrs.is_empty()
}
pub fn merge(&mut self, other: &AttributeSet) {
for attr in &other.fn_attrs {
self.add_fn_attr(attr.clone());
}
for attr in &other.param_attrs {
self.add_param_attr(attr.clone());
}
}
pub fn to_llvm_string(&self) -> String {
let mut parts = Vec::new();
for attr in &self.fn_attrs {
parts.push(attr.to_string());
}
for attr in &self.param_attrs {
parts.push(attr.to_string());
}
parts.join(" ")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_attribute_kind_from_string_standard() {
assert_eq!(
AttributeKind::from_string("noinline"),
Some(AttributeKind::NoInline)
);
assert_eq!(
AttributeKind::from_string("alwaysinline"),
Some(AttributeKind::AlwaysInline)
);
assert_eq!(
AttributeKind::from_string("optnone"),
Some(AttributeKind::OptimizeNone)
);
assert_eq!(
AttributeKind::from_string("readnone"),
Some(AttributeKind::ReadNone)
);
assert_eq!(
AttributeKind::from_string("readonly"),
Some(AttributeKind::ReadOnly)
);
assert_eq!(
AttributeKind::from_string("nounwind"),
Some(AttributeKind::NoUnwind)
);
assert_eq!(
AttributeKind::from_string("noreturn"),
Some(AttributeKind::NoReturn)
);
assert_eq!(
AttributeKind::from_string("cold"),
Some(AttributeKind::Cold)
);
assert_eq!(AttributeKind::from_string("hot"), Some(AttributeKind::Hot));
}
#[test]
fn test_attribute_kind_from_string_memory_aliases() {
assert_eq!(
AttributeKind::from_string("memory(none)"),
Some(AttributeKind::ReadNone)
);
assert_eq!(
AttributeKind::from_string("memory(read)"),
Some(AttributeKind::ReadOnly)
);
assert_eq!(
AttributeKind::from_string("memory(write)"),
Some(AttributeKind::WriteOnly)
);
}
#[test]
fn test_attribute_kind_from_string_sanitizers() {
assert_eq!(
AttributeKind::from_string("sanitize_address"),
Some(AttributeKind::SanitizeAddress)
);
assert_eq!(
AttributeKind::from_string("sanitize_thread"),
Some(AttributeKind::SanitizeThread)
);
assert_eq!(
AttributeKind::from_string("sanitize_memory"),
Some(AttributeKind::SanitizeMemory)
);
assert_eq!(
AttributeKind::from_string("nosanitize"),
Some(AttributeKind::NoSanitize)
);
}
#[test]
fn test_attribute_kind_from_string_security() {
assert_eq!(AttributeKind::from_string("ssp"), Some(AttributeKind::Ssp));
assert_eq!(
AttributeKind::from_string("sspreq"),
Some(AttributeKind::SspReq)
);
assert_eq!(
AttributeKind::from_string("sspstrong"),
Some(AttributeKind::SspStrong)
);
assert_eq!(
AttributeKind::from_string("shadowcallstack"),
Some(AttributeKind::ShadowCallStack)
);
assert_eq!(
AttributeKind::from_string("nocf_check"),
Some(AttributeKind::NoCfCheck)
);
}
#[test]
fn test_attribute_kind_from_string_unknown() {
assert_eq!(AttributeKind::from_string("nonexistent"), None);
assert_eq!(AttributeKind::from_string(""), None);
}
#[test]
fn test_attribute_kind_to_string_roundtrip() {
let attrs = vec![
AttributeKind::NoInline,
AttributeKind::AlwaysInline,
AttributeKind::ReadNone,
AttributeKind::NoUnwind,
AttributeKind::NoReturn,
AttributeKind::Cold,
AttributeKind::Hot,
];
for attr in attrs {
let s = attr.to_string();
let parsed = AttributeKind::from_string(&s);
assert!(
parsed.is_some(),
"Failed roundtrip for {:?} via string '{}'",
attr,
s
);
}
}
#[test]
fn test_attribute_kind_is_memory_attr() {
assert!(AttributeKind::ReadNone.is_memory_attr());
assert!(AttributeKind::ReadOnly.is_memory_attr());
assert!(AttributeKind::WriteOnly.is_memory_attr());
assert!(AttributeKind::ArgMemOnly.is_memory_attr());
assert!(AttributeKind::InaccessibleMemOnly.is_memory_attr());
assert!(!AttributeKind::NoInline.is_memory_attr());
assert!(!AttributeKind::Cold.is_memory_attr());
}
#[test]
fn test_attribute_kind_is_sanitizer_attr() {
assert!(AttributeKind::SanitizeAddress.is_sanitizer_attr());
assert!(AttributeKind::SanitizeThread.is_sanitizer_attr());
assert!(AttributeKind::NoSanitize.is_sanitizer_attr());
assert!(!AttributeKind::NoInline.is_sanitizer_attr());
}
#[test]
fn test_attribute_kind_is_security_attr() {
assert!(AttributeKind::Ssp.is_security_attr());
assert!(AttributeKind::SspReq.is_security_attr());
assert!(AttributeKind::ShadowCallStack.is_security_attr());
assert!(!AttributeKind::NoInline.is_security_attr());
}
#[test]
fn test_param_attr_kind_from_string_simple() {
assert_eq!(
ParamAttrKind::from_string("zeroext"),
Some(ParamAttrKind::ZeroExt)
);
assert_eq!(
ParamAttrKind::from_string("signext"),
Some(ParamAttrKind::SignExt)
);
assert_eq!(
ParamAttrKind::from_string("noalias"),
Some(ParamAttrKind::NoAlias)
);
assert_eq!(
ParamAttrKind::from_string("nocapture"),
Some(ParamAttrKind::NoCapture)
);
assert_eq!(
ParamAttrKind::from_string("nonnull"),
Some(ParamAttrKind::NonNull)
);
assert_eq!(
ParamAttrKind::from_string("inreg"),
Some(ParamAttrKind::InReg)
);
assert_eq!(
ParamAttrKind::from_string("returned"),
Some(ParamAttrKind::Returned)
);
assert_eq!(
ParamAttrKind::from_string("immarg"),
Some(ParamAttrKind::ImmArg)
);
assert_eq!(
ParamAttrKind::from_string("noundef"),
Some(ParamAttrKind::NoUndef)
);
}
#[test]
fn test_param_attr_kind_from_string_numeric() {
assert_eq!(
ParamAttrKind::from_string("align 8"),
Some(ParamAttrKind::Align(8))
);
assert_eq!(
ParamAttrKind::from_string("align 16"),
Some(ParamAttrKind::Align(16))
);
assert_eq!(
ParamAttrKind::from_string("dereferenceable(32)"),
Some(ParamAttrKind::Dereferenceable(32))
);
assert_eq!(
ParamAttrKind::from_string("dereferenceable_or_null(64)"),
Some(ParamAttrKind::DereferenceableOrNull(64))
);
}
#[test]
fn test_param_attr_kind_from_string_invalid() {
assert_eq!(ParamAttrKind::from_string("notanattr"), None);
assert_eq!(ParamAttrKind::from_string("align"), None);
assert_eq!(ParamAttrKind::from_string("align abc"), None);
}
#[test]
fn test_param_attr_kind_to_string_roundtrip_simple() {
let attrs = vec![
ParamAttrKind::ZeroExt,
ParamAttrKind::SignExt,
ParamAttrKind::NoAlias,
ParamAttrKind::NoCapture,
ParamAttrKind::NonNull,
ParamAttrKind::InReg,
ParamAttrKind::Returned,
];
for attr in attrs {
let s = attr.to_string();
let parsed = ParamAttrKind::from_string(&s);
assert_eq!(
Some(attr.clone()),
parsed,
"Failed roundtrip via string '{}'",
s
);
}
}
#[test]
fn test_param_attr_kind_to_string_numeric() {
assert_eq!(ParamAttrKind::Align(8).to_string(), "align 8");
assert_eq!(
ParamAttrKind::Dereferenceable(16).to_string(),
"dereferenceable(16)"
);
assert_eq!(
ParamAttrKind::DereferenceableOrNull(32).to_string(),
"dereferenceable_or_null(32)"
);
}
#[test]
fn test_param_attr_kind_is_type_attr() {
assert!(!ParamAttrKind::ZeroExt.is_type_attr());
assert!(!ParamAttrKind::Align(8).is_type_attr());
}
#[test]
fn test_param_attr_kind_kind_discriminant() {
assert_eq!(ParamAttrKind::ZeroExt.kind_discriminant(), "zeroext");
assert_eq!(ParamAttrKind::Align(8).kind_discriminant(), "align");
assert_eq!(
ParamAttrKind::Dereferenceable(4).kind_discriminant(),
"dereferenceable"
);
}
#[test]
fn test_attr_group_new() {
let group = AttrGroup::new(42);
assert_eq!(group.id, 42);
assert!(group.is_empty());
assert_eq!(group.len(), 0);
}
#[test]
fn test_attr_group_add_and_check() {
let mut group = AttrGroup::new(1);
group.add_attr(AttributeKind::NoInline);
group.add_attr(AttributeKind::NoUnwind);
assert_eq!(group.len(), 2);
assert!(group.has_attr(&AttributeKind::NoInline));
assert!(group.has_attr(&AttributeKind::NoUnwind));
assert!(!group.has_attr(&AttributeKind::Cold));
}
#[test]
fn test_attr_group_remove() {
let mut group = AttrGroup::new(1);
group.add_attr(AttributeKind::NoInline);
group.add_attr(AttributeKind::NoUnwind);
group.remove_attr(&AttributeKind::NoInline);
assert_eq!(group.len(), 1);
assert!(!group.has_attr(&AttributeKind::NoInline));
assert!(group.has_attr(&AttributeKind::NoUnwind));
}
#[test]
fn test_attr_group_no_duplicates() {
let mut group = AttrGroup::new(1);
group.add_attr(AttributeKind::NoInline);
group.add_attr(AttributeKind::NoInline);
assert_eq!(group.len(), 1);
}
#[test]
fn test_attribute_list_new_empty() {
let list = AttributeList::new();
assert!(list.is_empty());
assert_eq!(list.fn_attrs.len(), 0);
assert_eq!(list.ret_attrs.len(), 0);
assert_eq!(list.num_param_slots(), 0);
}
#[test]
fn test_attribute_list_add_fn_attrs() {
let mut list = AttributeList::new();
list.add_fn_attr(AttributeKind::NoInline);
list.add_fn_attr(AttributeKind::ReadNone);
assert!(list.has_fn_attr(&AttributeKind::NoInline));
assert!(list.has_fn_attr(&AttributeKind::ReadNone));
assert!(!list.has_fn_attr(&AttributeKind::Cold));
}
#[test]
fn test_attribute_list_add_param_attr() {
let mut list = AttributeList::new();
list.add_param_attr(0, ParamAttrKind::ZeroExt);
list.add_param_attr(0, ParamAttrKind::NonNull);
list.add_param_attr(1, ParamAttrKind::SignExt);
let p0 = list.get_param_attrs(0);
assert_eq!(p0.len(), 2);
assert!(p0.contains(&ParamAttrKind::ZeroExt));
assert!(p0.contains(&ParamAttrKind::NonNull));
let p1 = list.get_param_attrs(1);
assert_eq!(p1.len(), 1);
assert!(p1.contains(&ParamAttrKind::SignExt));
let p2 = list.get_param_attrs(2);
assert!(p2.is_empty());
}
#[test]
fn test_attribute_list_merge() {
let mut list1 = AttributeList::new();
list1.add_fn_attr(AttributeKind::NoInline);
list1.add_param_attr(0, ParamAttrKind::ZeroExt);
let mut list2 = AttributeList::new();
list2.add_fn_attr(AttributeKind::NoUnwind);
list2.add_param_attr(0, ParamAttrKind::NonNull);
list2.add_param_attr(1, ParamAttrKind::SignExt);
list1.merge(&list2);
assert!(list1.has_fn_attr(&AttributeKind::NoInline));
assert!(list1.has_fn_attr(&AttributeKind::NoUnwind));
let p0 = list1.get_param_attrs(0);
assert_eq!(p0.len(), 2);
assert!(p0.contains(&ParamAttrKind::ZeroExt));
assert!(p0.contains(&ParamAttrKind::NonNull));
let p1 = list1.get_param_attrs(1);
assert_eq!(p1.len(), 1);
assert!(p1.contains(&ParamAttrKind::SignExt));
}
#[test]
fn test_attribute_list_from_parts() {
let list = AttributeList::from_parts(
&["noinline", "nounwind"],
&["zeroext"],
&[vec!["nocapture", "nonnull"], vec!["signext"]],
);
assert!(list.has_fn_attr(&AttributeKind::NoInline));
assert!(list.has_fn_attr(&AttributeKind::NoUnwind));
assert!(list.has_ret_attr(&ParamAttrKind::ZeroExt));
let p0 = list.get_param_attrs(0);
assert!(p0.contains(&ParamAttrKind::NoCapture));
assert!(p0.contains(&ParamAttrKind::NonNull));
let p1 = list.get_param_attrs(1);
assert!(p1.contains(&ParamAttrKind::SignExt));
}
#[test]
fn test_attribute_list_emit_fn_attr_string() {
let mut list = AttributeList::new();
list.add_fn_attr(AttributeKind::NoInline);
list.add_fn_attr(AttributeKind::ReadNone);
let s = list.emit_fn_attr_string();
assert!(s.contains("noinline"));
assert!(s.contains("readnone"));
}
#[test]
fn test_attribute_list_groups() {
let mut list = AttributeList::new();
let id = list.create_group(vec![AttributeKind::NoInline, AttributeKind::ReadNone]);
let group = list.get_group(id);
assert!(group.is_some());
let group = group.unwrap();
assert_eq!(group.id, id);
assert!(group.has_attr(&AttributeKind::NoInline));
assert!(group.has_attr(&AttributeKind::ReadNone));
list.remove_group(id);
assert!(list.get_group(id).is_none());
}
#[test]
fn test_attribute_list_string_attribute() {
let attr =
AttributeKind::StringAttribute("my_custom_attr".to_string(), "some_value".to_string());
let s = attr.to_string();
assert!(s.contains("my_custom_attr"));
assert!(s.contains("some_value"));
}
#[test]
fn test_attribute_kind_minsize_and_optforsize() {
let mut list = AttributeList::new();
list.add_fn_attr(AttributeKind::Minsize);
list.add_fn_attr(AttributeKind::OptimizeForSize);
assert!(list.has_fn_attr(&AttributeKind::Minsize));
assert!(list.has_fn_attr(&AttributeKind::OptimizeForSize));
}
#[test]
fn test_param_attr_no_duplicates() {
let mut list = AttributeList::new();
list.add_param_attr(0, ParamAttrKind::NoAlias);
list.add_param_attr(0, ParamAttrKind::NoAlias);
assert_eq!(list.get_param_attrs(0).len(), 1);
}
#[test]
fn test_attribute_list_default() {
let list = AttributeList::default();
assert!(list.is_empty());
}
#[test]
fn test_attribute_kind_no_sync_no_free() {
assert_eq!(
AttributeKind::from_string("nosync"),
Some(AttributeKind::NoSync)
);
assert_eq!(
AttributeKind::from_string("nofree"),
Some(AttributeKind::NoFree)
);
assert_eq!(
AttributeKind::from_string("norecurse"),
Some(AttributeKind::NoRecurse)
);
assert_eq!(
AttributeKind::from_string("noduplicate"),
Some(AttributeKind::NoDuplicate)
);
}
#[test]
fn test_attribute_kind_willreturn() {
assert_eq!(
AttributeKind::from_string("willreturn"),
Some(AttributeKind::WillReturn)
);
assert_eq!(AttributeKind::WillReturn.to_string(), "willreturn");
}
#[test]
fn test_attribute_kind_null_pointer_is_valid() {
let attr = AttributeKind::NullPointerIsValid;
assert_eq!(attr.to_string(), "null_pointer_is_valid");
assert_eq!(
AttributeKind::from_string("null_pointer_is_valid"),
Some(AttributeKind::NullPointerIsValid)
);
}
}