use crate::{container::Container, detect::DetectionMatches};
use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GcMode {
Refc,
ArcOrc,
Unknown,
}
impl GcMode {
pub fn as_str(&self) -> &'static str {
match self {
Self::Refc => "Refc",
Self::ArcOrc => "ArcOrc",
Self::Unknown => "Unknown",
}
}
}
impl fmt::Display for GcMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NimVersionHint {
Nim1xRefc,
Nim2xArc,
Nim2xOrc,
Unknown,
}
impl NimVersionHint {
pub fn as_str(&self) -> &'static str {
match self {
Self::Nim1xRefc => "Nim1xRefc",
Self::Nim2xArc => "Nim2xArc",
Self::Nim2xOrc => "Nim2xOrc",
Self::Unknown => "Unknown",
}
}
}
impl fmt::Display for NimVersionHint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
pub fn detect_compiler_version(
container: &Container<'_>,
matches: DetectionMatches,
) -> NimVersionHint {
match gc_mode(matches) {
GcMode::ArcOrc => {
if has_cycle_collector(container) {
NimVersionHint::Nim2xOrc
} else {
NimVersionHint::Nim2xArc
}
}
GcMode::Refc => NimVersionHint::Nim1xRefc,
GcMode::Unknown => NimVersionHint::Unknown,
}
}
fn has_cycle_collector(container: &Container<'_>) -> bool {
container.symbols().iter().any(|s| {
let name = s.name.as_ref();
name.contains("collectCycles") || name.contains("collectCyclesBacon")
})
}
pub fn gc_mode(matches: DetectionMatches) -> GcMode {
let has_v2 = matches.contains(DetectionMatches::NTIV2_SYMBOL);
let has_legacy = matches.contains(DetectionMatches::NTI_LEGACY_SYMBOL);
match (has_v2, has_legacy) {
(true, _) => GcMode::ArcOrc,
(false, true) => GcMode::Refc,
_ => GcMode::Unknown,
}
}
pub fn nim_main_prefix<'a>(container: &'a Container<'a>) -> Option<&'a str> {
for sym in container.symbols() {
let name = sym.name.as_ref();
let stripped = name.strip_prefix('_').unwrap_or(name);
if stripped == "NimMain" {
return Some("");
}
if let Some(prefix) = stripped.strip_suffix("NimMain")
&& !prefix.is_empty()
&& !stripped.contains("__")
{
if prefix
.bytes()
.all(|b| b.is_ascii_alphanumeric() || b == b'_')
{
return Some(prefix);
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compiler_version_splits_arc_orc_via_cycle_collector() {
use crate::container::{self, Arch, Format, Symbol, SymbolKind};
use std::borrow::Cow;
let bytes = [0u8; 4];
let orc_syms = vec![Symbol {
name: Cow::Borrowed("collectCyclesBacon__system_u3313"),
vm_addr: 0x1000,
size: 0,
kind: SymbolKind::Function,
}];
let orc = container::assemble(&bytes, Format::Elf, Arch::Amd64, 0, vec![], orc_syms);
assert_eq!(
detect_compiler_version(&orc, DetectionMatches::NTIV2_SYMBOL),
NimVersionHint::Nim2xOrc
);
let arc = container::assemble(&bytes, Format::Elf, Arch::Amd64, 0, vec![], vec![]);
assert_eq!(
detect_compiler_version(&arc, DetectionMatches::NTIV2_SYMBOL),
NimVersionHint::Nim2xArc
);
assert_eq!(
detect_compiler_version(&arc, DetectionMatches::NTI_LEGACY_SYMBOL),
NimVersionHint::Nim1xRefc
);
assert_eq!(
detect_compiler_version(&arc, DetectionMatches::EMPTY),
NimVersionHint::Unknown
);
}
#[test]
fn gc_mode_from_flags() {
assert_eq!(gc_mode(DetectionMatches::NTIV2_SYMBOL), GcMode::ArcOrc);
assert_eq!(gc_mode(DetectionMatches::NTI_LEGACY_SYMBOL), GcMode::Refc);
assert_eq!(gc_mode(DetectionMatches::EMPTY), GcMode::Unknown);
assert_eq!(
gc_mode(DetectionMatches::NTIV2_SYMBOL | DetectionMatches::NTI_LEGACY_SYMBOL),
GcMode::ArcOrc
);
}
}