use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
use std::fmt;
use crate::clang::ast::*;
use crate::clang::sema::*;
use crate::clang::*;
use crate::x86::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IncludeKind {
System,
User,
Framework,
}
impl fmt::Display for IncludeKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IncludeKind::System => write!(f, "system"),
IncludeKind::User => write!(f, "user"),
IncludeKind::Framework => write!(f, "framework"),
}
}
}
#[derive(Debug, Clone)]
pub struct IncludeRecord {
pub written_path: String,
pub resolved_path: Option<String>,
pub kind: IncludeKind,
pub line: u32,
pub found: bool,
pub nested: Vec<IncludeRecord>,
}
impl IncludeRecord {
pub fn system(path: &str, line: u32) -> Self {
Self {
written_path: path.to_string(),
resolved_path: None,
kind: IncludeKind::System,
line,
found: false,
nested: Vec::new(),
}
}
pub fn user(path: &str, line: u32) -> Self {
Self {
written_path: path.to_string(),
resolved_path: None,
kind: IncludeKind::User,
line,
found: false,
nested: Vec::new(),
}
}
pub fn framework(path: &str, line: u32) -> Self {
Self {
written_path: path.to_string(),
resolved_path: None,
kind: IncludeKind::Framework,
line,
found: false,
nested: Vec::new(),
}
}
pub fn resolved(mut self, path: &str) -> Self {
self.found = true;
self.resolved_path = Some(path.to_string());
self
}
pub fn add_nested(&mut self, nested: IncludeRecord) {
self.nested.push(nested);
}
pub fn total_count(&self) -> usize {
1 + self.nested.iter().map(|n| n.total_count()).sum::<usize>()
}
pub fn missing(&self) -> Vec<&IncludeRecord> {
let mut result = Vec::new();
if !self.found {
result.push(self);
}
for n in &self.nested {
result.extend(n.missing());
}
result
}
}
#[derive(Debug, Clone)]
pub struct DefineRecord {
pub name: String,
pub value: Option<String>,
pub line: u32,
pub from_command_line: bool,
pub is_builtin: bool,
}
impl DefineRecord {
pub fn new(name: &str, value: Option<&str>, line: u32) -> Self {
Self {
name: name.to_string(),
value: value.map(|s| s.to_string()),
line,
from_command_line: false,
is_builtin: false,
}
}
pub fn command_line(mut self) -> Self {
self.from_command_line = true;
self
}
pub fn builtin(mut self) -> Self {
self.is_builtin = true;
self
}
pub fn expanded_value(&self) -> &str {
match &self.value {
Some(v) => v.as_str(),
None => "1",
}
}
}
#[derive(Debug, Clone)]
pub struct TargetTripleInfo {
pub triple: String,
pub arch: String,
pub vendor: String,
pub os: String,
pub environment: String,
pub is_64bit: bool,
pub data_model: DataModel,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DataModel {
LP64,
ILP32,
LLP64,
}
impl fmt::Display for DataModel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DataModel::LP64 => write!(f, "LP64"),
DataModel::ILP32 => write!(f, "ILP32"),
DataModel::LLP64 => write!(f, "LLP64"),
}
}
}
impl DataModel {
pub fn long_size(&self) -> usize {
match self {
DataModel::LP64 => 8,
DataModel::ILP32 => 4,
DataModel::LLP64 => 4,
}
}
pub fn pointer_size(&self) -> usize {
match self {
DataModel::LP64 => 8,
DataModel::ILP32 => 4,
DataModel::LLP64 => 8,
}
}
pub fn size_t_size(&self) -> usize {
self.pointer_size()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LanguageStandard {
pub standard: String,
pub language: SourceLanguage,
pub gnu_extensions: bool,
pub msvc_extensions: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SourceLanguage {
C,
Cxx,
ObjC,
ObjCxx,
OpenCL,
CUDA,
}
impl fmt::Display for SourceLanguage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SourceLanguage::C => write!(f, "C"),
SourceLanguage::Cxx => write!(f, "C++"),
SourceLanguage::ObjC => write!(f, "Objective-C"),
SourceLanguage::ObjCxx => write!(f, "Objective-C++"),
SourceLanguage::OpenCL => write!(f, "OpenCL"),
SourceLanguage::CUDA => write!(f, "CUDA"),
}
}
}
impl LanguageStandard {
pub fn c17() -> Self {
Self {
standard: "c17".to_string(),
language: SourceLanguage::C,
gnu_extensions: false,
msvc_extensions: false,
}
}
pub fn cxx17() -> Self {
Self {
standard: "c++17".to_string(),
language: SourceLanguage::Cxx,
gnu_extensions: false,
msvc_extensions: false,
}
}
pub fn gnu17() -> Self {
Self {
standard: "gnu17".to_string(),
language: SourceLanguage::C,
gnu_extensions: true,
msvc_extensions: false,
}
}
pub fn is_cxx(&self) -> bool {
matches!(self.language, SourceLanguage::Cxx | SourceLanguage::ObjCxx)
}
pub fn is_c(&self) -> bool {
matches!(self.language, SourceLanguage::C | SourceLanguage::ObjC)
}
}
#[derive(Debug, Clone)]
pub struct SourceFileInfo {
pub path: String,
pub name: String,
pub extension: String,
pub line_count: usize,
pub size_bytes: u64,
pub is_main: bool,
}
impl SourceFileInfo {
pub fn new(path: &str) -> Self {
let name = std::path::Path::new(path)
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| path.to_string());
let extension = std::path::Path::new(path)
.extension()
.map(|e| format!(".{}", e.to_string_lossy()))
.unwrap_or_default();
Self {
path: path.to_string(),
name,
extension,
line_count: 0,
size_bytes: 0,
is_main: false,
}
}
pub fn main(mut self) -> Self {
self.is_main = true;
self
}
pub fn with_stats(mut self, lines: usize, bytes: u64) -> Self {
self.line_count = lines;
self.size_bytes = bytes;
self
}
pub fn is_c_source(&self) -> bool {
matches!(self.extension.as_str(), ".c" | ".h")
}
pub fn is_cxx_source(&self) -> bool {
matches!(
self.extension.as_str(),
".cpp" | ".cxx" | ".cc" | ".c++" | ".C" | ".hpp" | ".hxx" | ".hh" | ".h++"
)
}
}
#[derive(Debug, Clone)]
pub struct X86TranslationUnitFix {
pub tu: TranslationUnit,
pub system_includes: Vec<IncludeRecord>,
pub user_includes: Vec<IncludeRecord>,
pub framework_includes: Vec<IncludeRecord>,
pub defines: Vec<DefineRecord>,
pub target: TargetTripleInfo,
pub language_standard: LanguageStandard,
pub source_file: SourceFileInfo,
pub additional_sources: Vec<SourceFileInfo>,
pub compiler_flags: Vec<String>,
pub warning_flags: Vec<String>,
}
impl X86TranslationUnitFix {
pub fn new(tu: TranslationUnit) -> Self {
Self {
tu,
system_includes: Vec::new(),
user_includes: Vec::new(),
framework_includes: Vec::new(),
defines: Vec::new(),
target: TargetTripleInfo {
triple: "x86_64-unknown-linux-gnu".to_string(),
arch: "x86_64".to_string(),
vendor: "unknown".to_string(),
os: "linux".to_string(),
environment: "gnu".to_string(),
is_64bit: true,
data_model: DataModel::LP64,
},
language_standard: LanguageStandard::c17(),
source_file: SourceFileInfo::new("source.c").main(),
additional_sources: Vec::new(),
compiler_flags: Vec::new(),
warning_flags: Vec::new(),
}
}
pub fn with_target(mut self, target: TargetTripleInfo) -> Self {
self.target = target;
self
}
pub fn with_language(mut self, lang: LanguageStandard) -> Self {
self.language_standard = lang;
self
}
pub fn with_source(mut self, source: SourceFileInfo) -> Self {
self.source_file = source;
self
}
pub fn add_system_include(&mut self, include: IncludeRecord) {
self.system_includes.push(include);
}
pub fn add_user_include(&mut self, include: IncludeRecord) {
self.user_includes.push(include);
}
pub fn add_framework_include(&mut self, include: IncludeRecord) {
self.framework_includes.push(include);
}
pub fn add_define(&mut self, define: DefineRecord) {
self.defines.push(define);
}
pub fn add_flag(&mut self, flag: &str) {
self.compiler_flags.push(flag.to_string());
}
pub fn add_warning(&mut self, warning: &str) {
self.warning_flags.push(warning.to_string());
}
pub fn all_includes(&self) -> Vec<&IncludeRecord> {
let mut result = Vec::new();
result.extend(self.system_includes.iter());
result.extend(self.user_includes.iter());
result.extend(self.framework_includes.iter());
result
}
pub fn find_define(&self, name: &str) -> Option<&DefineRecord> {
self.defines.iter().find(|d| d.name == name)
}
pub fn has_define(&self, name: &str) -> bool {
self.defines.iter().any(|d| d.name == name)
}
pub fn define_value(&self, name: &str) -> Option<&str> {
self.find_define(name).and_then(|d| d.value.as_deref())
}
pub fn total_include_count(&self) -> usize {
self.system_includes
.iter()
.map(|i| i.total_count())
.sum::<usize>()
+ self
.user_includes
.iter()
.map(|i| i.total_count())
.sum::<usize>()
+ self
.framework_includes
.iter()
.map(|i| i.total_count())
.sum::<usize>()
}
pub fn missing_includes(&self) -> Vec<&IncludeRecord> {
let mut result = Vec::new();
for inc in &self.system_includes {
result.extend(inc.missing());
}
for inc in &self.user_includes {
result.extend(inc.missing());
}
for inc in &self.framework_includes {
result.extend(inc.missing());
}
result
}
}
impl fmt::Display for X86TranslationUnitFix {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "TranslationUnitFix {{")?;
writeln!(f, " source: {}", self.source_file.name)?;
writeln!(f, " target: {}", self.target.triple)?;
writeln!(f, " standard: {}", self.language_standard.standard)?;
writeln!(
f,
" includes: {} (system: {}, user: {}, framework: {})",
self.total_include_count(),
self.system_includes.len(),
self.user_includes.len(),
self.framework_includes.len(),
)?;
writeln!(f, " defines: {}", self.defines.len())?;
writeln!(f, " decls: {}", self.tu.decls.len())?;
write!(f, "}}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TypeCheckMode {
Strict,
Structural,
Assignment,
Overload,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum X86FixIntRank {
Bool = 0,
Char = 1,
SChar = 2,
UChar = 3,
Short = 4,
UShort = 5,
Int = 6,
UInt = 7,
Long = 8,
ULong = 9,
LongLong = 10,
ULongLong = 11,
}
impl X86FixIntRank {
pub fn from_type(ty: &QualType) -> Option<Self> {
if ty.is_bool() {
return Some(Self::Bool);
}
if ty.is_char() {
return Some(Self::Char);
}
if ty.is_short() {
return Some(Self::Short);
}
if ty.is_int() {
return Some(Self::Int);
}
if ty.is_long() {
return Some(Self::Long);
}
if ty.is_long_long() {
return Some(Self::LongLong);
}
None
}
pub fn is_signed(&self) -> bool {
matches!(
self,
Self::Bool
| Self::Char
| Self::SChar
| Self::Short
| Self::Int
| Self::Long
| Self::LongLong
)
}
pub fn is_unsigned(&self) -> bool {
!self.is_signed()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PtrConversionKind {
None,
Qualifier,
DerivedToBase,
VoidPtr,
Incompatible,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArrayFuncConvKind {
None,
ArrayToPtr,
FuncToPtr,
BothDecay,
}
#[derive(Debug, Clone)]
pub struct CompoundTypeCompat {
pub compatible: bool,
pub reason: Option<String>,
pub common_type: Option<QualType>,
}
pub struct X86TypeChecker {
pub data_model: DataModel,
pub mode: TypeCheckMode,
pub allow_lossy: bool,
pub typedefs: HashMap<String, QualType>,
pub structs: HashMap<String, Vec<(String, QualType)>>,
pub enums: HashMap<String, Vec<(String, i64)>>,
}
impl X86TypeChecker {
pub fn new(data_model: DataModel) -> Self {
Self {
data_model,
mode: TypeCheckMode::Strict,
allow_lossy: false,
typedefs: HashMap::new(),
structs: HashMap::new(),
enums: HashMap::new(),
}
}
pub fn with_mode(mut self, mode: TypeCheckMode) -> Self {
self.mode = mode;
self
}
pub fn register_typedef(&mut self, name: &str, underlying: QualType) {
self.typedefs.insert(name.to_string(), underlying);
}
pub fn register_struct(&mut self, name: &str, fields: Vec<(String, QualType)>) {
self.structs.insert(name.to_string(), fields);
}
pub fn register_enum(&mut self, name: &str, variants: Vec<(String, i64)>) {
self.enums.insert(name.to_string(), variants);
}
pub fn are_compatible(&self, a: &QualType, b: &QualType) -> bool {
self.check_compatibility(a, b).compatible
}
pub fn check_compatibility(&self, a: &QualType, b: &QualType) -> CompoundTypeCompat {
if a.is_void() || b.is_void() {
if a.is_void() && b.is_void() {
return CompoundTypeCompat {
compatible: true,
reason: None,
common_type: Some(QualType::void()),
};
}
return CompoundTypeCompat {
compatible: false,
reason: Some("void is only compatible with void".to_string()),
common_type: None,
};
}
if a.is_integer() && b.is_integer() {
return self.check_integer_compatibility(a, b);
}
if a.is_floating() && b.is_floating() {
return self.check_float_compatibility(a, b);
}
if (a.is_integer() && b.is_floating()) || (a.is_floating() && b.is_integer()) {
let (int_ty, float_ty) = if a.is_integer() { (a, b) } else { (b, a) };
return self.check_int_float_compatibility(int_ty, float_ty);
}
if a.is_pointer() && b.is_pointer() {
return self.check_pointer_compatibility(a, b);
}
if a.is_array() && b.is_array() {
return self.check_array_compatibility(a, b);
}
CompoundTypeCompat {
compatible: false,
reason: Some(format!("incompatible types: {} and {}", a, b)),
common_type: None,
}
}
fn check_integer_compatibility(&self, a: &QualType, b: &QualType) -> CompoundTypeCompat {
let rank_a = X86FixIntRank::from_type(a);
let rank_b = X86FixIntRank::from_type(b);
match (rank_a, rank_b) {
(Some(ra), Some(rb)) => {
let common = if ra >= rb { a.clone() } else { b.clone() };
let compatible = match self.mode {
TypeCheckMode::Strict => a.base == b.base,
TypeCheckMode::Structural
| TypeCheckMode::Assignment
| TypeCheckMode::Overload => true,
};
CompoundTypeCompat {
compatible,
reason: if !compatible {
Some(format!("different integer types: {} and {}", a, b))
} else {
None
},
common_type: if compatible || self.allow_lossy {
Some(common)
} else {
None
},
}
}
_ => CompoundTypeCompat {
compatible: false,
reason: Some("non-integer type in integer check".to_string()),
common_type: None,
},
}
}
fn check_float_compatibility(&self, a: &QualType, b: &QualType) -> CompoundTypeCompat {
let common = if a.is_long_double() || b.is_long_double() {
QualType::long_double()
} else if a.is_double() || b.is_double() {
QualType::double()
} else {
QualType::float()
};
CompoundTypeCompat {
compatible: true,
reason: None,
common_type: Some(common),
}
}
fn check_int_float_compatibility(
&self,
int_ty: &QualType,
float_ty: &QualType,
) -> CompoundTypeCompat {
let compatible = self.mode != TypeCheckMode::Strict || self.allow_lossy;
CompoundTypeCompat {
compatible,
reason: if !compatible {
Some("conversion between integer and float may lose precision".to_string())
} else {
None
},
common_type: if compatible {
Some(float_ty.clone())
} else {
None
},
}
}
fn check_pointer_compatibility(&self, a: &QualType, b: &QualType) -> CompoundTypeCompat {
if a.is_void_pointer() || b.is_void_pointer() {
return CompoundTypeCompat {
compatible: true,
reason: None,
common_type: Some(QualType::pointer_to(QualType::void())),
};
}
let compatible = a.base == b.base;
CompoundTypeCompat {
compatible,
reason: if !compatible {
Some(format!("incompatible pointer types: {} and {}", a, b))
} else {
None
},
common_type: if compatible { Some(a.clone()) } else { None },
}
}
fn check_array_compatibility(&self, a: &QualType, b: &QualType) -> CompoundTypeCompat {
CompoundTypeCompat {
compatible: true,
reason: None,
common_type: Some(a.clone()),
}
}
pub fn check_pointer_conversion(&self, from: &QualType, to: &QualType) -> PtrConversionKind {
if !from.is_pointer() || !to.is_pointer() {
return PtrConversionKind::Incompatible;
}
if from.is_void_pointer() || to.is_void_pointer() {
return PtrConversionKind::VoidPtr;
}
if from.base == to.base {
if (to.is_const && !from.is_const) || (to.is_volatile && !from.is_volatile) {
return PtrConversionKind::Qualifier;
}
return PtrConversionKind::None;
}
PtrConversionKind::Incompatible
}
pub fn check_array_func_conversion(&self, from: &QualType) -> ArrayFuncConvKind {
match from.base.as_ref() {
TypeNode::Array { .. } => ArrayFuncConvKind::ArrayToPtr,
TypeNode::Function { .. } => ArrayFuncConvKind::FuncToPtr,
_ => ArrayFuncConvKind::None,
}
}
pub fn check_compound_compatibility(
&self,
a_name: Option<&str>,
b_name: Option<&str>,
a_fields: &[(String, QualType)],
b_fields: &[(String, QualType)],
) -> CompoundTypeCompat {
match (a_name, b_name) {
(Some(an), Some(bn)) if an == bn => {
if a_fields.len() != b_fields.len() {
return CompoundTypeCompat {
compatible: false,
reason: Some(format!(
"struct/union '{}' has different number of fields ({} vs {})",
an,
a_fields.len(),
b_fields.len()
)),
common_type: None,
};
}
for ((af_name, af_ty), (bf_name, bf_ty)) in a_fields.iter().zip(b_fields.iter()) {
if af_name != bf_name {
return CompoundTypeCompat {
compatible: false,
reason: Some(format!(
"field name mismatch: '{}' vs '{}' in struct '{}'",
af_name, bf_name, an
)),
common_type: None,
};
}
if !self.are_compatible(af_ty, bf_ty) {
return CompoundTypeCompat {
compatible: false,
reason: Some(format!(
"field '{}' type mismatch: {} vs {} in struct '{}'",
af_name, af_ty, bf_ty, an
)),
common_type: None,
};
}
}
CompoundTypeCompat {
compatible: true,
reason: None,
common_type: None, }
}
_ => CompoundTypeCompat {
compatible: false,
reason: Some(
"anonymous or differently-named structs are not compatible".to_string(),
),
common_type: None,
},
}
}
pub fn integer_rank(&self, ty: &QualType) -> Option<X86FixIntRank> {
X86FixIntRank::from_type(ty)
}
pub fn compare_ranks(&self, a: &QualType, b: &QualType) -> Option<std::cmp::Ordering> {
let ra = self.integer_rank(a)?;
let rb = self.integer_rank(b)?;
Some(ra.cmp(&rb))
}
pub fn size_of(&self, ty: &QualType) -> usize {
match ty.base.as_ref() {
TypeNode::Void => 0,
TypeNode::Bool => 1,
TypeNode::Char | TypeNode::SChar | TypeNode::UChar => 1,
TypeNode::Short | TypeNode::UShort => 2,
TypeNode::Int | TypeNode::UInt => 4,
TypeNode::Long | TypeNode::ULong => self.data_model.long_size(),
TypeNode::LongLong | TypeNode::ULongLong => 8,
TypeNode::Float => 4,
TypeNode::Double => 8,
TypeNode::LongDouble => 16,
TypeNode::Pointer(..) => self.data_model.pointer_size(),
TypeNode::Array { elem, size } => {
let elem_size = self.size_of(elem);
elem_size * size.unwrap_or(0)
}
TypeNode::Struct { fields, .. } => fields.iter().map(|f| self.size_of(&f.ty)).sum(),
_ => 0,
}
}
pub fn can_implicitly_convert(&self, from: &QualType, to: &QualType) -> (bool, Option<String>) {
if self.are_compatible(from, to) {
return (true, None);
}
if from.is_integer() && to.is_integer() {
if let (Some(rf), Some(rt)) = (self.integer_rank(from), self.integer_rank(to)) {
if rf <= rt {
return (true, None);
}
if self.allow_lossy {
return (true, Some("narrowing conversion".to_string()));
}
}
}
if from.is_floating() && to.is_integer() {
return (
self.allow_lossy,
Some("float to integer conversion".to_string()),
);
}
if from.is_integer() && to.is_floating() {
return (true, None);
}
if to.is_pointer() {
return (true, None); }
if from.is_array() && to.is_pointer() {
return (true, None);
}
if matches!(from.base.as_ref(), TypeNode::Function { .. }) && to.is_pointer() {
return (true, None);
}
(false, Some(format!("cannot convert {} to {}", from, to)))
}
}
impl Default for X86TypeChecker {
fn default() -> Self {
Self::new(DataModel::LP64)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SideEffectClass {
None,
ReadOnly,
Pure,
Const,
HasEffects,
Unknown,
}
impl fmt::Display for SideEffectClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SideEffectClass::None => write!(f, "none"),
SideEffectClass::ReadOnly => write!(f, "readonly"),
SideEffectClass::Pure => write!(f, "pure"),
SideEffectClass::Const => write!(f, "const"),
SideEffectClass::HasEffects => write!(f, "has_effects"),
SideEffectClass::Unknown => write!(f, "unknown"),
}
}
}
#[derive(Debug, Clone)]
pub enum ExceptionSpec {
None,
NoExcept,
Throw(Vec<String>),
ConditionalNoExcept(bool),
}
impl ExceptionSpec {
pub fn can_throw(&self) -> bool {
match self {
ExceptionSpec::None => true,
ExceptionSpec::NoExcept => false,
ExceptionSpec::Throw(types) => !types.is_empty(),
ExceptionSpec::ConditionalNoExcept(can) => !can,
}
}
}
#[derive(Debug, Clone)]
pub struct RecursionInfo {
pub direct: bool,
pub mutual: bool,
pub mutual_cycle: Vec<String>,
pub tail_recursive: bool,
pub max_nesting: usize,
}
impl RecursionInfo {
pub fn none() -> Self {
Self {
direct: false,
mutual: false,
mutual_cycle: Vec::new(),
tail_recursive: false,
max_nesting: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct ControlFlowResult {
pub all_paths_return: bool,
pub any_path_returns: bool,
pub is_noreturn: bool,
pub unreachable_blocks: Vec<usize>,
pub fallthrough_risk: bool,
pub is_reachable: bool,
}
impl ControlFlowResult {
pub fn reachable() -> Self {
Self {
all_paths_return: false,
any_path_returns: false,
is_noreturn: false,
unreachable_blocks: Vec::new(),
fallthrough_risk: false,
is_reachable: true,
}
}
pub fn unreachable() -> Self {
Self {
all_paths_return: false,
any_path_returns: false,
is_noreturn: false,
unreachable_blocks: Vec::new(),
fallthrough_risk: false,
is_reachable: false,
}
}
}
#[derive(Debug, Clone)]
pub struct ParamUsage {
pub index: usize,
pub is_read: bool,
pub is_written: bool,
pub is_used: bool,
pub name: String,
}
pub struct X86FunctionChecker {
call_graph: HashMap<String, HashSet<String>>,
side_effects: HashMap<String, SideEffectClass>,
exception_specs: HashMap<String, ExceptionSpec>,
recursion_info: HashMap<String, RecursionInfo>,
current_function: Option<String>,
path_returns: Vec<bool>,
}
impl X86FunctionChecker {
pub fn new() -> Self {
Self {
call_graph: HashMap::new(),
side_effects: HashMap::new(),
exception_specs: HashMap::new(),
recursion_info: HashMap::new(),
current_function: None,
path_returns: Vec::new(),
}
}
pub fn register_function(&mut self, name: &str, called: &[String]) {
self.call_graph
.entry(name.to_string())
.or_insert_with(HashSet::new)
.extend(called.iter().cloned());
}
pub fn analyze_control_flow(
&mut self,
func_name: &str,
stmts: &[Stmt],
ret_ty: &QualType,
) -> ControlFlowResult {
self.current_function = Some(func_name.to_string());
self.path_returns.clear();
let mut result = ControlFlowResult::reachable();
let is_void = ret_ty.is_void();
self.analyze_cf_stmts(stmts, &mut result, is_void, &mut HashSet::new());
result.all_paths_return = !is_void && self.path_returns.iter().all(|&r| r);
result.any_path_returns = self.path_returns.iter().any(|&r| r);
result.is_noreturn = !is_void && !result.any_path_returns && !stmts.is_empty();
self.current_function = None;
result
}
fn analyze_cf_stmts(
&mut self,
stmts: &[Stmt],
result: &mut ControlFlowResult,
is_void: bool,
visited: &mut HashSet<usize>,
) {
for stmt in stmts {
self.analyze_cf_stmt(stmt, result, is_void, visited);
}
}
fn analyze_cf_stmt(
&mut self,
stmt: &Stmt,
result: &mut ControlFlowResult,
is_void: bool,
visited: &mut HashSet<usize>,
) {
match stmt {
Stmt::Return(expr) => {
if is_void && expr.is_some() {
result.fallthrough_risk = true;
}
if !is_void && expr.is_none() {
result.fallthrough_risk = true;
}
result.any_path_returns = true;
self.path_returns.push(true);
}
Stmt::Compound(b) => {
self.analyze_cf_stmts(&b.stmts, result, is_void, visited);
}
Stmt::If { cond: _, then, els } => {
let mut then_returns = false;
let mut else_returns = els.is_none();
self.analyze_cf_stmt(then, result, is_void, visited);
then_returns = result.any_path_returns;
if let Some(else_stmt) = els {
let mut else_cf = ControlFlowResult::reachable();
self.analyze_cf_stmt(else_stmt, &mut else_cf, is_void, visited);
else_returns = else_cf.any_path_returns;
}
if then_returns && else_returns {
self.path_returns.push(true);
}
}
Stmt::While { cond: _, body } => {
self.analyze_cf_stmt(body, result, is_void, visited);
}
Stmt::DoWhile { body, cond: _ } => {
self.analyze_cf_stmt(body, result, is_void, visited);
}
Stmt::For {
init: _,
cond: _,
incr: _,
body,
} => {
self.analyze_cf_stmt(body, result, is_void, visited);
}
Stmt::Switch { expr: _, body } => {
self.analyze_cf_stmt(body, result, is_void, visited);
}
Stmt::Break => {
}
Stmt::Continue => {
}
Stmt::Goto { .. } => {
}
Stmt::Label { stmt: body, .. } => {
self.analyze_cf_stmt(body, result, is_void, visited);
}
Stmt::Case { stmt: body, .. } | Stmt::Default { stmt: body } => {
self.analyze_cf_stmt(body, result, is_void, visited);
}
_ => {
}
}
}
pub fn analyze_side_effects(
&self,
func_name: &str,
reads_globals: bool,
writes_globals: bool,
has_volatile: bool,
has_asm: bool,
) -> SideEffectClass {
if has_asm || has_volatile {
return SideEffectClass::HasEffects;
}
if reads_globals && writes_globals {
return SideEffectClass::HasEffects;
}
if writes_globals {
return SideEffectClass::HasEffects;
}
if reads_globals {
return SideEffectClass::ReadOnly;
}
SideEffectClass::Pure
}
pub fn set_side_effects(&mut self, name: &str, cls: SideEffectClass) {
self.side_effects.insert(name.to_string(), cls);
}
pub fn get_side_effects(&self, name: &str) -> SideEffectClass {
self.side_effects
.get(name)
.copied()
.unwrap_or(SideEffectClass::Unknown)
}
pub fn set_exception_spec(&mut self, name: &str, spec: ExceptionSpec) {
self.exception_specs.insert(name.to_string(), spec);
}
pub fn check_exception_compat(&self, caller: &str, callee: &str) -> (bool, Option<String>) {
let caller_spec = self.exception_specs.get(caller);
let callee_spec = self.exception_specs.get(callee);
match (caller_spec, callee_spec) {
(Some(caller_s), Some(callee_s)) => {
if !callee_s.can_throw() {
return (true, None);
}
if !caller_s.can_throw() {
return (
false,
Some(format!(
"noexcept function '{}' calls potentially-throwing function '{}'",
caller, callee
)),
);
}
(true, None)
}
_ => (true, None), }
}
pub fn analyze_parameter_usage(
&self,
params: &[(String, QualType)],
stmts: &[Stmt],
) -> Vec<ParamUsage> {
params
.iter()
.enumerate()
.map(|(i, (name, _))| {
let (is_read, is_written) = self.check_param_usage_in_stmts(name, stmts);
ParamUsage {
index: i,
is_read,
is_written,
is_used: is_read || is_written,
name: name.clone(),
}
})
.collect()
}
fn check_param_usage_in_stmts(&self, name: &str, stmts: &[Stmt]) -> (bool, bool) {
let mut read = false;
let mut written = false;
for stmt in stmts {
let (r, w) = self.check_param_in_stmt(name, stmt);
read |= r;
written |= w;
}
(read, written)
}
fn check_param_in_stmt(&self, name: &str, stmt: &Stmt) -> (bool, bool) {
match stmt {
Stmt::Expr(e) | Stmt::Return(Some(e)) => self.check_param_in_expr(name, e),
Stmt::Compound(b) => self.check_param_usage_in_stmts(name, &b.stmts),
Stmt::If { cond, then, els } => {
let (cr, cw) = self.check_param_in_expr(name, cond);
let (tr, tw) = self.check_param_in_stmt(name, then);
let (er, ew) = if let Some(es) = els {
self.check_param_in_stmt(name, es)
} else {
(false, false)
};
(cr || tr || er, cw || tw || ew)
}
Stmt::While { cond, body } | Stmt::DoWhile { cond, body } => {
let (cr, cw) = self.check_param_in_expr(name, cond);
let (br, bw) = self.check_param_in_stmt(name, body);
(cr || br, cw || bw)
}
Stmt::For {
init,
cond,
incr,
body,
} => {
let (ir, iw) = init.as_ref().map_or((false, false), |e| {
self.check_param_in_stmt(name, e.as_ref())
});
let (cr, cw) = cond.as_ref().map_or((false, false), |e| {
self.check_param_in_expr(name, e.as_ref())
});
let (nr, nw) = incr.as_ref().map_or((false, false), |e| {
self.check_param_in_expr(name, e.as_ref())
});
let (br, bw) = self.check_param_in_stmt(name, body.as_ref());
(ir || cr || nr || br, iw || cw || nw || bw)
}
Stmt::Switch { expr, body } => {
let (er, ew) = self.check_param_in_expr(name, expr);
let (br, bw) = self.check_param_in_stmt(name, body);
(er || br, ew || bw)
}
Stmt::Case { stmt: s, .. }
| Stmt::Default { stmt: s }
| Stmt::Label { stmt: s, .. } => self.check_param_in_stmt(name, s),
_ => (false, false),
}
}
fn check_param_in_expr(&self, name: &str, expr: &Expr) -> (bool, bool) {
match expr {
Expr::Ident(id) => {
let is_read = id == name;
(is_read, false)
}
Expr::Assign(BinaryOp::Assign, lhs, rhs) => {
let (lr, lw) = self.check_param_in_expr(name, lhs);
let (rr, rw) = self.check_param_in_expr(name, rhs);
let lhs_is_param = matches!(lhs.as_ref(), Expr::Ident(id) if id == name);
(lr || rr, lw || rw || lhs_is_param)
}
Expr::Binary(_, lhs, rhs) => {
let (lr, lw) = self.check_param_in_expr(name, lhs);
let (rr, rw) = self.check_param_in_expr(name, rhs);
(lr || rr, lw || rw)
}
Expr::Unary(_, operand) | Expr::Cast(_, operand) => {
self.check_param_in_expr(name, operand)
}
Expr::Call { args, .. } => {
let mut read = false;
let mut written = false;
for arg in args {
let (r, w) = self.check_param_in_expr(name, arg);
read |= r;
written |= w;
}
(read, written)
}
Expr::Conditional(cond, then, els) => {
let (cr, cw) = self.check_param_in_expr(name, cond);
let (tr, tw) = self.check_param_in_expr(name, then);
let (er, ew) = self.check_param_in_expr(name, els);
(cr || tr || er, cw || tw || ew)
}
Expr::Subscript { base, index } => {
let (br, bw) = self.check_param_in_expr(name, base);
let (ir, iw) = self.check_param_in_expr(name, index);
(br || ir, bw || iw)
}
Expr::Member { base, .. } => self.check_param_in_expr(name, base),
Expr::PostInc(e) | Expr::PostDec(e) | Expr::PreInc(e) | Expr::PreDec(e) => {
let (r, w) = self.check_param_in_expr(name, e);
let param_written = matches!(e.as_ref(), Expr::Ident(id) if id == name);
(r || param_written, w || param_written)
}
_ => (false, false),
}
}
pub fn detect_recursion(&self, func_name: &str) -> RecursionInfo {
let mut info = RecursionInfo::none();
if let Some(callees) = self.call_graph.get(func_name) {
info.direct = callees.contains(func_name);
}
if let Some(cycle) = self.find_mutual_cycle(func_name) {
if !cycle.is_empty() {
info.mutual = true;
info.mutual_cycle = cycle;
}
}
info.tail_recursive = info.direct;
info
}
fn find_mutual_cycle(&self, start: &str) -> Option<Vec<String>> {
let mut visited = HashSet::new();
let mut path = Vec::new();
let mut path_set = HashSet::new();
self.dfs_cycle(start, &mut visited, &mut path, &mut path_set)
}
fn dfs_cycle(
&self,
node: &str,
visited: &mut HashSet<String>,
path: &mut Vec<String>,
path_set: &mut HashSet<String>,
) -> Option<Vec<String>> {
if path_set.contains(node) {
if let Some(pos) = path.iter().position(|n| n == node) {
let mut cycle: Vec<String> = path[pos..].to_vec();
cycle.push(node.to_string());
return Some(cycle);
}
return None;
}
if visited.contains(node) {
return None;
}
visited.insert(node.to_string());
path.push(node.to_string());
path_set.insert(node.to_string());
if let Some(callees) = self.call_graph.get(node) {
for callee in callees {
if let Some(cycle) = self.dfs_cycle(callee, visited, path, path_set) {
return Some(cycle);
}
}
}
path.pop();
path_set.remove(node);
None
}
pub fn build_call_graph_from_tu(&mut self, tu: &TranslationUnit) {
for decl in &tu.decls {
if let Decl::Function(f) = decl {
let callees = self.extract_callees_from_body(&f.body);
self.register_function(&f.name, &callees);
}
}
}
fn extract_callees_from_body(&self, body: &Option<CompoundStmt>) -> Vec<String> {
match body {
Some(b) => self.extract_calls_from_stmts(&b.stmts),
None => Vec::new(),
}
}
fn extract_calls_from_stmts(&self, stmts: &[Stmt]) -> Vec<String> {
let mut calls = Vec::new();
for stmt in stmts {
match stmt {
Stmt::Expr(e) => calls.extend(self.extract_calls_from_expr(e)),
Stmt::Return(Some(e)) => calls.extend(self.extract_calls_from_expr(e)),
Stmt::Compound(b) => calls.extend(self.extract_calls_from_stmts(&b.stmts)),
Stmt::If { cond, then, els } => {
calls.extend(self.extract_calls_from_expr(cond));
calls.extend(self.extract_calls_from_stmts(&[then.as_ref().clone()]));
if let Some(es) = els {
calls.extend(self.extract_calls_from_stmts(&[es.as_ref().clone()]));
}
}
Stmt::While { cond, body } | Stmt::DoWhile { cond, body } => {
calls.extend(self.extract_calls_from_expr(cond));
calls.extend(self.extract_calls_from_stmts(&[body.as_ref().clone()]));
}
Stmt::For {
init,
cond,
incr,
body,
} => {
if let Some(Stmt::Expr(expr)) = init.as_ref().map(|b| b.as_ref()) {
calls.extend(self.extract_calls_from_expr(expr));
}
if let Some(c) = cond {
calls.extend(self.extract_calls_from_expr(c));
}
if let Some(n) = incr {
calls.extend(self.extract_calls_from_expr(n));
}
calls.extend(self.extract_calls_from_stmts(&[body.as_ref().clone()]));
}
Stmt::Switch { expr, body } => {
calls.extend(self.extract_calls_from_expr(expr));
calls.extend(self.extract_calls_from_stmts(&[body.as_ref().clone()]));
}
_ => {}
}
}
calls
}
fn extract_calls_from_expr(&self, expr: &Expr) -> Vec<String> {
match expr {
Expr::Call { callee, args } => {
let mut calls = Vec::new();
if let Expr::Ident(name) = callee.as_ref() {
calls.push(name.clone());
}
calls.extend(self.extract_calls_from_expr(callee));
for arg in args {
calls.extend(self.extract_calls_from_expr(arg));
}
calls
}
Expr::Binary(_, lhs, rhs) => {
let mut calls = self.extract_calls_from_expr(lhs);
calls.extend(self.extract_calls_from_expr(rhs));
calls
}
Expr::Unary(_, operand) | Expr::Cast(_, operand) => {
self.extract_calls_from_expr(operand)
}
Expr::Assign(BinaryOp::Assign, lhs, rhs) => {
let mut calls = self.extract_calls_from_expr(lhs);
calls.extend(self.extract_calls_from_expr(rhs));
calls
}
Expr::Conditional(cond, then, els) => {
let mut calls = self.extract_calls_from_expr(cond);
calls.extend(self.extract_calls_from_expr(then));
calls.extend(self.extract_calls_from_expr(els));
calls
}
Expr::Subscript { base, index } => {
let mut calls = self.extract_calls_from_expr(base);
calls.extend(self.extract_calls_from_expr(index));
calls
}
_ => Vec::new(),
}
}
}
impl Default for X86FunctionChecker {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ValueRange {
pub min: i64,
pub max: i64,
pub is_constant: bool,
}
impl ValueRange {
pub fn constant(val: i64) -> Self {
Self {
min: val,
max: val,
is_constant: true,
}
}
pub fn unknown() -> Self {
Self {
min: i64::MIN,
max: i64::MAX,
is_constant: false,
}
}
pub fn bounded(min: i64, max: i64) -> Self {
Self {
min,
max,
is_constant: min == max,
}
}
pub fn contains(&self, val: i64) -> bool {
val >= self.min && val <= self.max
}
pub fn is_subset_of(&self, other: &ValueRange) -> bool {
self.min >= other.min && self.max <= other.max
}
pub fn union(&self, other: &ValueRange) -> ValueRange {
ValueRange {
min: self.min.min(other.min),
max: self.max.max(other.max),
is_constant: self.is_constant && other.is_constant && self.min == other.min,
}
}
pub fn intersection(&self, other: &ValueRange) -> Option<ValueRange> {
let min = self.min.max(other.min);
let max = self.max.min(other.max);
if min > max {
None
} else {
Some(ValueRange {
min,
max,
is_constant: min == max,
})
}
}
pub fn add(&self, other: &ValueRange) -> ValueRange {
let min = self.min.saturating_add(other.min);
let max = self.max.saturating_add(other.max);
ValueRange {
min,
max,
is_constant: self.is_constant && other.is_constant,
}
}
pub fn mul(&self, other: &ValueRange) -> ValueRange {
let products = [
self.min.saturating_mul(other.min),
self.min.saturating_mul(other.max),
self.max.saturating_mul(other.min),
self.max.saturating_mul(other.max),
];
let min = *products.iter().min().unwrap_or(&0);
let max = *products.iter().max().unwrap_or(&0);
ValueRange {
min,
max,
is_constant: self.is_constant && other.is_constant,
}
}
}
impl fmt::Display for ValueRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_constant {
write!(f, "{}", self.min)
} else if self.min == i64::MIN && self.max == i64::MAX {
write!(f, "<unknown>")
} else {
write!(f, "[{}, {}]", self.min, self.max)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NullState {
Null,
NotNull,
Maybe,
Unknown,
}
impl NullState {
pub fn is_definitely_null(&self) -> bool {
matches!(self, NullState::Null)
}
pub fn might_be_null(&self) -> bool {
matches!(
self,
NullState::Null | NullState::Maybe | NullState::Unknown
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InitState {
Initialized,
Uninitialized,
Maybe,
Unknown,
}
#[derive(Debug, Clone)]
pub struct DeadStoreInfo {
pub variable: String,
pub line: u32,
pub stored_value: Option<String>,
pub partial: bool,
}
pub struct X86ValueAnalysis {
constants: HashMap<String, i64>,
ranges: HashMap<String, ValueRange>,
null_states: HashMap<String, NullState>,
init_states: HashMap<String, InitState>,
dead_stores: Vec<DeadStoreInfo>,
live_vars: HashSet<String>,
max_iterations: usize,
}
impl X86ValueAnalysis {
pub fn new() -> Self {
Self {
constants: HashMap::new(),
ranges: HashMap::new(),
null_states: HashMap::new(),
init_states: HashMap::new(),
dead_stores: Vec::new(),
live_vars: HashSet::new(),
max_iterations: 32,
}
}
pub fn set_constant(&mut self, name: &str, value: i64) {
self.constants.insert(name.to_string(), value);
self.ranges
.insert(name.to_string(), ValueRange::constant(value));
}
pub fn get_constant(&self, name: &str) -> Option<i64> {
self.constants.get(name).copied()
}
pub fn set_range(&mut self, name: &str, range: ValueRange) {
self.ranges.insert(name.to_string(), range);
}
pub fn get_range(&self, name: &str) -> ValueRange {
self.ranges
.get(name)
.copied()
.unwrap_or(ValueRange::unknown())
}
pub fn set_null_state(&mut self, name: &str, state: NullState) {
self.null_states.insert(name.to_string(), state);
}
pub fn get_null_state(&self, name: &str) -> NullState {
self.null_states
.get(name)
.cloned()
.unwrap_or(NullState::Unknown)
}
pub fn set_init_state(&mut self, name: &str, state: InitState) {
self.init_states.insert(name.to_string(), state);
}
pub fn get_init_state(&self, name: &str) -> InitState {
self.init_states
.get(name)
.cloned()
.unwrap_or(InitState::Unknown)
}
pub fn mark_live(&mut self, name: &str) {
self.live_vars.insert(name.to_string());
}
pub fn record_store(&mut self, name: &str, line: u32, value: Option<&str>, partial: bool) {
if !self.live_vars.contains(name) {
self.dead_stores.push(DeadStoreInfo {
variable: name.to_string(),
line,
stored_value: value.map(|s| s.to_string()),
partial,
});
}
}
pub fn dead_stores(&self) -> &[DeadStoreInfo] {
&self.dead_stores
}
pub fn detect_uninitialized(&self) -> Vec<String> {
self.init_states
.iter()
.filter(|(_, state)| matches!(state, InitState::Uninitialized))
.map(|(name, _)| name.clone())
.collect()
}
pub fn detect_null_deref(&self, name: &str) -> bool {
self.null_states
.get(name)
.map_or(false, |s| s.is_definitely_null())
}
pub fn constant_fold(&self, expr: &Expr) -> Option<i64> {
match expr {
Expr::IntLiteral(v) => Some(*v),
Expr::UIntLiteral(v, _) => Some(*v as i64),
Expr::CharLiteral(c) => Some(*c as i64),
Expr::Ident(name) => self.get_constant(name),
Expr::Unary(op, operand) => {
let val = self.constant_fold(operand)?;
match op {
UnaryOp::Plus => Some(val),
UnaryOp::Minus => Some(-val),
UnaryOp::Not => Some(if val == 0 { 1 } else { 0 }),
UnaryOp::BitNot => Some(!val),
_ => None,
}
}
Expr::Binary(op, lhs, rhs) => {
let l = self.constant_fold(lhs)?;
let r = self.constant_fold(rhs)?;
self.fold_binary(op, l, r)
}
Expr::Cast(_, inner) => self.constant_fold(inner),
Expr::Conditional(cond, then, els) => {
let c = self.constant_fold(cond)?;
if c != 0 {
self.constant_fold(then)
} else {
self.constant_fold(els)
}
}
_ => None,
}
}
fn fold_binary(&self, op: &BinaryOp, a: i64, b: i64) -> Option<i64> {
match op {
BinaryOp::Add => Some(a.wrapping_add(b)),
BinaryOp::Sub => Some(a.wrapping_sub(b)),
BinaryOp::Mul => Some(a.wrapping_mul(b)),
BinaryOp::Div => {
if b == 0 {
None
} else {
Some(a.wrapping_div(b))
}
}
BinaryOp::Mod => {
if b == 0 {
None
} else {
Some(a.wrapping_rem(b))
}
}
BinaryOp::And => Some(a & b),
BinaryOp::Or => Some(a | b),
BinaryOp::Xor => Some(a ^ b),
BinaryOp::Shl => Some(a.wrapping_shl(b as u32)),
BinaryOp::Shr => Some(a.wrapping_shr(b as u32)),
BinaryOp::Eq => Some(if a == b { 1 } else { 0 }),
BinaryOp::Ne => Some(if a != b { 1 } else { 0 }),
BinaryOp::Lt => Some(if a < b { 1 } else { 0 }),
BinaryOp::Gt => Some(if a > b { 1 } else { 0 }),
BinaryOp::Le => Some(if a <= b { 1 } else { 0 }),
BinaryOp::Ge => Some(if a >= b { 1 } else { 0 }),
BinaryOp::LogicAnd => Some(if a != 0 && b != 0 { 1 } else { 0 }),
BinaryOp::LogicOr => Some(if a != 0 || b != 0 { 1 } else { 0 }),
_ => None,
}
}
pub fn range_of_binary(
&self,
op: &BinaryOp,
lhs_range: &ValueRange,
rhs_range: &ValueRange,
) -> ValueRange {
match op {
BinaryOp::Add => lhs_range.add(rhs_range),
BinaryOp::Sub => lhs_range.add(&ValueRange::bounded(-rhs_range.max, -rhs_range.min)),
BinaryOp::Mul => lhs_range.mul(rhs_range),
_ => ValueRange::unknown(),
}
}
pub fn clear(&mut self) {
self.constants.clear();
self.ranges.clear();
self.null_states.clear();
self.init_states.clear();
self.dead_stores.clear();
self.live_vars.clear();
}
}
impl Default for X86ValueAnalysis {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AttributeSource {
Standard,
Gnu,
Msvc,
X86,
}
impl fmt::Display for AttributeSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AttributeSource::Standard => write!(f, "standard"),
AttributeSource::Gnu => write!(f, "GNU"),
AttributeSource::Msvc => write!(f, "MSVC"),
AttributeSource::X86 => write!(f, "X86"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AttributeName {
NoReturn,
CarriesDependency,
Deprecated,
Fallthrough,
NoDiscard,
MaybeUnused,
Likely,
Unlikely,
NoUniqueAddress,
Assume,
Aligned,
Packed,
ConstAttr, Pure,
Malloc,
Format,
NonNull,
WarnUnusedResult,
Cold,
Hot,
Constructor,
Destructor,
Visibility,
Weak,
Alias,
Section,
TlsModel,
Target,
DllImport,
DllExport,
Naked,
NoInline,
NoThrow,
NoVTable,
SelectAny,
Thread,
Uuid,
Interrupt,
Signal,
CpuTarget,
CpuTune,
}
impl AttributeName {
pub fn from_str(s: &str) -> Option<Self> {
match s {
"noreturn" | "no_return" => Some(Self::NoReturn),
"carries_dependency" => Some(Self::CarriesDependency),
"deprecated" => Some(Self::Deprecated),
"fallthrough" => Some(Self::Fallthrough),
"nodiscard" | "no_discard" => Some(Self::NoDiscard),
"maybe_unused" => Some(Self::MaybeUnused),
"likely" => Some(Self::Likely),
"unlikely" => Some(Self::Unlikely),
"no_unique_address" => Some(Self::NoUniqueAddress),
"assume" => Some(Self::Assume),
"aligned" => Some(Self::Aligned),
"packed" => Some(Self::Packed),
"const" => Some(Self::ConstAttr),
"pure" => Some(Self::Pure),
"malloc" => Some(Self::Malloc),
"format" => Some(Self::Format),
"nonnull" | "non_null" => Some(Self::NonNull),
"warn_unused_result" => Some(Self::WarnUnusedResult),
"cold" => Some(Self::Cold),
"hot" => Some(Self::Hot),
"constructor" => Some(Self::Constructor),
"destructor" => Some(Self::Destructor),
"visibility" => Some(Self::Visibility),
"weak" => Some(Self::Weak),
"alias" => Some(Self::Alias),
"section" => Some(Self::Section),
"tls_model" => Some(Self::TlsModel),
"target" => Some(Self::Target),
"dllimport" | "dll_import" => Some(Self::DllImport),
"dllexport" | "dll_export" => Some(Self::DllExport),
"naked" => Some(Self::Naked),
"noinline" | "no_inline" => Some(Self::NoInline),
"nothrow" | "no_throw" => Some(Self::NoThrow),
"novtable" | "no_vtable" => Some(Self::NoVTable),
"selectany" | "select_any" => Some(Self::SelectAny),
"thread" => Some(Self::Thread),
"uuid" => Some(Self::Uuid),
"interrupt" => Some(Self::Interrupt),
"signal" => Some(Self::Signal),
_ => None,
}
}
pub fn source(&self) -> AttributeSource {
match self {
Self::NoReturn
| Self::CarriesDependency
| Self::Deprecated
| Self::Fallthrough
| Self::NoDiscard
| Self::MaybeUnused
| Self::Likely
| Self::Unlikely
| Self::NoUniqueAddress
| Self::Assume => AttributeSource::Standard,
Self::Aligned
| Self::Packed
| Self::ConstAttr
| Self::Pure
| Self::Malloc
| Self::Format
| Self::NonNull
| Self::WarnUnusedResult
| Self::Cold
| Self::Hot
| Self::Constructor
| Self::Destructor
| Self::Visibility
| Self::Weak
| Self::Alias
| Self::Section
| Self::TlsModel
| Self::Target => AttributeSource::Gnu,
Self::DllImport
| Self::DllExport
| Self::Naked
| Self::NoInline
| Self::NoThrow
| Self::NoVTable
| Self::SelectAny
| Self::Thread
| Self::Uuid => AttributeSource::Msvc,
Self::Interrupt | Self::Signal | Self::CpuTarget | Self::CpuTune => {
AttributeSource::X86
}
}
}
pub fn valid_targets(&self) -> Vec<AttrApplyTarget> {
match self {
Self::NoReturn
| Self::Naked
| Self::NoThrow
| Self::Hot
| Self::Cold
| Self::Constructor
| Self::Destructor
| Self::Interrupt
| Self::Signal => {
vec![AttrApplyTarget::Function]
}
Self::CarriesDependency
| Self::Deprecated
| Self::NoDiscard
| Self::WarnUnusedResult => {
vec![AttrApplyTarget::Function, AttrApplyTarget::Type]
}
Self::Fallthrough => vec![AttrApplyTarget::Statement],
Self::MaybeUnused | Self::NoUniqueAddress | Self::Assume => {
vec![
AttrApplyTarget::Variable,
AttrApplyTarget::Function,
AttrApplyTarget::Type,
AttrApplyTarget::Parameter,
]
}
Self::Likely | Self::Unlikely => {
vec![AttrApplyTarget::Statement, AttrApplyTarget::Label]
}
Self::Aligned | Self::Packed => vec![AttrApplyTarget::Type, AttrApplyTarget::Variable],
Self::ConstAttr | Self::Pure => vec![AttrApplyTarget::Function],
Self::Malloc => vec![AttrApplyTarget::Function],
Self::Format => vec![AttrApplyTarget::Function],
Self::NonNull => vec![AttrApplyTarget::Function, AttrApplyTarget::Parameter],
Self::Visibility
| Self::Weak
| Self::Alias
| Self::Section
| Self::TlsModel
| Self::DllImport
| Self::DllExport => {
vec![AttrApplyTarget::Function, AttrApplyTarget::Variable]
}
Self::Target => vec![AttrApplyTarget::Function],
Self::NoInline => vec![AttrApplyTarget::Function],
Self::NoVTable => vec![AttrApplyTarget::Type],
Self::SelectAny => vec![AttrApplyTarget::Variable, AttrApplyTarget::Function],
Self::Thread => vec![AttrApplyTarget::Variable],
Self::Uuid => vec![AttrApplyTarget::Type],
Self::CpuTarget | Self::CpuTune => vec![AttrApplyTarget::Function],
}
}
pub fn description(&self) -> &'static str {
match self {
Self::NoReturn => "Indicates that the function does not return",
Self::CarriesDependency => {
"Indicates that dependency chain in release-consume propagates"
}
Self::Deprecated => "Marks an entity as deprecated",
Self::Fallthrough => "Indicates that fallthrough in a switch case is intentional",
Self::NoDiscard => "Warns if the return value is discarded",
Self::MaybeUnused => "Suppresses warnings about unused entities",
Self::Likely => "Hints that a branch is likely to be taken",
Self::Unlikely => "Hints that a branch is unlikely to be taken",
Self::NoUniqueAddress => "Allows empty base optimization for data members",
Self::Assume => "Provides an assumption for the optimizer",
Self::Aligned => "Specifies alignment for a variable or type",
Self::Packed => "Requests minimum alignment for a type",
Self::ConstAttr => "Indicates a function has no side effects and doesn't read globals",
Self::Pure => "Indicates a function has no side effects (may read globals)",
Self::Malloc => "Indicates a function behaves like malloc",
Self::Format => "Indicates a function takes printf/scanf-style format strings",
Self::NonNull => "Indicates pointer arguments are non-null",
Self::WarnUnusedResult => "Warns if the return value is unused",
Self::Cold => "Indicates a function is rarely called",
Self::Hot => "Indicates a function is frequently called",
Self::Constructor => "Function is called before main()",
Self::Destructor => "Function is called after main()",
Self::Visibility => "Sets symbol visibility (default/hidden/protected/internal)",
Self::Weak => "Marks a symbol as weak",
Self::Alias => "Specifies that this is an alias for another symbol",
Self::Section => "Places the symbol in a specific section",
Self::TlsModel => {
"Selects the TLS model (global-dynamic/local-dynamic/initial-exec/local-exec)"
}
Self::Target => "Specifies target-specific options for code generation",
Self::DllImport => "Marks a symbol as imported from a DLL",
Self::DllExport => "Marks a symbol as exported from a DLL",
Self::Naked => "Function prologue/epilogue is not emitted",
Self::NoInline => "Prevents function inlining",
Self::NoThrow => "Indicates a function does not throw exceptions",
Self::NoVTable => "Suppresses vtable generation for a class",
Self::SelectAny => "Allows the linker to pick any definition",
Self::Thread => "Marks a variable as thread-local",
Self::Uuid => "Associates a UUID with a class/interface",
Self::Interrupt => "Marks a function as an interrupt handler",
Self::Signal => "Marks a function as a signal handler",
Self::CpuTarget => "Specifies target CPU for function code generation (X86)",
Self::CpuTune => "Specifies tuning CPU for function scheduling (X86)",
}
}
pub fn takes_args(&self) -> bool {
matches!(
self,
Self::Aligned
| Self::Format
| Self::Visibility
| Self::Alias
| Self::Section
| Self::TlsModel
| Self::Target
| Self::Assume
| Self::NonNull
| Self::CpuTarget
| Self::CpuTune
| Self::Deprecated
)
}
}
impl fmt::Display for AttributeName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoReturn => write!(f, "noreturn"),
Self::CarriesDependency => write!(f, "carries_dependency"),
Self::Deprecated => write!(f, "deprecated"),
Self::Fallthrough => write!(f, "fallthrough"),
Self::NoDiscard => write!(f, "nodiscard"),
Self::MaybeUnused => write!(f, "maybe_unused"),
Self::Likely => write!(f, "likely"),
Self::Unlikely => write!(f, "unlikely"),
Self::NoUniqueAddress => write!(f, "no_unique_address"),
Self::Assume => write!(f, "assume"),
Self::Aligned => write!(f, "aligned"),
Self::Packed => write!(f, "packed"),
Self::ConstAttr => write!(f, "const"),
Self::Pure => write!(f, "pure"),
Self::Malloc => write!(f, "malloc"),
Self::Format => write!(f, "format"),
Self::NonNull => write!(f, "nonnull"),
Self::WarnUnusedResult => write!(f, "warn_unused_result"),
Self::Cold => write!(f, "cold"),
Self::Hot => write!(f, "hot"),
Self::Constructor => write!(f, "constructor"),
Self::Destructor => write!(f, "destructor"),
Self::Visibility => write!(f, "visibility"),
Self::Weak => write!(f, "weak"),
Self::Alias => write!(f, "alias"),
Self::Section => write!(f, "section"),
Self::TlsModel => write!(f, "tls_model"),
Self::Target => write!(f, "target"),
Self::DllImport => write!(f, "dllimport"),
Self::DllExport => write!(f, "dllexport"),
Self::Naked => write!(f, "naked"),
Self::NoInline => write!(f, "noinline"),
Self::NoThrow => write!(f, "nothrow"),
Self::NoVTable => write!(f, "novtable"),
Self::SelectAny => write!(f, "selectany"),
Self::Thread => write!(f, "thread"),
Self::Uuid => write!(f, "uuid"),
Self::Interrupt => write!(f, "interrupt"),
Self::Signal => write!(f, "signal"),
Self::CpuTarget => write!(f, "cpu_target"),
Self::CpuTune => write!(f, "cpu_tune"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AttrApplyTarget {
Function,
Variable,
Type,
Parameter,
Statement,
Label,
Enum,
}
#[derive(Debug, Clone)]
pub struct AttributeInstance {
pub name: AttributeName,
pub source: AttributeSource,
pub arguments: Vec<String>,
pub applied_to: AttrApplyTarget,
pub line: u32,
}
impl AttributeInstance {
pub fn new(name: AttributeName, applied_to: AttrApplyTarget, line: u32) -> Self {
Self {
name,
source: name.source(),
arguments: Vec::new(),
applied_to,
line,
}
}
pub fn with_args(mut self, args: Vec<String>) -> Self {
self.arguments = args;
self
}
}
#[derive(Debug, Clone)]
pub struct AttributeConflict {
pub attr1: AttributeName,
pub attr2: AttributeName,
pub reason: String,
}
pub struct X86AttributeChecker {
pub function_attrs: HashMap<String, Vec<AttributeInstance>>,
pub variable_attrs: HashMap<String, Vec<AttributeInstance>>,
pub type_attrs: HashMap<String, Vec<AttributeInstance>>,
pub known_conflicts: HashMap<(AttributeName, AttributeName), String>,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
impl X86AttributeChecker {
pub fn new() -> Self {
let mut checker = Self {
function_attrs: HashMap::new(),
variable_attrs: HashMap::new(),
type_attrs: HashMap::new(),
known_conflicts: HashMap::new(),
errors: Vec::new(),
warnings: Vec::new(),
};
checker.register_known_conflicts();
checker
}
fn register_known_conflicts(&mut self) {
self.known_conflicts.insert(
(AttributeName::ConstAttr, AttributeName::Pure),
"const implies pure; specifying both is redundant".to_string(),
);
self.known_conflicts.insert(
(AttributeName::Hot, AttributeName::Cold),
"a function cannot be both hot and cold".to_string(),
);
self.known_conflicts.insert(
(AttributeName::DllImport, AttributeName::DllExport),
"a symbol cannot be both imported and exported".to_string(),
);
self.known_conflicts.insert(
(AttributeName::Weak, AttributeName::DllExport),
"weak dllexport is not supported on some targets".to_string(),
);
self.known_conflicts.insert(
(AttributeName::Naked, AttributeName::Constructor),
"naked functions cannot be constructors".to_string(),
);
self.known_conflicts.insert(
(AttributeName::Naked, AttributeName::Destructor),
"naked functions cannot be destructors".to_string(),
);
}
pub fn add_attribute(
&mut self,
entity_name: &str,
entity_type: AttrApplyTarget,
attr: AttributeInstance,
) {
let name = entity_name.to_string();
match entity_type {
AttrApplyTarget::Function => {
self.function_attrs.entry(name).or_default().push(attr);
}
AttrApplyTarget::Variable => {
self.variable_attrs.entry(name).or_default().push(attr);
}
AttrApplyTarget::Type => {
self.type_attrs.entry(name).or_default().push(attr);
}
_ => {
self.function_attrs.entry(name).or_default().push(attr);
}
}
}
pub fn is_valid_for(&self, attr: &AttributeName, target: AttrApplyTarget) -> bool {
attr.valid_targets().contains(&target)
}
pub fn validate_all(&mut self) {
self.errors.clear();
self.warnings.clear();
let func_attrs: Vec<(String, Vec<AttributeInstance>)> =
self.function_attrs.drain().collect();
for (name, attrs) in &func_attrs {
self.validate_entity(name, attrs, AttrApplyTarget::Function);
}
for (name, attrs) in func_attrs {
self.function_attrs.insert(name, attrs);
}
let var_attrs: Vec<(String, Vec<AttributeInstance>)> =
self.variable_attrs.drain().collect();
for (name, attrs) in &var_attrs {
self.validate_entity(name, attrs, AttrApplyTarget::Variable);
}
for (name, attrs) in var_attrs {
self.variable_attrs.insert(name, attrs);
}
let type_attrs: Vec<(String, Vec<AttributeInstance>)> = self.type_attrs.drain().collect();
for (name, attrs) in &type_attrs {
self.validate_entity(name, attrs, AttrApplyTarget::Type);
}
for (name, attrs) in type_attrs {
self.type_attrs.insert(name, attrs);
}
}
fn validate_entity(
&mut self,
entity_name: &str,
attrs: &[AttributeInstance],
target: AttrApplyTarget,
) {
let mut seen: HashSet<AttributeName> = HashSet::new();
for attr in attrs {
if !self.is_valid_for(&attr.name, target) {
self.errors.push(format!(
"attribute '{}' cannot be applied to {:?} '{}'",
attr.name, target, entity_name
));
}
if !seen.insert(attr.name) {
self.warnings.push(format!(
"duplicate attribute '{}' on '{}'",
attr.name, entity_name
));
}
for other_name in &seen {
if *other_name == attr.name {
continue;
}
let pair = (attr.name, *other_name);
let reverse = (*other_name, attr.name);
if let Some(reason) = self
.known_conflicts
.get(&pair)
.or_else(|| self.known_conflicts.get(&reverse))
{
self.warnings.push(format!(
"conflicting attributes '{}' and '{}' on '{}': {}",
attr.name, other_name, entity_name, reason
));
}
}
}
}
pub fn has_attribute(&self, name: &str, attr: AttributeName) -> bool {
self.function_attrs
.get(name)
.map(|attrs| attrs.iter().any(|a| a.name == attr))
.unwrap_or(false)
}
pub fn get_function_attributes(&self, name: &str) -> Vec<&AttributeInstance> {
self.function_attrs
.get(name)
.map(|v| v.iter().collect())
.unwrap_or_default()
}
pub fn is_noreturn(&self, name: &str) -> bool {
self.has_attribute(name, AttributeName::NoReturn)
}
pub fn is_deprecated(&self, name: &str) -> bool {
self.has_attribute(name, AttributeName::Deprecated)
}
pub fn parse_gnu_attribute(
&mut self,
entity: &str,
target: AttrApplyTarget,
attr_str: &str,
line: u32,
) -> Result<(), String> {
let inner = attr_str
.trim()
.strip_prefix("__attribute__((")
.and_then(|s| s.strip_suffix("))"))
.or_else(|| {
attr_str
.trim()
.strip_prefix("__attribute__((")
.and_then(|s| s.strip_suffix("))"))
})
.unwrap_or(attr_str);
let (name_str, args) = if let Some(paren_pos) = inner.find('(') {
let name = &inner[..paren_pos];
let args_str = &inner[paren_pos + 1..];
let args_str = args_str.trim_end_matches(')');
let args: Vec<String> = args_str
.split(',')
.map(|a| a.trim().to_string())
.filter(|a| !a.is_empty())
.collect();
(name, args)
} else {
(inner, Vec::new())
};
let attr_name = AttributeName::from_str(name_str.trim())
.ok_or_else(|| format!("unknown GNU attribute: {}", name_str))?;
if !self.is_valid_for(&attr_name, target) {
self.warnings.push(format!(
"attribute '{}' applied to invalid target {:?} on '{}'",
attr_name, target, entity
));
}
let instance = AttributeInstance::new(attr_name, target, line).with_args(args);
self.add_attribute(entity, target, instance);
Ok(())
}
pub fn parse_msvc_attribute(
&mut self,
entity: &str,
target: AttrApplyTarget,
attr_str: &str,
line: u32,
) -> Result<(), String> {
let inner = attr_str
.trim()
.strip_prefix("__declspec(")
.and_then(|s| s.strip_suffix(')'))
.unwrap_or(attr_str);
let (name_str, args) = if let Some(paren_pos) = inner.find('(') {
let name = &inner[..paren_pos];
let args_str = &inner[paren_pos + 1..];
let args_str = args_str.trim_end_matches(')');
let args: Vec<String> = args_str
.split(',')
.map(|a| a.trim().to_string())
.filter(|a| !a.is_empty())
.collect();
(name, args)
} else {
(inner, Vec::new())
};
let attr_name = AttributeName::from_str(name_str.trim())
.ok_or_else(|| format!("unknown MSVC attribute: {}", name_str))?;
let instance = AttributeInstance::new(attr_name, target, line).with_args(args);
self.add_attribute(entity, target, instance);
Ok(())
}
pub fn clear(&mut self) {
self.function_attrs.clear();
self.variable_attrs.clear();
self.type_attrs.clear();
self.errors.clear();
self.warnings.clear();
}
pub fn all_attribute_names() -> Vec<AttributeName> {
vec![
AttributeName::NoReturn,
AttributeName::CarriesDependency,
AttributeName::Deprecated,
AttributeName::Fallthrough,
AttributeName::NoDiscard,
AttributeName::MaybeUnused,
AttributeName::Likely,
AttributeName::Unlikely,
AttributeName::NoUniqueAddress,
AttributeName::Assume,
AttributeName::Aligned,
AttributeName::Packed,
AttributeName::ConstAttr,
AttributeName::Pure,
AttributeName::Malloc,
AttributeName::Format,
AttributeName::NonNull,
AttributeName::WarnUnusedResult,
AttributeName::Cold,
AttributeName::Hot,
AttributeName::Constructor,
AttributeName::Destructor,
AttributeName::Visibility,
AttributeName::Weak,
AttributeName::Alias,
AttributeName::Section,
AttributeName::TlsModel,
AttributeName::Target,
AttributeName::DllImport,
AttributeName::DllExport,
AttributeName::Naked,
AttributeName::NoInline,
AttributeName::NoThrow,
AttributeName::NoVTable,
AttributeName::SelectAny,
AttributeName::Thread,
AttributeName::Uuid,
AttributeName::Interrupt,
AttributeName::Signal,
AttributeName::CpuTarget,
AttributeName::CpuTune,
]
}
}
impl Default for X86AttributeChecker {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SymbolVisibility {
Default,
Hidden,
Protected,
Internal,
}
impl SymbolVisibility {
pub fn from_str(s: &str) -> Option<Self> {
match s {
"default" => Some(Self::Default),
"hidden" => Some(Self::Hidden),
"protected" => Some(Self::Protected),
"internal" => Some(Self::Internal),
_ => None,
}
}
}
impl fmt::Display for SymbolVisibility {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SymbolVisibility::Default => write!(f, "default"),
SymbolVisibility::Hidden => write!(f, "hidden"),
SymbolVisibility::Protected => write!(f, "protected"),
SymbolVisibility::Internal => write!(f, "internal"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymbolStrength {
Strong,
Weak,
WeakRef,
Common,
}
impl fmt::Display for SymbolStrength {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SymbolStrength::Strong => write!(f, "strong"),
SymbolStrength::Weak => write!(f, "weak"),
SymbolStrength::WeakRef => write!(f, "weakref"),
SymbolStrength::Common => write!(f, "common"),
}
}
}
#[derive(Debug, Clone)]
pub struct ComdatGroup {
pub name: String,
pub selection_kind: ComdatSelection,
pub members: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ComdatSelection {
Any,
ExactMatch,
Largest,
NoDuplicates,
SameSize,
}
impl ComdatSelection {
pub fn from_str(s: &str) -> Option<Self> {
match s {
"any" => Some(Self::Any),
"exactmatch" | "exact_match" => Some(Self::ExactMatch),
"largest" => Some(Self::Largest),
"noduplicates" | "no_duplicates" => Some(Self::NoDuplicates),
"samesize" | "same_size" => Some(Self::SameSize),
_ => None,
}
}
}
impl fmt::Display for ComdatSelection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ComdatSelection::Any => write!(f, "any"),
ComdatSelection::ExactMatch => write!(f, "exactmatch"),
ComdatSelection::Largest => write!(f, "largest"),
ComdatSelection::NoDuplicates => write!(f, "noduplicates"),
ComdatSelection::SameSize => write!(f, "samesize"),
}
}
}
#[derive(Debug, Clone)]
pub struct OdrViolation {
pub name: String,
pub first_def: Option<String>,
pub second_def: Option<String>,
pub reason: String,
pub is_hard_error: bool,
}
#[derive(Debug, Clone)]
pub struct SymbolDef {
pub name: String,
pub is_definition: bool,
pub linkage: Linkage,
pub visibility: SymbolVisibility,
pub strength: SymbolStrength,
pub comdat_group: Option<String>,
pub file: Option<String>,
pub line: u32,
pub is_inline: bool,
pub is_template: bool,
}
pub struct X86LinkageChecker {
pub symbols: HashMap<String, Vec<SymbolDef>>,
pub comdat_groups: HashMap<String, ComdatGroup>,
pub odr_violations: Vec<OdrViolation>,
pub warnings: Vec<String>,
pub is_cxx: bool,
pub target_triple: String,
}
impl X86LinkageChecker {
pub fn new(is_cxx: bool, target_triple: &str) -> Self {
Self {
symbols: HashMap::new(),
comdat_groups: HashMap::new(),
odr_violations: Vec::new(),
warnings: Vec::new(),
is_cxx,
target_triple: target_triple.to_string(),
}
}
pub fn register_symbol(&mut self, sym: SymbolDef) {
self.symbols.entry(sym.name.clone()).or_default().push(sym);
}
pub fn register_comdat(&mut self, group: ComdatGroup) {
self.comdat_groups.insert(group.name.clone(), group);
}
pub fn analyze(&mut self) {
self.odr_violations.clear();
self.warnings.clear();
if self.is_cxx {
self.check_odr();
}
self.check_weak_strong();
self.check_visibility();
self.check_comdat();
}
fn check_odr(&mut self) {
for (name, defs) in &self.symbols {
let definitions: Vec<&SymbolDef> = defs.iter().filter(|d| d.is_definition).collect();
if definitions.len() <= 1 {
continue;
}
let all_inline_or_template = definitions.iter().all(|d| d.is_inline || d.is_template);
if all_inline_or_template {
self.warnings.push(format!(
"multiple inline/template definitions of '{}' — ensure identical tokens",
name
));
continue;
}
self.odr_violations.push(OdrViolation {
name: name.clone(),
first_def: definitions.first().and_then(|d| d.file.clone()),
second_def: definitions.get(1).and_then(|d| d.file.clone()),
reason: format!(
"multiple definitions of '{}'; defined in {} and {}",
name,
definitions
.first()
.and_then(|d| d.file.as_deref())
.unwrap_or("<unknown>"),
definitions
.get(1)
.and_then(|d| d.file.as_deref())
.unwrap_or("<unknown>"),
),
is_hard_error: true,
});
}
}
fn check_weak_strong(&mut self) {
for (name, defs) in &self.symbols {
let strong_count = defs
.iter()
.filter(|d| d.strength == SymbolStrength::Strong && d.is_definition)
.count();
let weak_count = defs
.iter()
.filter(|d| d.strength == SymbolStrength::Weak && d.is_definition)
.count();
if strong_count > 1 {
self.warnings.push(format!(
"multiple strong definitions of '{}' — will cause linker error",
name
));
}
if strong_count == 0 && weak_count > 1 {
self.warnings.push(format!(
"multiple weak definitions of '{}' — linker will pick one arbitrarily",
name
));
}
if strong_count > 0 {
let common_count = defs
.iter()
.filter(|d| d.strength == SymbolStrength::Common)
.count();
if common_count > 0 {
self.warnings.push(format!(
"common definition of '{}' overridden by strong definition",
name
));
}
}
}
}
fn check_visibility(&mut self) {
for (name, defs) in &self.symbols {
let visibilities: HashSet<SymbolVisibility> =
defs.iter().map(|d| d.visibility).collect();
if visibilities.len() > 1 {
self.warnings.push(format!(
"inconsistent visibility for '{}': declared with multiple visibilities",
name
));
}
}
}
fn check_comdat(&mut self) {
for (name, group) in &self.comdat_groups {
if group.selection_kind == ComdatSelection::NoDuplicates {
let mut all_defs = Vec::new();
for member in &group.members {
if let Some(defs) = self.symbols.get(member) {
all_defs.extend(defs.iter().filter(|d| d.is_definition));
}
}
if all_defs.len() > 1 {
self.warnings.push(format!(
"COMDAT group '{}' has selection kind 'noduplicates' but multiple definitions exist",
name
));
}
}
if group.selection_kind == ComdatSelection::ExactMatch {
for member in &group.members {
if let Some(defs) = self.symbols.get(member) {
let def_count = defs.iter().filter(|d| d.is_definition).count();
if def_count > 1 {
self.warnings.push(format!(
"COMDAT group '{}' member '{}' has multiple definitions — exactmatch requires identical copies",
name, member
));
}
}
}
}
if group.members.is_empty() {
self.warnings
.push(format!("COMDAT group '{}' has no members", name));
}
}
}
pub fn is_exported(&self, name: &str) -> bool {
if let Some(defs) = self.symbols.get(name) {
defs.iter().any(|d| {
d.visibility == SymbolVisibility::Default
&& d.linkage != Linkage::Internal
&& d.is_definition
})
} else {
false
}
}
pub fn effective_linkage(&self, name: &str) -> Option<Linkage> {
self.symbols.get(name).and_then(|defs| {
let strong = defs
.iter()
.find(|d| d.strength == SymbolStrength::Strong && d.is_definition);
strong.or_else(|| defs.first()).map(|d| d.linkage)
})
}
pub fn effective_visibility(&self, name: &str) -> SymbolVisibility {
self.symbols
.get(name)
.and_then(|defs| defs.first())
.map(|d| d.visibility)
.unwrap_or(SymbolVisibility::Default)
}
pub fn has_multiple_defs(&self, name: &str) -> bool {
self.symbols
.get(name)
.map(|defs| defs.iter().filter(|d| d.is_definition).count() > 1)
.unwrap_or(false)
}
pub fn clear(&mut self) {
self.symbols.clear();
self.comdat_groups.clear();
self.odr_violations.clear();
self.warnings.clear();
}
pub fn stats(&self) -> LinkageStats {
let mut stats = LinkageStats::default();
for (_, defs) in &self.symbols {
stats.total_symbols += 1;
for d in defs {
if d.is_definition {
stats.definitions += 1;
match d.strength {
SymbolStrength::Strong => stats.strong_defs += 1,
SymbolStrength::Weak => stats.weak_defs += 1,
SymbolStrength::WeakRef => stats.weak_refs += 1,
SymbolStrength::Common => stats.common_syms += 1,
}
} else {
stats.declarations += 1;
}
match d.visibility {
SymbolVisibility::Default => stats.default_vis += 1,
SymbolVisibility::Hidden => stats.hidden_vis += 1,
SymbolVisibility::Protected => stats.protected_vis += 1,
SymbolVisibility::Internal => stats.internal_vis += 1,
}
}
}
stats.odr_violations = self.odr_violations.len();
stats.comdat_groups = self.comdat_groups.len();
stats
}
}
impl Default for X86LinkageChecker {
fn default() -> Self {
Self::new(false, "x86_64-unknown-linux-gnu")
}
}
#[derive(Debug, Clone, Default)]
pub struct LinkageStats {
pub total_symbols: usize,
pub definitions: usize,
pub declarations: usize,
pub strong_defs: usize,
pub weak_defs: usize,
pub weak_refs: usize,
pub common_syms: usize,
pub default_vis: usize,
pub hidden_vis: usize,
pub protected_vis: usize,
pub internal_vis: usize,
pub odr_violations: usize,
pub comdat_groups: usize,
}
impl fmt::Display for LinkageStats {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Linkage Statistics:")?;
writeln!(f, " Total symbols: {}", self.total_symbols)?;
writeln!(f, " Definitions: {}", self.definitions)?;
writeln!(f, " Declarations: {}", self.declarations)?;
writeln!(f, " Strong defs: {}", self.strong_defs)?;
writeln!(f, " Weak defs: {}", self.weak_defs)?;
writeln!(f, " Weak refs: {}", self.weak_refs)?;
writeln!(f, " Common syms: {}", self.common_syms)?;
writeln!(f, " Default vis: {}", self.default_vis)?;
writeln!(f, " Hidden vis: {}", self.hidden_vis)?;
writeln!(f, " Protected vis: {}", self.protected_vis)?;
writeln!(f, " Internal vis: {}", self.internal_vis)?;
writeln!(f, " ODR violations: {}", self.odr_violations)?;
write!(f, " COMDAT groups: {}", self.comdat_groups)
}
}
pub struct X86DeepSemaFix {
pub tu: X86TranslationUnitFix,
pub type_checker: X86TypeChecker,
pub function_checker: X86FunctionChecker,
pub value_analysis: X86ValueAnalysis,
pub attribute_checker: X86AttributeChecker,
pub linkage_checker: X86LinkageChecker,
pub sema: Sema,
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub success: bool,
}
impl X86DeepSemaFix {
pub fn new(standard: &str, target_triple: &str) -> Self {
let is_cxx = standard.contains("++");
let is_64bit = target_triple.contains("x86_64") || target_triple.contains("amd64");
let data_model = if is_64bit {
if target_triple.contains("windows") || target_triple.contains("msvc") {
DataModel::LLP64
} else {
DataModel::LP64
}
} else {
DataModel::ILP32
};
let lang = if is_cxx {
LanguageStandard::cxx17()
} else {
LanguageStandard::c17()
};
let source = SourceFileInfo::new("source.c").main();
let tu = TranslationUnit::new("source.c");
let fixed_tu = X86TranslationUnitFix::new(tu)
.with_target(TargetTripleInfo {
triple: target_triple.to_string(),
arch: if is_64bit {
"x86_64".into()
} else {
"i386".into()
},
vendor: "unknown".into(),
os: if target_triple.contains("linux") {
"linux".into()
} else {
"unknown".into()
},
environment: if target_triple.contains("gnu") {
"gnu".into()
} else {
"unknown".into()
},
is_64bit,
data_model,
})
.with_language(lang)
.with_source(source);
Self {
tu: fixed_tu,
type_checker: X86TypeChecker::new(data_model),
function_checker: X86FunctionChecker::new(),
value_analysis: X86ValueAnalysis::new(),
attribute_checker: X86AttributeChecker::new(),
linkage_checker: X86LinkageChecker::new(is_cxx, target_triple),
sema: Sema::new(CLangStandard::C17),
errors: Vec::new(),
warnings: Vec::new(),
success: true,
}
}
pub fn new_x86_64() -> Self {
Self::new("c17", "x86_64-unknown-linux-gnu")
}
pub fn new_x86_32() -> Self {
Self::new("c17", "i386-unknown-linux-gnu")
}
pub fn analyze_tu(&mut self, tu: &TranslationUnit) -> bool {
self.tu.tu = tu.clone();
self.errors.clear();
self.warnings.clear();
self.success = true;
for decl in &tu.decls {
self.register_decl(decl);
}
self.function_checker.build_call_graph_from_tu(tu);
for decl in &tu.decls {
if let Decl::Function(f) = decl {
if !self.analyze_function(f) {
self.success = false;
}
}
}
self.attribute_checker.validate_all();
self.linkage_checker.analyze();
self.collect_sub_errors();
self.sema.analyze(tu);
self.success = self.errors.is_empty() && self.success;
self.success
}
fn register_decl(&mut self, decl: &Decl) {
match decl {
Decl::Function(f) => {
let sym = SymbolDef {
name: f.name.clone(),
is_definition: f.body.is_some(),
linkage: f.linkage,
visibility: SymbolVisibility::Default,
strength: if f.linkage == Linkage::Internal {
SymbolStrength::Strong
} else {
SymbolStrength::Strong
},
comdat_group: if f.is_inline {
Some(f.name.clone())
} else {
None
},
file: Some(self.tu.source_file.path.clone()),
line: 0,
is_inline: f.is_inline,
is_template: false,
};
self.linkage_checker.register_symbol(sym);
if f.is_noreturn {
let noreturn_attr = AttributeInstance::new(
AttributeName::NoReturn,
AttrApplyTarget::Function,
0,
);
self.attribute_checker.add_attribute(
&f.name,
AttrApplyTarget::Function,
noreturn_attr,
);
}
}
Decl::Variable(v) => {
let sym = SymbolDef {
name: v.name.clone(),
is_definition: v.init.is_some() || !v.is_extern,
linkage: v.linkage,
visibility: SymbolVisibility::Default,
strength: if v.is_extern && v.init.is_none() {
SymbolStrength::Weak
} else {
SymbolStrength::Strong
},
comdat_group: None,
file: Some(self.tu.source_file.path.clone()),
line: 0,
is_inline: false,
is_template: false,
};
self.linkage_checker.register_symbol(sym);
}
Decl::Struct(s) => {
self.type_checker.register_struct(
s.name.as_deref().unwrap_or(""),
s.fields
.iter()
.map(|f| (f.name.clone(), f.ty.clone()))
.collect(),
);
}
Decl::Enum(e) => {
self.type_checker.register_enum(
e.name.as_deref().unwrap_or(""),
e.variants
.iter()
.filter_map(|v| v.value.map(|val| (v.name.clone(), val)))
.collect(),
);
}
Decl::Typedef(t) => {
self.type_checker
.register_typedef(&t.name, t.underlying.clone());
}
_ => {}
}
}
fn analyze_function(&mut self, func: &FunctionDecl) -> bool {
let mut ok = true;
if !self.type_checker.are_compatible(&func.ret_ty, &func.ret_ty) {
ok = false;
}
if let Some(ref body) = func.body {
let cf =
self.function_checker
.analyze_control_flow(&func.name, &body.stmts, &func.ret_ty);
if !func.ret_ty.is_void() && !cf.all_paths_return && !func.is_noreturn {
self.warnings.push(format!(
"function '{}': not all control paths return a value",
func.name
));
}
if cf.is_noreturn && !func.is_noreturn {
self.warnings.push(format!(
"function '{}': appears to be noreturn but is not marked as such",
func.name
));
}
let params: Vec<(String, QualType)> = func
.params
.iter()
.enumerate()
.map(|(i, ty)| (format!("param_{}", i), ty.ty.clone()))
.collect();
let param_usage = self
.function_checker
.analyze_parameter_usage(¶ms, &body.stmts);
for pu in ¶m_usage {
if !pu.is_used {
self.warnings.push(format!(
"function '{}': unused parameter '{}'",
func.name, pu.name
));
}
}
let recursion = self.function_checker.detect_recursion(&func.name);
if recursion.direct {
if !recursion.tail_recursive {
self.warnings.push(format!(
"function '{}': direct recursion is not tail-recursive",
func.name
));
}
}
if recursion.mutual {
self.warnings.push(format!(
"function '{}': participates in mutual recursion cycle: {:?}",
func.name, recursion.mutual_cycle
));
}
}
ok
}
fn collect_sub_errors(&mut self) {
self.errors.extend(self.attribute_checker.errors.clone());
self.warnings
.extend(self.attribute_checker.warnings.clone());
self.warnings.extend(self.linkage_checker.warnings.clone());
for v in &self.linkage_checker.odr_violations {
if v.is_hard_error {
self.errors.push(format!("ODR violation: {}", v.reason));
} else {
self.warnings.push(format!("ODR warning: {}", v.reason));
}
}
}
pub fn check_type_compatibility(&self, a: &QualType, b: &QualType) -> bool {
self.type_checker.are_compatible(a, b)
}
pub fn type_of_expr(&self, expr: &Expr) -> Option<QualType> {
match expr {
Expr::IntLiteral(_) => Some(QualType::int()),
Expr::UIntLiteral(..) => Some(QualType::uint()),
Expr::FloatLiteral(_) => Some(QualType::float()),
Expr::DoubleLiteral(_) => Some(QualType::double()),
Expr::CharLiteral(_) => Some(QualType::char()),
Expr::StringLiteral(_) => Some(QualType::pointer_to(QualType::char())),
Expr::Ident(_) => Some(QualType::int()), Expr::Binary(_, lhs, _) => self.type_of_expr(lhs),
Expr::Unary(_, operand) => self.type_of_expr(operand),
Expr::Call { callee, .. } => {
if let Expr::Ident(name) = callee.as_ref() {
for decl in &self.tu.tu.decls {
if let Decl::Function(f) = decl {
if &f.name == name {
return Some(f.ret_ty.clone());
}
}
}
}
Some(QualType::int()) }
_ => Some(QualType::int()),
}
}
pub fn evaluate_constexpr(&self, expr: &Expr) -> Option<i64> {
self.value_analysis.constant_fold(expr)
}
pub fn dump_symbol_table(&self) -> String {
let mut out = String::new();
out.push_str("=== Symbol Table ===\n");
for decl in &self.tu.tu.decls {
match decl {
Decl::Function(f) => {
out.push_str(&format!(
" Function: {} -> {} (inline: {}, noreturn: {})\n",
f.name, f.ret_ty, f.is_inline, f.is_noreturn
));
}
Decl::Variable(v) => {
out.push_str(&format!(
" Variable: {} : {} (extern: {}, static: {})\n",
v.name, v.ty, v.is_extern, v.is_static
));
}
Decl::Struct(s) => {
out.push_str(&format!(
" Struct: {} ({} fields)\n",
s.name.as_deref().unwrap_or("<anonymous>"),
s.fields.len()
));
}
Decl::Enum(e) => {
out.push_str(&format!(
" Enum: {} ({} variants)\n",
e.name.as_deref().unwrap_or("<anonymous>"),
e.variants.len()
));
}
Decl::Typedef(t) => {
out.push_str(&format!(" Typedef: {} = {}\n", t.name, t.underlying));
}
_ => {}
}
}
out
}
pub fn linkage_stats(&self) -> LinkageStats {
self.linkage_checker.stats()
}
pub fn clear(&mut self) {
self.errors.clear();
self.warnings.clear();
self.value_analysis.clear();
self.attribute_checker.clear();
self.linkage_checker.clear();
self.success = true;
}
}
impl Default for X86DeepSemaFix {
fn default() -> Self {
Self::new_x86_64()
}
}
pub fn make_x86_64_deep_sema_fix() -> X86DeepSemaFix {
X86DeepSemaFix::new_x86_64()
}
pub fn make_x86_32_deep_sema_fix() -> X86DeepSemaFix {
X86DeepSemaFix::new_x86_32()
}
pub fn analyze_x86_tu_fix(tu: &TranslationUnit) -> (bool, Vec<String>, Vec<String>) {
let mut sema = make_x86_64_deep_sema_fix();
let ok = sema.analyze_tu(tu);
(ok, sema.errors.clone(), sema.warnings.clone())
}
pub fn analyze_x86_function_fix(func: &FunctionDecl) -> (bool, Vec<String>, Vec<String>) {
let tu = TranslationUnit {
decls: vec![Decl::Function(func.clone())],
filename: "test.c".to_string(),
};
analyze_x86_tu_fix(&tu)
}
pub fn contains_x86_fix_message(errors: &[String], msg: &str) -> bool {
errors.iter().any(|e| e.contains(msg))
}
pub fn make_fixed_tu(tu: TranslationUnit) -> X86TranslationUnitFix {
X86TranslationUnitFix::new(tu)
}
pub fn make_fixed_tu_with_includes(
tu: TranslationUnit,
system_includes: Vec<&str>,
user_includes: Vec<&str>,
) -> X86TranslationUnitFix {
let mut fix = X86TranslationUnitFix::new(tu);
for inc in system_includes {
fix.add_system_include(IncludeRecord::system(inc, 0));
}
for inc in user_includes {
fix.add_user_include(IncludeRecord::user(inc, 0));
}
fix
}
pub fn make_type_checker_lp64() -> X86TypeChecker {
X86TypeChecker::new(DataModel::LP64)
}
pub fn make_type_checker_ilp32() -> X86TypeChecker {
X86TypeChecker::new(DataModel::ILP32)
}
pub fn fix_int_lit(val: i64) -> Expr {
Expr::IntLiteral(val)
}
pub fn fix_ident(name: &str) -> Expr {
Expr::Ident(name.to_string())
}
pub fn fix_binary(op: BinaryOp, lhs: Expr, rhs: Expr) -> Expr {
Expr::Binary(op, Box::new(lhs), Box::new(rhs))
}
pub fn fix_assign(lhs: Expr, rhs: Expr) -> Expr {
Expr::Assign(BinaryOp::Assign, Box::new(lhs), Box::new(rhs))
}
pub fn fix_return(expr: Option<Expr>) -> Stmt {
Stmt::Return(expr.map(Box::new))
}
pub fn fix_compound(stmts: Vec<Stmt>) -> Stmt {
Stmt::Compound(CompoundStmt { stmts })
}
pub fn fix_call(callee: Expr, args: Vec<Expr>) -> Expr {
Expr::Call {
callee: Box::new(callee),
args,
}
}
pub fn fix_make_function(
name: &str,
ret_ty: QualType,
params: Vec<QualType>,
body: Option<CompoundStmt>,
) -> FunctionDecl {
let var_decls: Vec<VarDecl> = params
.into_iter()
.enumerate()
.map(|(i, ty)| VarDecl {
name: format!("p{}", i),
ty,
init: None,
linkage: Linkage::None,
is_global: false,
is_extern: false,
is_static: false,
})
.collect();
FunctionDecl {
name: name.to_string(),
ret_ty,
params: var_decls,
body,
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
}
}
pub fn fix_make_variable(name: &str, ty: QualType, init: Option<Expr>) -> VarDecl {
VarDecl {
name: name.to_string(),
ty,
init: init.map(Box::new),
linkage: Linkage::External,
is_global: true,
is_extern: false,
is_static: false,
}
}
pub fn fix_make_tu(decls: Vec<Decl>) -> TranslationUnit {
TranslationUnit {
decls,
filename: "test.c".to_string(),
}
}
impl X86TypeChecker {
pub fn check_qualifier_compat(&self, from: &QualType, to: &QualType) -> bool {
if to.is_const && !from.is_const {
return false;
}
if to.is_volatile && !from.is_volatile {
return false;
}
if to.is_restrict && !from.is_restrict {
return false;
}
true
}
pub fn check_structural_compat(&self, a: &QualType, b: &QualType) -> bool {
self.size_of(a) == self.size_of(b)
}
pub fn resolve_typedef(&self, ty: &QualType) -> QualType {
match ty.base.as_ref() {
TypeNode::Typedef { name, underlying } => {
if let Some(ut) = self.typedefs.get(name) {
self.resolve_typedef(ut)
} else {
QualType::new((*underlying.base).clone())
}
}
TypeNode::Pointer(inner) => {
let resolved_inner = self.resolve_typedef(inner);
QualType::new(TypeNode::Pointer(Box::new(resolved_inner)))
}
TypeNode::Array { elem, size } => {
let resolved_elem = self.resolve_typedef(elem);
QualType::new(TypeNode::Array {
elem: Box::new(resolved_elem),
size: *size,
})
}
_ => ty.clone(),
}
}
pub fn strip_qualifiers(&self, ty: &QualType) -> QualType {
QualType {
is_const: false,
is_volatile: false,
is_restrict: false,
..ty.clone()
}
}
pub fn add_const(&self, ty: &QualType) -> QualType {
let mut result = ty.clone();
result.is_const = true;
result
}
pub fn is_character_type(&self, ty: &QualType) -> bool {
matches!(*ty.base, TypeNode::Char | TypeNode::SChar | TypeNode::UChar)
}
pub fn is_complete_type(&self, ty: &QualType) -> bool {
!matches!(
*ty.base,
TypeNode::Void | TypeNode::Array { size: None, .. }
)
}
pub fn to_signed(&self, ty: &QualType) -> QualType {
match ty.base.as_ref() {
TypeNode::UChar => QualType::schar(),
TypeNode::UShort => QualType::short(),
TypeNode::UInt => QualType::int(),
TypeNode::ULong => QualType::long(),
TypeNode::ULongLong => QualType::long_long(),
_ => ty.clone(),
}
}
pub fn to_unsigned(&self, ty: &QualType) -> QualType {
match ty.base.as_ref() {
TypeNode::Char | TypeNode::SChar => QualType::uchar(),
TypeNode::Short => QualType::ushort(),
TypeNode::Int => QualType::uint(),
TypeNode::Long => QualType::ulong(),
TypeNode::LongLong => QualType::ulong_long(),
_ => ty.clone(),
}
}
pub fn all_primitive_sizes_lp64() -> Vec<(&'static str, usize)> {
vec![
("void", 0),
("bool", 1),
("char", 1),
("schar", 1),
("uchar", 1),
("short", 2),
("ushort", 2),
("int", 4),
("uint", 4),
("long", 8),
("ulong", 8),
("longlong", 8),
("ulonglong", 8),
("float", 4),
("double", 8),
("longdouble", 16),
("pointer", 8),
]
}
pub fn all_primitive_sizes_ilp32() -> Vec<(&'static str, usize)> {
vec![
("void", 0),
("bool", 1),
("char", 1),
("schar", 1),
("uchar", 1),
("short", 2),
("ushort", 2),
("int", 4),
("uint", 4),
("long", 4),
("ulong", 4),
("longlong", 8),
("ulonglong", 8),
("float", 4),
("double", 8),
("longdouble", 12),
("pointer", 4),
]
}
pub fn abi_classification(&self, ty: &QualType) -> X86AbiTypeClass {
match ty.base.as_ref() {
TypeNode::Void => X86AbiTypeClass::NoClass,
TypeNode::Bool
| TypeNode::Char
| TypeNode::SChar
| TypeNode::UChar
| TypeNode::Short
| TypeNode::UShort
| TypeNode::Int
| TypeNode::UInt => X86AbiTypeClass::Integer,
TypeNode::Long | TypeNode::ULong | TypeNode::LongLong | TypeNode::ULongLong => {
X86AbiTypeClass::Integer
}
TypeNode::Float | TypeNode::Double => X86AbiTypeClass::Sse,
TypeNode::LongDouble => X86AbiTypeClass::X87,
TypeNode::Pointer(..) => X86AbiTypeClass::Integer,
TypeNode::Array { .. } => X86AbiTypeClass::Memory,
TypeNode::Struct {
is_union, fields, ..
} => {
if *is_union {
X86AbiTypeClass::Memory
} else if fields.is_empty() {
X86AbiTypeClass::NoClass
} else {
X86AbiTypeClass::Memory
}
}
_ => X86AbiTypeClass::Memory,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86AbiTypeClass {
NoClass,
Integer,
Sse,
SseUp,
X87,
X87Up,
ComplexX87,
Memory,
}
impl fmt::Display for X86AbiTypeClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86AbiTypeClass::NoClass => write!(f, "NO_CLASS"),
X86AbiTypeClass::Integer => write!(f, "INTEGER"),
X86AbiTypeClass::Sse => write!(f, "SSE"),
X86AbiTypeClass::SseUp => write!(f, "SSEUP"),
X86AbiTypeClass::X87 => write!(f, "X87"),
X86AbiTypeClass::X87Up => write!(f, "X87UP"),
X86AbiTypeClass::ComplexX87 => write!(f, "COMPLEX_X87"),
X86AbiTypeClass::Memory => write!(f, "MEMORY"),
}
}
}
#[derive(Debug, Clone)]
pub struct CfgBlock {
pub id: usize,
pub stmts: Vec<Stmt>,
pub successors: Vec<usize>,
pub terminates_with_return: bool,
pub is_entry: bool,
pub is_exit: bool,
}
#[derive(Debug, Clone)]
pub struct ControlFlowGraph {
pub blocks: Vec<CfgBlock>,
pub entry: usize,
pub exit: usize,
pub function_name: String,
}
impl ControlFlowGraph {
pub fn is_reachable(&self, block_id: usize) -> bool {
let mut visited = HashSet::new();
let mut stack = vec![self.entry];
while let Some(id) = stack.pop() {
if id == block_id {
return true;
}
if visited.insert(id) {
if let Some(block) = self.blocks.get(id) {
stack.extend(&block.successors);
}
}
}
false
}
pub fn dominators(&self, block_id: usize) -> HashSet<usize> {
let n = self.blocks.len();
let mut dom: Vec<HashSet<usize>> = vec![HashSet::new(); n];
dom[self.entry].insert(self.entry);
for i in 0..n {
if i != self.entry {
dom[i] = (0..n).collect();
}
}
let mut changed = true;
while changed {
changed = false;
for i in 0..n {
if i == self.entry {
continue;
}
let preds: Vec<usize> = self
.blocks
.iter()
.enumerate()
.filter(|(_, b)| b.successors.contains(&i))
.map(|(j, _)| j)
.collect();
if preds.is_empty() {
continue;
}
let mut new_dom: HashSet<usize> = dom[preds[0]].clone();
for &p in &preds[1..] {
new_dom = new_dom.intersection(&dom[p]).copied().collect();
}
new_dom.insert(i);
if new_dom != dom[i] {
dom[i] = new_dom;
changed = true;
}
}
}
dom[block_id].clone()
}
}
impl X86FunctionChecker {
pub fn build_cfg(&self, name: &str, stmts: &[Stmt]) -> ControlFlowGraph {
let mut blocks = Vec::new();
let mut current_id = 0;
let entry = CfgBlock {
id: current_id,
stmts: vec![],
successors: vec![1],
terminates_with_return: false,
is_entry: true,
is_exit: false,
};
blocks.push(entry);
current_id += 1;
let body = self.build_cfg_block(stmts, &mut current_id);
blocks.extend(body);
let exit = CfgBlock {
id: current_id,
stmts: vec![],
successors: vec![],
terminates_with_return: false,
is_entry: false,
is_exit: true,
};
blocks.push(exit);
ControlFlowGraph {
blocks,
entry: 0,
exit: current_id,
function_name: name.to_string(),
}
}
fn build_cfg_block(&self, stmts: &[Stmt], next_id: &mut usize) -> Vec<CfgBlock> {
let mut blocks = Vec::new();
if stmts.is_empty() {
let block = CfgBlock {
id: *next_id,
stmts: vec![],
successors: vec![*next_id + 1],
terminates_with_return: false,
is_entry: false,
is_exit: false,
};
blocks.push(block);
*next_id += 1;
return blocks;
}
let mut current_stmts = Vec::new();
for stmt in stmts {
match stmt {
Stmt::Return(_) | Stmt::Break | Stmt::Continue | Stmt::Goto { .. } => {
if !current_stmts.is_empty() {
let id = *next_id;
*next_id += 1;
blocks.push(CfgBlock {
id,
stmts: std::mem::take(&mut current_stmts),
successors: if matches!(stmt, Stmt::Return(_)) {
vec![*next_id + 1]
} else {
vec![*next_id]
},
terminates_with_return: matches!(stmt, Stmt::Return(_)),
is_entry: false,
is_exit: false,
});
}
}
Stmt::If { cond: _, then, els } => {
if !current_stmts.is_empty() {
let id = *next_id;
*next_id += 1;
blocks.push(CfgBlock {
id,
stmts: std::mem::take(&mut current_stmts),
successors: vec![*next_id],
terminates_with_return: false,
is_entry: false,
is_exit: false,
});
}
let then_id = *next_id;
let then_blocks = self.build_cfg_block(&[then.as_ref().clone()], next_id);
blocks.extend(then_blocks);
let then_exit = *next_id - 1;
let else_id = if let Some(es) = els {
let eid = *next_id;
let else_blocks = self.build_cfg_block(&[es.as_ref().clone()], next_id);
blocks.extend(else_blocks);
Some(eid)
} else {
None
};
let else_exit = *next_id - 1;
if let Some(last) = blocks.last_mut() {
}
}
_ => {
current_stmts.push(stmt.clone());
}
}
}
if !current_stmts.is_empty() {
let id = *next_id;
*next_id += 1;
blocks.push(CfgBlock {
id,
stmts: current_stmts,
successors: vec![*next_id],
terminates_with_return: false,
is_entry: false,
is_exit: false,
});
}
blocks
}
pub fn interprocedural_side_effects(&mut self) {
let mut changed = true;
let mut iteration = 0;
while changed && iteration < self.max_iterations() {
changed = false;
for (func, callees) in self.call_graph.clone().iter() {
let mut worst = SideEffectClass::None;
for callee in callees {
let cls = self.get_side_effects(callee);
worst = Self::merge_side_effects(worst, cls);
}
let current = self.get_side_effects(func);
if worst != current && worst != SideEffectClass::Unknown {
self.set_side_effects(func, worst);
changed = true;
}
}
iteration += 1;
}
}
fn merge_side_effects(a: SideEffectClass, b: SideEffectClass) -> SideEffectClass {
use SideEffectClass::*;
match (a, b) {
(HasEffects, _) | (_, HasEffects) => HasEffects,
(Unknown, x) | (x, Unknown) => x,
(ReadOnly, _) | (_, ReadOnly) => ReadOnly,
(Pure, _) | (_, Pure) => Pure,
(Const, Const) => Const,
(None, x) => x,
(_, None) => unreachable!(),
}
}
fn max_iterations(&self) -> usize {
64
}
pub fn may_be_recursive_indirect(&self, func_name: &str) -> bool {
let mut visited = HashSet::new();
let mut stack: Vec<&str> = self
.call_graph
.get(func_name)
.map(|cs| cs.iter().map(|s| s.as_str()).collect())
.unwrap_or_default();
while let Some(current) = stack.pop() {
if current == func_name {
return true;
}
if visited.insert(current.to_string()) {
if let Some(callees) = self.call_graph.get(current) {
stack.extend(callees.iter().map(|s| s.as_str()));
}
}
}
false
}
pub fn call_depth(&self, func_name: &str) -> usize {
let mut depth = 0;
let mut current = func_name.to_string();
let mut visited = HashSet::new();
loop {
if !visited.insert(current.clone()) {
break; }
if let Some(callees) = self.call_graph.get(¤t) {
if callees.is_empty() {
break;
}
depth += 1;
current = callees.iter().next().unwrap().clone();
} else {
break;
}
}
depth
}
}
impl X86ValueAnalysis {
pub fn propagate_ranges(&mut self, stmts: &[Stmt]) {
for stmt in stmts {
self.propagate_stmt(stmt);
}
}
fn propagate_stmt(&mut self, stmt: &Stmt) {
match stmt {
Stmt::Expr(expr) => {
self.propagate_expr(expr);
}
Stmt::Return(Some(expr)) => {
self.propagate_expr(expr);
}
Stmt::Compound(b) => {
self.propagate_ranges(&b.stmts);
}
Stmt::If { cond, then, els } => {
self.propagate_expr(cond);
let saved_ranges = self.ranges.clone();
let saved_constants = self.constants.clone();
self.propagate_stmt(then);
if let Some(es) = els {
self.ranges = saved_ranges;
self.constants = saved_constants;
self.propagate_stmt(es);
}
}
Stmt::While { cond, body } => {
self.propagate_expr(cond);
self.propagate_stmt(body);
}
Stmt::For {
init,
cond,
incr,
body,
} => {
if let Some(Stmt::Expr(expr)) = init.as_ref().map(|b| b.as_ref()) {
self.propagate_expr(expr);
}
if let Some(c) = cond {
self.propagate_expr(c);
}
self.propagate_stmt(body);
if let Some(n) = incr {
self.propagate_expr(n);
}
}
Stmt::Switch { expr, body } => {
self.propagate_expr(expr);
self.propagate_stmt(body);
}
_ => {}
}
}
fn propagate_expr(&mut self, expr: &Expr) {
match expr {
Expr::Assign(BinaryOp::Assign, lhs, rhs) => {
if let Expr::Ident(name) = lhs.as_ref() {
if let Some(val) = self.constant_fold(rhs) {
self.set_constant(name, val);
} else {
let range = self.compute_expr_range(rhs);
self.set_range(name, range);
}
self.set_init_state(name, InitState::Initialized);
}
self.propagate_expr(rhs);
}
Expr::Binary(_, lhs, rhs) => {
self.propagate_expr(lhs);
self.propagate_expr(rhs);
}
Expr::Unary(_, operand) => {
self.propagate_expr(operand);
}
Expr::Call { args, .. } => {
for arg in args {
self.propagate_expr(arg);
}
}
Expr::Conditional(cond, then, els) => {
self.propagate_expr(cond);
self.propagate_expr(then);
self.propagate_expr(els);
}
_ => {}
}
}
pub fn compute_expr_range(&self, expr: &Expr) -> ValueRange {
match expr {
Expr::IntLiteral(v) => ValueRange::constant(*v),
Expr::UIntLiteral(v, _) => ValueRange::constant(*v as i64),
Expr::CharLiteral(c) => ValueRange::constant(*c as i64),
Expr::Ident(name) => self.get_range(name),
Expr::Binary(op, lhs, rhs) => {
let lr = self.compute_expr_range(lhs);
let rr = self.compute_expr_range(rhs);
self.range_of_binary(op, &lr, &rr)
}
Expr::Unary(op, operand) => {
let inner = self.compute_expr_range(operand);
match op {
UnaryOp::Minus => ValueRange::bounded(-inner.max, -inner.min),
UnaryOp::Not => ValueRange::bounded(0, 1),
UnaryOp::BitNot => ValueRange::unknown(),
_ => inner,
}
}
Expr::Conditional(_, then, els) => {
let tr = self.compute_expr_range(then);
let er = self.compute_expr_range(els);
tr.union(&er)
}
_ => ValueRange::unknown(),
}
}
pub fn live_variable_analysis(&mut self, stmts: &[Stmt]) {
self.live_vars.clear();
self.analyze_liveness_backward(stmts);
}
fn analyze_liveness_backward(&mut self, stmts: &[Stmt]) {
for stmt in stmts.iter().rev() {
self.liveness_stmt_backward(stmt);
}
}
fn liveness_stmt_backward(&mut self, stmt: &Stmt) {
match stmt {
Stmt::Expr(expr) => {
self.liveness_expr_backward(expr);
}
Stmt::Return(Some(expr)) => {
self.liveness_expr_backward(expr);
}
Stmt::Compound(b) => {
self.analyze_liveness_backward(&b.stmts);
}
Stmt::If { cond, then, els } => {
self.liveness_expr_backward(cond);
let saved = self.live_vars.clone();
self.analyze_liveness_backward(&[then.as_ref().clone()]);
let then_live = self.live_vars.clone();
if let Some(es) = els {
self.live_vars = saved;
self.analyze_liveness_backward(&[es.as_ref().clone()]);
self.live_vars = then_live.union(&self.live_vars.clone()).cloned().collect();
}
}
_ => {}
}
}
fn liveness_expr_backward(&mut self, expr: &Expr) {
match expr {
Expr::Ident(name) => {
self.live_vars.insert(name.clone());
}
Expr::Assign(BinaryOp::Assign, lhs, rhs) => {
self.liveness_expr_backward(rhs);
if let Expr::Ident(name) = lhs.as_ref() {
self.live_vars.remove(name);
}
}
Expr::Binary(_, lhs, rhs) => {
self.liveness_expr_backward(rhs);
self.liveness_expr_backward(lhs);
}
Expr::Unary(_, operand) => {
self.liveness_expr_backward(operand);
}
Expr::Call { args, .. } => {
for arg in args.iter().rev() {
self.liveness_expr_backward(arg);
}
}
Expr::Conditional(cond, then, els) => {
self.liveness_expr_backward(els);
self.liveness_expr_backward(then);
self.liveness_expr_backward(cond);
}
Expr::Subscript { base, index } => {
self.liveness_expr_backward(index);
self.liveness_expr_backward(base);
}
_ => {}
}
}
pub fn run_dead_store_detection(&mut self, stmts: &[Stmt]) -> Vec<DeadStoreInfo> {
self.dead_stores.clear();
self.live_variable_analysis(stmts);
self.detect_dead_stores_forward(stmts, 0);
self.dead_stores.clone()
}
fn detect_dead_stores_forward(&mut self, stmts: &[Stmt], _base_line: u32) {
for stmt in stmts {
match stmt {
Stmt::Expr(e) => {
if let Expr::Assign(_, lhs, rhs) = e.as_ref() {
if let Expr::Ident(name) = lhs.as_ref() {
if !self.live_vars.contains(name) {
self.dead_stores.push(DeadStoreInfo {
variable: name.clone(),
line: 0,
stored_value: Some(format!("{:?}", rhs)),
partial: false,
});
}
}
}
}
Stmt::Compound(b) => {
self.detect_dead_stores_forward(&b.stmts, 0);
}
_ => {}
}
}
}
}
impl X86DeepSemaFix {
pub fn run_interprocedural_analysis(&mut self) {
self.function_checker.interprocedural_side_effects();
}
pub fn detect_dead_stores(&mut self, func_name: &str) -> Vec<DeadStoreInfo> {
for decl in &self.tu.tu.decls {
if let Decl::Function(f) = decl {
if f.name == func_name {
if let Some(ref body) = f.body {
return self.value_analysis.run_dead_store_detection(&body.stmts);
}
}
}
}
Vec::new()
}
pub fn call_graph_summary(&self) -> String {
let mut out = String::from("Call Graph:\n");
for (func, callees) in &self.function_checker.call_graph {
out.push_str(&format!(
" {} -> [{}]\n",
func,
callees.iter().cloned().collect::<Vec<_>>().join(", ")
));
}
if out == "Call Graph:\n" {
out.push_str(" (empty)\n");
}
out
}
pub fn attribute_summary(&self) -> String {
let mut out = String::from("Attributes:\n");
for (name, attrs) in &self.attribute_checker.function_attrs {
let names: Vec<String> = attrs.iter().map(|a| format!("{}", a.name)).collect();
out.push_str(&format!(" {}: [{}]\n", name, names.join(", ")));
}
if out == "Attributes:\n" {
out.push_str(" (none)\n");
}
out
}
pub fn get_variable_range(&self, name: &str) -> ValueRange {
self.value_analysis.get_range(name)
}
pub fn check_uninit_use(&self, name: &str) -> bool {
matches!(
self.value_analysis.get_init_state(name),
InitState::Uninitialized
)
}
pub fn build_cfg(&self, func_name: &str) -> Option<ControlFlowGraph> {
for decl in &self.tu.tu.decls {
if let Decl::Function(f) = decl {
if f.name == func_name {
if let Some(ref body) = f.body {
return Some(self.function_checker.build_cfg(func_name, &body.stmts));
}
}
}
}
None
}
pub fn analyze_tu_deep(&mut self, tu: &TranslationUnit) -> bool {
let basic_ok = self.analyze_tu(tu);
if !basic_ok {
return false;
}
self.function_checker.interprocedural_side_effects();
for decl in &tu.decls {
if let Decl::Function(f) = decl {
if let Some(ref body) = f.body {
self.value_analysis.propagate_ranges(&body.stmts);
}
}
}
for decl in &tu.decls {
if let Decl::Function(f) = decl {
if let Some(ref body) = f.body {
let _ = self.value_analysis.run_dead_store_detection(&body.stmts);
}
}
}
self.success
}
pub fn analysis_report(&self) -> String {
let mut report = String::new();
report.push_str("=== X86 Deep Semantic Analysis Report ===\n\n");
report.push_str(&format!("Target: {}\n", self.tu.target.triple));
report.push_str(&format!(
"Standard: {}\n",
self.tu.language_standard.standard
));
report.push_str(&format!("Data Model: {}\n", self.tu.target.data_model));
report.push_str(&format!("Source: {}\n", self.tu.source_file.name));
report.push_str(&format!(
"Includes: {} system, {} user\n",
self.tu.system_includes.len(),
self.tu.user_includes.len()
));
report.push_str(&format!("Defines: {}\n", self.tu.defines.len()));
report.push('\n');
report.push_str(&format!("Errors: {}\n", self.errors.len()));
for e in &self.errors {
report.push_str(&format!(" ERROR: {}\n", e));
}
report.push_str(&format!("Warnings: {}\n", self.warnings.len()));
for w in &self.warnings {
report.push_str(&format!(" WARNING: {}\n", w));
}
report.push('\n');
report.push_str(&self.call_graph_summary());
report.push('\n');
report.push_str(&self.attribute_summary());
report.push('\n');
let stats = self.linkage_stats();
report.push_str(&format!("{}", stats));
report
}
}
pub const X86_FIX_LP64_CHAR_BITS: usize = 8;
pub const X86_FIX_LP64_SHORT_BITS: usize = 16;
pub const X86_FIX_LP64_INT_BITS: usize = 32;
pub const X86_FIX_LP64_LONG_BITS: usize = 64;
pub const X86_FIX_LP64_LONG_LONG_BITS: usize = 64;
pub const X86_FIX_LP64_POINTER_BITS: usize = 64;
pub const X86_FIX_LP64_FLOAT_BITS: usize = 32;
pub const X86_FIX_LP64_DOUBLE_BITS: usize = 64;
pub const X86_FIX_LP64_LONG_DOUBLE_BITS: usize = 128;
pub const X86_FIX_ILP32_LONG_BITS: usize = 32;
pub const X86_FIX_ILP32_POINTER_BITS: usize = 32;
pub const X86_FIX_MAX_INT_BITS: usize = 128;
pub const X86_FIX_MMX_BITS: usize = 64;
pub const X86_FIX_SSE_BITS: usize = 128;
pub const X86_FIX_AVX_BITS: usize = 256;
pub const X86_FIX_AVX512_BITS: usize = 512;
pub fn x86_fix_size_constants_lp64() -> HashMap<&'static str, usize> {
let mut m = HashMap::new();
m.insert("char", X86_FIX_LP64_CHAR_BITS);
m.insert("short", X86_FIX_LP64_SHORT_BITS);
m.insert("int", X86_FIX_LP64_INT_BITS);
m.insert("long", X86_FIX_LP64_LONG_BITS);
m.insert("long_long", X86_FIX_LP64_LONG_LONG_BITS);
m.insert("pointer", X86_FIX_LP64_POINTER_BITS);
m.insert("float", X86_FIX_LP64_FLOAT_BITS);
m.insert("double", X86_FIX_LP64_DOUBLE_BITS);
m.insert("long_double", X86_FIX_LP64_LONG_DOUBLE_BITS);
m.insert("max_int", X86_FIX_MAX_INT_BITS);
m.insert("mmx", X86_FIX_MMX_BITS);
m.insert("sse", X86_FIX_SSE_BITS);
m.insert("avx", X86_FIX_AVX_BITS);
m.insert("avx512", X86_FIX_AVX512_BITS);
m
}
pub fn x86_fix_size_constants_ilp32() -> HashMap<&'static str, usize> {
let mut m = x86_fix_size_constants_lp64();
m.insert("long", X86_FIX_ILP32_LONG_BITS);
m.insert("pointer", X86_FIX_ILP32_POINTER_BITS);
m
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86FixCallingConvention {
CDecl,
StdCall,
FastCall,
ThisCall,
VectorCall,
RegCall,
SysV,
Win64,
}
impl fmt::Display for X86FixCallingConvention {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86FixCallingConvention::CDecl => write!(f, "cdecl"),
X86FixCallingConvention::StdCall => write!(f, "stdcall"),
X86FixCallingConvention::FastCall => write!(f, "fastcall"),
X86FixCallingConvention::ThisCall => write!(f, "thiscall"),
X86FixCallingConvention::VectorCall => write!(f, "vectorcall"),
X86FixCallingConvention::RegCall => write!(f, "regcall"),
X86FixCallingConvention::SysV => write!(f, "sysv_abi"),
X86FixCallingConvention::Win64 => write!(f, "ms_abi"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86FixLowLevelType {
Void,
I1,
I8,
I16,
I32,
I64,
I128,
F32,
F64,
F80,
F128,
Pointer,
}
impl X86FixLowLevelType {
pub fn from_qual_type(ty: &QualType) -> Self {
match ty.base.as_ref() {
TypeNode::Void => Self::Void,
TypeNode::Bool => Self::I1,
TypeNode::Char | TypeNode::SChar | TypeNode::UChar => Self::I8,
TypeNode::Short | TypeNode::UShort => Self::I16,
TypeNode::Int | TypeNode::UInt => Self::I32,
TypeNode::Long | TypeNode::ULong => Self::I64,
TypeNode::LongLong | TypeNode::ULongLong => Self::I64,
TypeNode::Float => Self::F32,
TypeNode::Double => Self::F64,
TypeNode::LongDouble => Self::F128,
TypeNode::Pointer(..) => Self::Pointer,
_ => Self::I32,
}
}
pub fn bits(&self) -> usize {
match self {
Self::Void => 0,
Self::I1 => 1,
Self::I8 => 8,
Self::I16 => 16,
Self::I32 => 32,
Self::I64 => 64,
Self::I128 => 128,
Self::F32 => 32,
Self::F64 => 64,
Self::F80 => 80,
Self::F128 => 128,
Self::Pointer => 64,
}
}
}
pub fn x86_fix_type_to_string(ty: &QualType) -> String {
let mut s = String::new();
if ty.is_const {
s.push_str("const ");
}
if ty.is_volatile {
s.push_str("volatile ");
}
if ty.is_restrict {
s.push_str("restrict ");
}
match ty.base.as_ref() {
TypeNode::Void => s.push_str("void"),
TypeNode::Bool => s.push_str("_Bool"),
TypeNode::Char => s.push_str("char"),
TypeNode::SChar => s.push_str("signed char"),
TypeNode::UChar => s.push_str("unsigned char"),
TypeNode::Short => s.push_str("short"),
TypeNode::UShort => s.push_str("unsigned short"),
TypeNode::Int => s.push_str("int"),
TypeNode::UInt => s.push_str("unsigned int"),
TypeNode::Long => s.push_str("long"),
TypeNode::ULong => s.push_str("unsigned long"),
TypeNode::LongLong => s.push_str("long long"),
TypeNode::ULongLong => s.push_str("unsigned long long"),
TypeNode::Float => s.push_str("float"),
TypeNode::Double => s.push_str("double"),
TypeNode::LongDouble => s.push_str("long double"),
TypeNode::Pointer(inner) => {
s.push_str(&x86_fix_type_to_string(inner));
s.push('*');
}
TypeNode::Array { elem, size } => {
s.push_str(&x86_fix_type_to_string(elem));
s.push_str(&if let Some(sz) = size {
format!("[{}]", sz)
} else {
"[]".to_string()
});
}
TypeNode::Function {
ret,
params,
is_vararg,
} => {
s.push_str(&x86_fix_type_to_string(ret));
s.push('(');
let param_strs: Vec<String> = params.iter().map(x86_fix_type_to_string).collect();
s.push_str(¶m_strs.join(", "));
if *is_vararg {
if !params.is_empty() {
s.push_str(", ");
}
s.push_str("...");
}
s.push(')');
}
TypeNode::Struct {
name,
fields,
is_union,
} => {
if *is_union {
s.push_str("union ");
} else {
s.push_str("struct ");
}
s.push_str(name.as_deref().unwrap_or("<anonymous>"));
if !fields.is_empty() {
s.push_str(" { ");
let field_strs: Vec<String> = fields
.iter()
.map(|f| format!("{} {}", x86_fix_type_to_string(&f.ty), f.name))
.collect();
s.push_str(&field_strs.join("; "));
s.push_str("; }");
}
}
TypeNode::Enum { name, .. } => {
s.push_str("enum ");
s.push_str(name.as_deref().unwrap_or("<anonymous>"));
}
TypeNode::Typedef { name, .. } => {
s.push_str(name);
}
_ => s.push_str(&format!("{}", ty.base)),
}
s
}
#[cfg(test)]
mod tests {
use super::*;
fn make_sema() -> X86DeepSemaFix {
X86DeepSemaFix::new_x86_64()
}
fn make_sema_32() -> X86DeepSemaFix {
X86DeepSemaFix::new_x86_32()
}
fn int_ty() -> QualType {
QualType::int()
}
fn float_ty() -> QualType {
QualType::float()
}
fn double_ty() -> QualType {
QualType::double()
}
fn void_ty() -> QualType {
QualType::void()
}
fn char_ty() -> QualType {
QualType::char()
}
fn short_ty() -> QualType {
QualType::short()
}
fn long_ty() -> QualType {
QualType::long()
}
fn ptr_ty() -> QualType {
QualType::pointer_to(QualType::int())
}
#[test]
fn test_tu_fix_default_creation() {
let fix = X86TranslationUnitFix::new(TranslationUnit::new("test.c"));
assert_eq!(fix.target.arch, "x86_64");
assert_eq!(fix.target.data_model, DataModel::LP64);
assert_eq!(fix.language_standard.standard, "c17");
assert!(fix.source_file.is_main);
assert!(fix.system_includes.is_empty());
assert!(fix.defines.is_empty());
}
#[test]
fn test_tu_fix_32bit_target() {
let fix = X86TranslationUnitFix::new(TranslationUnit::new("test.c")).with_target(
TargetTripleInfo {
triple: "i386-unknown-linux-gnu".into(),
arch: "i386".into(),
vendor: "unknown".into(),
os: "linux".into(),
environment: "gnu".into(),
is_64bit: false,
data_model: DataModel::ILP32,
},
);
assert!(!fix.target.is_64bit);
assert_eq!(fix.target.data_model, DataModel::ILP32);
}
#[test]
fn test_tu_fix_add_includes() {
let mut fix = X86TranslationUnitFix::new(TranslationUnit::new("test.c"));
fix.add_system_include(IncludeRecord::system("stdio.h", 1));
fix.add_user_include(IncludeRecord::user("myheader.h", 2));
fix.add_framework_include(IncludeRecord::framework("Foundation", 3));
assert_eq!(fix.system_includes.len(), 1);
assert_eq!(fix.user_includes.len(), 1);
assert_eq!(fix.framework_includes.len(), 1);
assert_eq!(fix.all_includes().len(), 3);
}
#[test]
fn test_tu_fix_add_defines() {
let mut fix = X86TranslationUnitFix::new(TranslationUnit::new("test.c"));
fix.add_define(DefineRecord::new("FOO", Some("42"), 1).command_line());
fix.add_define(DefineRecord::new("BAR", None, 2).builtin());
assert!(fix.has_define("FOO"));
assert!(fix.has_define("BAR"));
assert_eq!(fix.define_value("FOO"), Some("42"));
assert_eq!(fix.define_value("BAR"), None);
assert!(!fix.has_define("BAZ"));
}
#[test]
fn test_tu_fix_nested_includes() {
let mut parent = IncludeRecord::system("parent.h", 1);
let child = IncludeRecord::user("child.h", 2).resolved("/usr/include/child.h");
parent.add_nested(child);
let mut fix = X86TranslationUnitFix::new(TranslationUnit::new("test.c"));
fix.add_system_include(parent);
assert_eq!(fix.total_include_count(), 2);
assert!(fix.missing_includes().len() == 1); }
#[test]
fn test_tu_fix_missing_includes() {
let mut fix = X86TranslationUnitFix::new(TranslationUnit::new("test.c"));
fix.add_system_include(IncludeRecord::system("nonexistent.h", 1));
fix.add_user_include(IncludeRecord::user("found.h", 2).resolved("/tmp/found.h"));
let missing = fix.missing_includes();
assert_eq!(missing.len(), 1);
assert_eq!(missing[0].written_path, "nonexistent.h");
}
#[test]
fn test_tu_fix_with_cxx_language() {
let fix = X86TranslationUnitFix::new(TranslationUnit::new("test.cpp"))
.with_language(LanguageStandard::cxx17());
assert_eq!(fix.language_standard.standard, "c++17");
assert!(fix.language_standard.is_cxx());
}
#[test]
fn test_tu_fix_display() {
let fix = X86TranslationUnitFix::new(TranslationUnit::new("test.c"));
let s = format!("{}", fix);
assert!(s.contains("TranslationUnitFix"));
assert!(s.contains("x86_64"));
assert!(s.contains("c17"));
}
#[test]
fn test_tu_fix_source_file_info() {
let sf = SourceFileInfo::new("/home/user/main.c")
.main()
.with_stats(100, 2048);
assert_eq!(sf.name, "main.c");
assert_eq!(sf.extension, ".c");
assert!(sf.is_c_source());
assert!(!sf.is_cxx_source());
assert_eq!(sf.line_count, 100);
}
#[test]
fn test_data_model_sizes() {
assert_eq!(DataModel::LP64.long_size(), 8);
assert_eq!(DataModel::LP64.pointer_size(), 8);
assert_eq!(DataModel::ILP32.long_size(), 4);
assert_eq!(DataModel::ILP32.pointer_size(), 4);
assert_eq!(DataModel::LLP64.long_size(), 4);
assert_eq!(DataModel::LLP64.pointer_size(), 8);
}
#[test]
fn test_define_record_expansion() {
let d1 = DefineRecord::new("A", Some("hello"), 1);
assert_eq!(d1.expanded_value(), "hello");
let d2 = DefineRecord::new("B", None, 2);
assert_eq!(d2.expanded_value(), "1");
}
#[test]
fn test_type_checker_same_type_compatible() {
let tc = X86TypeChecker::default();
assert!(tc.are_compatible(&QualType::int(), &QualType::int()));
assert!(tc.are_compatible(&QualType::float(), &QualType::float()));
assert!(tc.are_compatible(&QualType::void(), &QualType::void()));
}
#[test]
fn test_type_checker_int_float_not_strict() {
let tc = X86TypeChecker::new(DataModel::LP64);
let result = tc.check_compatibility(&QualType::int(), &QualType::float());
assert!(!result.compatible);
}
#[test]
fn test_type_checker_void_only_compat_with_void() {
let tc = X86TypeChecker::default();
assert!(tc.are_compatible(&QualType::void(), &QualType::void()));
assert!(!tc.are_compatible(&QualType::void(), &QualType::int()));
assert!(!tc.are_compatible(&QualType::int(), &QualType::void()));
}
#[test]
fn test_type_checker_pointer_to_void() {
let tc = X86TypeChecker::default();
let void_ptr = QualType::pointer_to(QualType::void());
let int_ptr = QualType::pointer_to(QualType::int());
assert!(tc.are_compatible(&void_ptr, &int_ptr));
assert!(tc.are_compatible(&int_ptr, &void_ptr));
}
#[test]
fn test_type_checker_integer_rank_comparison() {
let tc = X86TypeChecker::default();
assert!(
tc.compare_ranks(&QualType::int(), &QualType::short())
== Some(std::cmp::Ordering::Greater)
);
assert!(
tc.compare_ranks(&QualType::char(), &QualType::long())
== Some(std::cmp::Ordering::Less)
);
assert!(
tc.compare_ranks(&QualType::int(), &QualType::int()) == Some(std::cmp::Ordering::Equal)
);
}
#[test]
fn test_type_checker_pointer_conversion_void_ptr() {
let tc = X86TypeChecker::default();
let void_ptr = QualType::pointer_to(QualType::void());
let int_ptr = QualType::pointer_to(QualType::int());
assert_eq!(
tc.check_pointer_conversion(&int_ptr, &void_ptr),
PtrConversionKind::VoidPtr
);
assert_eq!(
tc.check_pointer_conversion(&int_ptr, &int_ptr),
PtrConversionKind::None
);
}
#[test]
fn test_type_checker_size_of_lp64() {
let tc = X86TypeChecker::new(DataModel::LP64);
assert_eq!(tc.size_of(&QualType::int()), 4);
assert_eq!(tc.size_of(&QualType::long()), 8);
assert_eq!(tc.size_of(&QualType::pointer_to(QualType::int())), 8);
assert_eq!(tc.size_of(&QualType::char()), 1);
assert_eq!(tc.size_of(&QualType::short()), 2);
}
#[test]
fn test_type_checker_size_of_ilp32() {
let tc = X86TypeChecker::new(DataModel::ILP32);
assert_eq!(tc.size_of(&QualType::int()), 4);
assert_eq!(tc.size_of(&QualType::long()), 4);
assert_eq!(tc.size_of(&QualType::pointer_to(QualType::int())), 4);
}
#[test]
fn test_type_checker_can_implicitly_convert() {
let tc = X86TypeChecker::default();
let (ok, _) = tc.can_implicitly_convert(&QualType::char(), &QualType::int());
assert!(ok);
let (ok, _) = tc.can_implicitly_convert(&QualType::int(), &QualType::char());
assert!(!ok);
let (ok, _) = tc.can_implicitly_convert(&QualType::int(), &QualType::float());
assert!(ok);
}
#[test]
fn test_type_checker_compound_compat_same_name_same_fields() {
let tc = X86TypeChecker::default();
let result = tc.check_compound_compatibility(
Some("Point"),
Some("Point"),
&[("x".into(), QualType::int()), ("y".into(), QualType::int())],
&[("x".into(), QualType::int()), ("y".into(), QualType::int())],
);
assert!(result.compatible);
}
#[test]
fn test_type_checker_compound_compat_different_fields() {
let tc = X86TypeChecker::default();
let result = tc.check_compound_compatibility(
Some("Point"),
Some("Point"),
&[("x".into(), QualType::int())],
&[("x".into(), QualType::int()), ("y".into(), QualType::int())],
);
assert!(!result.compatible);
}
#[test]
fn test_type_checker_register_typedef() {
let mut tc = X86TypeChecker::default();
tc.register_typedef("size_t", QualType::long());
assert!(tc.typedefs.contains_key("size_t"));
}
#[test]
fn test_type_checker_mode_assignment() {
let tc = X86TypeChecker::new(DataModel::LP64).with_mode(TypeCheckMode::Assignment);
assert!(tc.are_compatible(&QualType::int(), &QualType::long()));
}
#[test]
fn test_function_checker_void_function_no_return_ok() {
let mut fc = X86FunctionChecker::new();
let stmts = vec![fix_compound(vec![])];
let cf = fc.analyze_control_flow("foo", &stmts, &void_ty());
assert!(!cf.is_noreturn);
}
#[test]
fn test_function_checker_nonvoid_no_return_warning() {
let mut fc = X86FunctionChecker::new();
let stmts = vec![fix_compound(vec![])];
let cf = fc.analyze_control_flow("bar", &stmts, &int_ty());
assert!(!cf.all_paths_return);
}
#[test]
fn test_function_checker_has_return() {
let mut fc = X86FunctionChecker::new();
let stmts = vec![fix_return(Some(fix_int_lit(42)))];
let cf = fc.analyze_control_flow("baz", &stmts, &int_ty());
assert!(cf.any_path_returns);
assert!(cf.all_paths_return);
}
#[test]
fn test_function_checker_if_else_both_return() {
let mut fc = X86FunctionChecker::new();
let then_branch = fix_return(Some(fix_int_lit(1)));
let else_branch = fix_return(Some(fix_int_lit(0)));
let if_stmt = Stmt::If {
cond: Box::new(fix_int_lit(1)),
then: Box::new(then_branch),
els: Some(Box::new(else_branch)),
};
let cf = fc.analyze_control_flow("test", &[if_stmt], &int_ty());
assert!(cf.all_paths_return);
}
#[test]
fn test_function_checker_side_effects_pure() {
let fc = X86FunctionChecker::new();
let cls = fc.analyze_side_effects("f", false, false, false, false);
assert_eq!(cls, SideEffectClass::Pure);
}
#[test]
fn test_function_checker_side_effects_read_globals() {
let fc = X86FunctionChecker::new();
let cls = fc.analyze_side_effects("f", true, false, false, false);
assert_eq!(cls, SideEffectClass::ReadOnly);
}
#[test]
fn test_function_checker_side_effects_write_globals() {
let fc = X86FunctionChecker::new();
let cls = fc.analyze_side_effects("f", false, true, false, false);
assert_eq!(cls, SideEffectClass::HasEffects);
}
#[test]
fn test_function_checker_side_effects_asm() {
let fc = X86FunctionChecker::new();
let cls = fc.analyze_side_effects("f", false, false, false, true);
assert_eq!(cls, SideEffectClass::HasEffects);
}
#[test]
fn test_function_checker_recursion_direct() {
let mut fc = X86FunctionChecker::new();
fc.register_function("f", &["f".to_string()]);
let info = fc.detect_recursion("f");
assert!(info.direct);
}
#[test]
fn test_function_checker_recursion_mutual() {
let mut fc = X86FunctionChecker::new();
fc.register_function("a", &["b".to_string()]);
fc.register_function("b", &["a".to_string()]);
let info = fc.detect_recursion("a");
assert!(info.mutual);
assert!(!info.mutual_cycle.is_empty());
}
#[test]
fn test_function_checker_no_recursion() {
let mut fc = X86FunctionChecker::new();
fc.register_function("a", &["b".to_string()]);
fc.register_function("b", &["c".to_string()]);
fc.register_function("c", &[]);
let info = fc.detect_recursion("a");
assert!(!info.direct);
assert!(!info.mutual);
}
#[test]
fn test_function_checker_exception_compat_noexcept_caller_calls_throwing() {
let mut fc = X86FunctionChecker::new();
fc.set_exception_spec("caller", ExceptionSpec::NoExcept);
fc.set_exception_spec("callee", ExceptionSpec::None);
let (ok, reason) = fc.check_exception_compat("caller", "callee");
assert!(!ok);
assert!(reason.is_some());
}
#[test]
fn test_function_checker_exception_compat_noexcept_calls_noexcept() {
let mut fc = X86FunctionChecker::new();
fc.set_exception_spec("caller", ExceptionSpec::NoExcept);
fc.set_exception_spec("callee", ExceptionSpec::NoExcept);
let (ok, _) = fc.check_exception_compat("caller", "callee");
assert!(ok);
}
#[test]
fn test_function_checker_parameter_usage_read() {
let fc = X86FunctionChecker::new();
let stmts = vec![Stmt::Expr(fix_ident("x"))];
let params = vec![("x".into(), int_ty())];
let usage = fc.analyze_parameter_usage(¶ms, &stmts);
assert_eq!(usage.len(), 1);
assert!(usage[0].is_read);
assert!(!usage[0].is_written);
assert!(usage[0].is_used);
}
#[test]
fn test_function_checker_parameter_usage_unused() {
let fc = X86FunctionChecker::new();
let stmts = vec![fix_compound(vec![])];
let params = vec![("x".into(), int_ty())];
let usage = fc.analyze_parameter_usage(¶ms, &stmts);
assert!(!usage[0].is_used);
}
#[test]
fn test_function_checker_parameter_written() {
let fc = X86FunctionChecker::new();
let stmts = vec![Stmt::Expr(fix_assign(fix_ident("x"), fix_int_lit(42)))];
let params = vec![("x".into(), int_ty())];
let usage = fc.analyze_parameter_usage(¶ms, &stmts);
assert!(usage[0].is_written);
}
#[test]
fn test_value_analysis_constant_fold_int_lit() {
let va = X86ValueAnalysis::new();
assert_eq!(va.constant_fold(&fix_int_lit(42)), Some(42));
}
#[test]
fn test_value_analysis_constant_fold_binary_add() {
let va = X86ValueAnalysis::new();
let expr = fix_binary(BinaryOp::Add, fix_int_lit(10), fix_int_lit(20));
assert_eq!(va.constant_fold(&expr), Some(30));
}
#[test]
fn test_value_analysis_constant_fold_div_by_zero() {
let va = X86ValueAnalysis::new();
let expr = fix_binary(BinaryOp::Div, fix_int_lit(10), fix_int_lit(0));
assert_eq!(va.constant_fold(&expr), None); }
#[test]
fn test_value_analysis_constant_set_get() {
let mut va = X86ValueAnalysis::new();
va.set_constant("x", 100);
assert_eq!(va.get_constant("x"), Some(100));
assert_eq!(va.get_constant("y"), None);
}
#[test]
fn test_value_analysis_range() {
let mut va = X86ValueAnalysis::new();
va.set_range("x", ValueRange::bounded(0, 10));
let range = va.get_range("x");
assert!(range.contains(5));
assert!(!range.contains(11));
assert!(!range.contains(-1));
}
#[test]
fn test_value_analysis_range_constant() {
let range = ValueRange::constant(42);
assert!(range.is_constant);
assert_eq!(range.min, 42);
assert_eq!(range.max, 42);
}
#[test]
fn test_value_analysis_range_union() {
let r1 = ValueRange::bounded(0, 10);
let r2 = ValueRange::bounded(5, 15);
let u = r1.union(&r2);
assert_eq!(u.min, 0);
assert_eq!(u.max, 15);
}
#[test]
fn test_value_analysis_range_intersection() {
let r1 = ValueRange::bounded(0, 10);
let r2 = ValueRange::bounded(5, 15);
let i = r1.intersection(&r2).unwrap();
assert_eq!(i.min, 5);
assert_eq!(i.max, 10);
}
#[test]
fn test_value_analysis_range_intersection_disjoint() {
let r1 = ValueRange::bounded(0, 5);
let r2 = ValueRange::bounded(10, 15);
assert!(r1.intersection(&r2).is_none());
}
#[test]
fn test_value_analysis_null_state() {
let mut va = X86ValueAnalysis::new();
va.set_null_state("p", NullState::Null);
assert!(va.detect_null_deref("p"));
va.set_null_state("q", NullState::NotNull);
assert!(!va.detect_null_deref("q"));
}
#[test]
fn test_value_analysis_uninitialized() {
let mut va = X86ValueAnalysis::new();
va.set_init_state("x", InitState::Uninitialized);
va.set_init_state("y", InitState::Initialized);
let uninit = va.detect_uninitialized();
assert!(uninit.contains(&"x".to_string()));
assert!(!uninit.contains(&"y".to_string()));
}
#[test]
fn test_value_analysis_dead_store() {
let mut va = X86ValueAnalysis::new();
va.record_store("x", 10, Some("42"), false);
assert_eq!(va.dead_stores().len(), 1);
assert_eq!(va.dead_stores()[0].variable, "x");
}
#[test]
fn test_value_analysis_dead_store_live_var() {
let mut va = X86ValueAnalysis::new();
va.mark_live("x");
va.record_store("x", 10, Some("42"), false);
assert!(va.dead_stores().is_empty());
}
#[test]
fn test_value_analysis_range_add() {
let r1 = ValueRange::bounded(1, 10);
let r2 = ValueRange::bounded(5, 15);
let sum = r1.add(&r2);
assert_eq!(sum.min, 6);
assert_eq!(sum.max, 25);
}
#[test]
fn test_value_analysis_fold_binary_ops() {
let va = X86ValueAnalysis::new();
assert_eq!(
va.constant_fold(&fix_binary(BinaryOp::Sub, fix_int_lit(10), fix_int_lit(3))),
Some(7)
);
assert_eq!(
va.constant_fold(&fix_binary(BinaryOp::Mul, fix_int_lit(6), fix_int_lit(7))),
Some(42)
);
assert_eq!(
va.constant_fold(&fix_binary(BinaryOp::Eq, fix_int_lit(5), fix_int_lit(5))),
Some(1)
);
assert_eq!(
va.constant_fold(&fix_binary(BinaryOp::Lt, fix_int_lit(3), fix_int_lit(5))),
Some(1)
);
}
#[test]
fn test_value_analysis_fold_conditional() {
let va = X86ValueAnalysis::new();
let cond = Expr::Conditional(
Box::new(fix_int_lit(1)),
Box::new(fix_int_lit(42)),
Box::new(fix_int_lit(0)),
);
assert_eq!(va.constant_fold(&cond), Some(42));
}
#[test]
fn test_attr_checker_parse_standard_all_known() {
for name in X86AttributeChecker::all_attribute_names() {
let s = format!("{}", name);
let parsed = AttributeName::from_str(&s);
assert!(parsed.is_some(), "Failed to parse: {}", s);
}
}
#[test]
fn test_attr_checker_unknown_attribute() {
assert!(AttributeName::from_str("nonexistent_attr").is_none());
}
#[test]
fn test_attr_checker_valid_for_function() {
let checker = X86AttributeChecker::new();
assert!(checker.is_valid_for(&AttributeName::NoReturn, AttrApplyTarget::Function));
assert!(checker.is_valid_for(&AttributeName::Constructor, AttrApplyTarget::Function));
assert!(checker.is_valid_for(&AttributeName::Hot, AttrApplyTarget::Function));
}
#[test]
fn test_attr_checker_invalid_target() {
let mut checker = X86AttributeChecker::new();
let attr = AttributeInstance::new(AttributeName::NoReturn, AttrApplyTarget::Variable, 1);
checker.add_attribute("x", AttrApplyTarget::Variable, attr);
checker.validate_all();
assert!(!checker.errors.is_empty());
}
#[test]
fn test_attr_checker_duplicate_warning() {
let mut checker = X86AttributeChecker::new();
checker.add_attribute(
"f",
AttrApplyTarget::Function,
AttributeInstance::new(AttributeName::Pure, AttrApplyTarget::Function, 1),
);
checker.add_attribute(
"f",
AttrApplyTarget::Function,
AttributeInstance::new(AttributeName::Pure, AttrApplyTarget::Function, 2),
);
checker.validate_all();
assert!(checker.warnings.iter().any(|w| w.contains("duplicate")));
}
#[test]
fn test_attr_checker_conflict_hot_cold() {
let mut checker = X86AttributeChecker::new();
checker.add_attribute(
"f",
AttrApplyTarget::Function,
AttributeInstance::new(AttributeName::Hot, AttrApplyTarget::Function, 1),
);
checker.add_attribute(
"f",
AttrApplyTarget::Function,
AttributeInstance::new(AttributeName::Cold, AttrApplyTarget::Function, 2),
);
checker.validate_all();
assert!(checker.warnings.iter().any(|w| w.contains("conflicting")));
}
#[test]
fn test_attr_checker_is_noreturn() {
let mut checker = X86AttributeChecker::new();
checker.add_attribute(
"exit",
AttrApplyTarget::Function,
AttributeInstance::new(AttributeName::NoReturn, AttrApplyTarget::Function, 1),
);
assert!(checker.is_noreturn("exit"));
assert!(!checker.is_noreturn("main"));
}
#[test]
fn test_attr_checker_is_deprecated() {
let mut checker = X86AttributeChecker::new();
checker.add_attribute(
"old_func",
AttrApplyTarget::Function,
AttributeInstance::new(AttributeName::Deprecated, AttrApplyTarget::Function, 1),
);
assert!(checker.is_deprecated("old_func"));
}
#[test]
fn test_attr_checker_parse_gnu() {
let mut checker = X86AttributeChecker::new();
let result =
checker.parse_gnu_attribute("f", AttrApplyTarget::Function, "__attribute__((pure))", 1);
assert!(result.is_ok());
assert!(checker.has_attribute("f", AttributeName::Pure));
}
#[test]
fn test_attr_checker_parse_msvc() {
let mut checker = X86AttributeChecker::new();
let result =
checker.parse_msvc_attribute("f", AttrApplyTarget::Function, "__declspec(naked)", 1);
assert!(result.is_ok());
assert!(checker.has_attribute("f", AttributeName::Naked));
}
#[test]
fn test_attr_checker_conflict_naked_constructor() {
let mut checker = X86AttributeChecker::new();
checker.add_attribute(
"f",
AttrApplyTarget::Function,
AttributeInstance::new(AttributeName::Naked, AttrApplyTarget::Function, 1),
);
checker.add_attribute(
"f",
AttrApplyTarget::Function,
AttributeInstance::new(AttributeName::Constructor, AttrApplyTarget::Function, 2),
);
checker.validate_all();
assert!(checker.warnings.iter().any(|w| w.contains("naked")));
}
#[test]
fn test_attr_checker_get_function_attrs() {
let mut checker = X86AttributeChecker::new();
checker.add_attribute(
"f",
AttrApplyTarget::Function,
AttributeInstance::new(AttributeName::NoReturn, AttrApplyTarget::Function, 1),
);
let attrs = checker.get_function_attributes("f");
assert_eq!(attrs.len(), 1);
assert_eq!(attrs[0].name, AttributeName::NoReturn);
}
#[test]
fn test_attr_checker_no_attrs_for_unknown() {
let checker = X86AttributeChecker::new();
let attrs = checker.get_function_attributes("unknown");
assert!(attrs.is_empty());
}
#[test]
fn test_linkage_checker_single_def() {
let mut lc = X86LinkageChecker::new(false, "x86_64-unknown-linux-gnu");
lc.register_symbol(SymbolDef {
name: "foo".into(),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Strong,
comdat_group: None,
file: Some("a.c".into()),
line: 1,
is_inline: false,
is_template: false,
});
lc.analyze();
assert!(lc.odr_violations.is_empty());
}
#[test]
fn test_linkage_checker_odr_violation() {
let mut lc = X86LinkageChecker::new(true, "x86_64-unknown-linux-gnu");
lc.register_symbol(SymbolDef {
name: "foo".into(),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Strong,
comdat_group: None,
file: Some("a.c".into()),
line: 1,
is_inline: false,
is_template: false,
});
lc.register_symbol(SymbolDef {
name: "foo".into(),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Strong,
comdat_group: None,
file: Some("b.c".into()),
line: 5,
is_inline: false,
is_template: false,
});
lc.analyze();
assert!(!lc.odr_violations.is_empty());
assert!(lc.odr_violations[0].is_hard_error);
}
#[test]
fn test_linkage_checker_inline_ok() {
let mut lc = X86LinkageChecker::new(true, "x86_64-unknown-linux-gnu");
lc.register_symbol(SymbolDef {
name: "foo".into(),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Strong,
comdat_group: None,
file: Some("a.h".into()),
line: 1,
is_inline: true,
is_template: false,
});
lc.register_symbol(SymbolDef {
name: "foo".into(),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Strong,
comdat_group: None,
file: Some("b.h".into()),
line: 2,
is_inline: true,
is_template: false,
});
lc.analyze();
assert!(lc.odr_violations.is_empty());
}
#[test]
fn test_linkage_checker_weak_strong_resolution() {
let mut lc = X86LinkageChecker::new(false, "x86_64-unknown-linux-gnu");
lc.register_symbol(SymbolDef {
name: "bar".into(),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Strong,
comdat_group: None,
file: Some("a.c".into()),
line: 1,
is_inline: false,
is_template: false,
});
lc.register_symbol(SymbolDef {
name: "bar".into(),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Weak,
comdat_group: None,
file: Some("b.c".into()),
line: 2,
is_inline: false,
is_template: false,
});
lc.analyze();
assert!(lc.warnings.iter().any(|w| w.contains("multiple strong")));
}
#[test]
fn test_linkage_checker_visibility_inconsistency() {
let mut lc = X86LinkageChecker::new(false, "x86_64-unknown-linux-gnu");
lc.register_symbol(SymbolDef {
name: "baz".into(),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Strong,
comdat_group: None,
file: Some("a.c".into()),
line: 1,
is_inline: false,
is_template: false,
});
lc.register_symbol(SymbolDef {
name: "baz".into(),
is_definition: false,
linkage: Linkage::External,
visibility: SymbolVisibility::Hidden,
strength: SymbolStrength::Strong,
comdat_group: None,
file: Some("b.h".into()),
line: 2,
is_inline: false,
is_template: false,
});
lc.analyze();
assert!(lc.warnings.iter().any(|w| w.contains("visibility")));
}
#[test]
fn test_linkage_checker_comdat_noduplicates() {
let mut lc = X86LinkageChecker::new(false, "x86_64-unknown-linux-gnu");
lc.register_symbol(SymbolDef {
name: "singleton".into(),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Strong,
comdat_group: Some("grp".into()),
file: Some("a.c".into()),
line: 1,
is_inline: true,
is_template: false,
});
lc.register_symbol(SymbolDef {
name: "singleton".into(),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Strong,
comdat_group: Some("grp".into()),
file: Some("b.c".into()),
line: 2,
is_inline: true,
is_template: false,
});
lc.register_comdat(ComdatGroup {
name: "grp".into(),
selection_kind: ComdatSelection::NoDuplicates,
members: vec!["singleton".into()],
});
lc.analyze();
assert!(lc.warnings.iter().any(|w| w.contains("noduplicates")));
}
#[test]
fn test_linkage_checker_is_exported() {
let mut lc = X86LinkageChecker::new(false, "x86_64-unknown-linux-gnu");
lc.register_symbol(SymbolDef {
name: "exported_func".into(),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Strong,
comdat_group: None,
file: Some("a.c".into()),
line: 1,
is_inline: false,
is_template: false,
});
lc.register_symbol(SymbolDef {
name: "hidden_func".into(),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Hidden,
strength: SymbolStrength::Strong,
comdat_group: None,
file: Some("a.c".into()),
line: 2,
is_inline: false,
is_template: false,
});
assert!(lc.is_exported("exported_func"));
assert!(!lc.is_exported("hidden_func"));
assert!(!lc.is_exported("nonexistent"));
}
#[test]
fn test_linkage_checker_stats() {
let mut lc = X86LinkageChecker::new(false, "x86_64-unknown-linux-gnu");
lc.register_symbol(SymbolDef {
name: "foo".into(),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Strong,
comdat_group: None,
file: Some("a.c".into()),
line: 1,
is_inline: false,
is_template: false,
});
lc.register_symbol(SymbolDef {
name: "bar".into(),
is_definition: false,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Strong,
comdat_group: None,
file: Some("a.h".into()),
line: 2,
is_inline: false,
is_template: false,
});
let stats = lc.stats();
assert_eq!(stats.total_symbols, 2);
assert_eq!(stats.definitions, 1);
assert_eq!(stats.declarations, 1);
}
#[test]
fn test_linkage_checker_empty_comdat() {
let mut lc = X86LinkageChecker::new(false, "x86_64-unknown-linux-gnu");
lc.register_comdat(ComdatGroup {
name: "empty".into(),
selection_kind: ComdatSelection::Any,
members: vec![],
});
lc.analyze();
assert!(lc.warnings.iter().any(|w| w.contains("no members")));
}
#[test]
fn test_linkage_effective_visibility() {
let mut lc = X86LinkageChecker::new(false, "x86_64-unknown-linux-gnu");
lc.register_symbol(SymbolDef {
name: "f".into(),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Protected,
strength: SymbolStrength::Strong,
comdat_group: None,
file: Some("a.c".into()),
line: 1,
is_inline: false,
is_template: false,
});
assert_eq!(lc.effective_visibility("f"), SymbolVisibility::Protected);
}
#[test]
fn test_linkage_stats_display() {
let stats = LinkageStats {
total_symbols: 10,
definitions: 5,
declarations: 5,
odr_violations: 1,
..Default::default()
};
let s = format!("{}", stats);
assert!(s.contains("10"));
assert!(s.contains("5"));
assert!(s.contains("1"));
}
#[test]
fn test_deep_sema_fix_creation() {
let sema = X86DeepSemaFix::new_x86_64();
assert_eq!(sema.tu.target.arch, "x86_64");
assert!(sema.tu.target.is_64bit);
}
#[test]
fn test_deep_sema_fix_32bit_creation() {
let sema = X86DeepSemaFix::new_x86_32();
assert!(!sema.tu.target.is_64bit);
}
#[test]
fn test_deep_sema_fix_empty_tu() {
let mut sema = make_sema();
let tu = fix_make_tu(vec![]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
assert!(sema.errors.is_empty());
}
#[test]
fn test_deep_sema_fix_simple_function() {
let mut sema = make_sema();
let func = fix_make_function("foo", int_ty(), vec![], Some(CompoundStmt::new()));
let tu = fix_make_tu(vec![Decl::Function(func)]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
}
#[test]
fn test_deep_sema_fix_nonvoid_no_return_warning() {
let mut sema = make_sema();
let mut body = CompoundStmt::new();
body.push(fix_compound(vec![]));
let func = fix_make_function("bar", int_ty(), vec![], Some(body));
let tu = fix_make_tu(vec![Decl::Function(func)]);
let _ = sema.analyze_tu(&tu);
assert!(sema
.warnings
.iter()
.any(|w| w.contains("not all control paths return")));
}
#[test]
fn test_deep_sema_fix_function_with_return() {
let mut sema = make_sema();
let mut body = CompoundStmt::new();
body.push(fix_return(Some(fix_int_lit(0))));
let func = fix_make_function("baz", int_ty(), vec![], Some(body));
let tu = fix_make_tu(vec![Decl::Function(func)]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
}
#[test]
fn test_deep_sema_fix_void_function() {
let mut sema = make_sema();
let mut body = CompoundStmt::new();
body.push(fix_return(None));
let func = fix_make_function("vfunc", void_ty(), vec![], Some(body));
let tu = fix_make_tu(vec![Decl::Function(func)]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
}
#[test]
fn test_deep_sema_fix_noreturn_attribute() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "abort".into(),
ret_ty: void_ty(),
params: vec![],
body: None,
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: true,
};
let tu = fix_make_tu(vec![Decl::Function(func)]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
assert!(sema.attribute_checker.is_noreturn("abort"));
}
#[test]
fn test_deep_sema_fix_type_of_expr() {
let sema = make_sema();
assert_eq!(sema.type_of_expr(&fix_int_lit(42)), Some(int_ty()));
}
#[test]
fn test_deep_sema_fix_check_type_compatibility() {
let sema = make_sema();
assert!(sema.check_type_compatibility(&int_ty(), &int_ty()));
assert!(!sema.check_type_compatibility(&int_ty(), &float_ty()));
}
#[test]
fn test_deep_sema_fix_evaluate_constexpr() {
let sema = make_sema();
let expr = fix_binary(BinaryOp::Add, fix_int_lit(3), fix_int_lit(4));
assert_eq!(sema.evaluate_constexpr(&expr), Some(7));
}
#[test]
fn test_deep_sema_fix_dump_symbol_table() {
let sema = make_sema();
let output = sema.dump_symbol_table();
assert!(output.contains("Symbol Table"));
}
#[test]
fn test_deep_sema_fix_linkage_stats() {
let sema = make_sema();
let stats = sema.linkage_stats();
assert_eq!(stats.total_symbols, 0);
}
#[test]
fn test_deep_sema_fix_multiple_functions() {
let mut sema = make_sema();
let func1 = fix_make_function("f1", int_ty(), vec![], Some(CompoundStmt::new()));
let mut body2 = CompoundStmt::new();
body2.push(fix_return(Some(fix_int_lit(1))));
let func2 = fix_make_function("f2", int_ty(), vec![], Some(body2));
let tu = fix_make_tu(vec![Decl::Function(func1), Decl::Function(func2)]);
let _ = sema.analyze_tu(&tu);
}
#[test]
fn test_deep_sema_fix_struct_registration() {
let mut sema = make_sema();
let s = StructDecl {
name: "Point".into(),
fields: vec![
FieldDecl::new("x", QualType::int()),
FieldDecl::new("y", QualType::int()),
],
is_union: false,
};
let tu = fix_make_tu(vec![Decl::Struct(s)]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
}
#[test]
fn test_deep_sema_fix_enum_registration() {
let mut sema = make_sema();
let e = EnumDecl {
name: "Color".into(),
variants: vec![
EnumVariant::new("RED", 0),
EnumVariant::new("GREEN", 1),
EnumVariant::new("BLUE", 2),
],
underlying: QualType::int(),
};
let tu = fix_make_tu(vec![Decl::Enum(e)]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
}
#[test]
fn test_deep_sema_fix_typedef_registration() {
let mut sema = make_sema();
let td = TypedefDecl {
name: "size_t".into(),
underlying: QualType::long(),
};
let tu = fix_make_tu(vec![Decl::Typedef(td)]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
}
#[test]
fn test_deep_sema_fix_clear_state() {
let mut sema = make_sema();
sema.errors.push("test error".into());
sema.warnings.push("test warning".into());
sema.clear();
assert!(sema.errors.is_empty());
assert!(sema.warnings.is_empty());
assert!(sema.success);
}
#[test]
fn test_make_x86_64_deep_sema_fix() {
let sema = make_x86_64_deep_sema_fix();
assert!(sema.tu.target.is_64bit);
}
#[test]
fn test_make_x86_32_deep_sema_fix() {
let sema = make_x86_32_deep_sema_fix();
assert!(!sema.tu.target.is_64bit);
}
#[test]
fn test_analyze_x86_tu_fix() {
let tu = fix_make_tu(vec![]);
let (ok, _, _) = analyze_x86_tu_fix(&tu);
assert!(ok);
}
#[test]
fn test_analyze_x86_function_fix() {
let func = fix_make_function("f", int_ty(), vec![], Some(CompoundStmt::new()));
let (ok, _, _) = analyze_x86_function_fix(&func);
assert!(ok);
}
#[test]
fn test_contains_x86_fix_message() {
let msgs = vec!["error: something".into(), "warning: test".into()];
assert!(contains_x86_fix_message(&msgs, "error"));
assert!(!contains_x86_fix_message(&msgs, "fatal"));
}
#[test]
fn test_make_fixed_tu() {
let tu = TranslationUnit::new("test.c");
let fix = make_fixed_tu(tu);
assert_eq!(fix.source_file.name, "test.c");
}
#[test]
fn test_make_fixed_tu_with_includes() {
let tu = TranslationUnit::new("test.c");
let fix = make_fixed_tu_with_includes(tu, vec!["stdio.h"], vec!["my.h"]);
assert_eq!(fix.system_includes.len(), 1);
assert_eq!(fix.user_includes.len(), 1);
}
#[test]
fn test_fix_helpers() {
let lit = fix_int_lit(42);
assert!(matches!(lit, Expr::IntLiteral(42)));
let ident = fix_ident("x");
assert!(matches!(ident, Expr::Ident(_)));
let bin = fix_binary(BinaryOp::Add, fix_int_lit(1), fix_int_lit(2));
assert!(matches!(bin, Expr::Binary(..)));
let assign = fix_assign(fix_ident("a"), fix_int_lit(10));
assert!(matches!(assign, Expr::Assign(..)));
let ret = fix_return(Some(fix_int_lit(0)));
assert!(matches!(ret, Stmt::Return(_)));
let compound = fix_compound(vec![]);
assert!(matches!(compound, Stmt::Compound(_)));
let call = fix_call(fix_ident("f"), vec![fix_int_lit(1)]);
assert!(matches!(call, Expr::Call { .. }));
let func = fix_make_function("main", int_ty(), vec![], None);
assert_eq!(func.name, "main");
let var = fix_make_variable("g", int_ty(), None);
assert_eq!(var.name, "g");
let tu = fix_make_tu(vec![]);
assert!(tu.decls.is_empty());
}
#[test]
fn test_stress_many_includes() {
let mut fix = X86TranslationUnitFix::new(TranslationUnit::new("test.c"));
for i in 0..100 {
fix.add_system_include(IncludeRecord::system(&format!("header_{}.h", i), i as u32));
fix.add_define(DefineRecord::new(
&format!("DEF_{}", i),
Some(&format!("{}", i)),
i as u32,
));
}
assert_eq!(fix.system_includes.len(), 100);
assert_eq!(fix.defines.len(), 100);
assert_eq!(fix.total_include_count(), 100);
}
#[test]
fn test_stress_many_attributes() {
let mut checker = X86AttributeChecker::new();
for i in 0..50 {
checker.add_attribute(
&format!("func_{}", i),
AttrApplyTarget::Function,
AttributeInstance::new(AttributeName::NoReturn, AttrApplyTarget::Function, i),
);
}
checker.validate_all();
assert!(checker.errors.is_empty());
for i in 0..50 {
assert!(checker.is_noreturn(&format!("func_{}", i)));
}
}
#[test]
fn test_stress_many_symbols() {
let mut lc = X86LinkageChecker::new(false, "x86_64-unknown-linux-gnu");
for i in 0..100 {
lc.register_symbol(SymbolDef {
name: format!("sym_{}", i),
is_definition: i % 2 == 0,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Strong,
comdat_group: None,
file: Some(format!("file_{}.c", i)),
line: i,
is_inline: false,
is_template: false,
});
}
lc.analyze();
assert_eq!(lc.stats().total_symbols, 100);
}
#[test]
fn test_stress_nested_call_graph() {
let mut fc = X86FunctionChecker::new();
for i in 0..99 {
fc.register_function(&format!("f{}", i), &[format!("f{}", i + 1)]);
}
fc.register_function("f99", &[]);
let info = fc.detect_recursion("f0");
assert!(!info.direct);
assert!(!info.mutual);
}
#[test]
fn test_stress_constant_folding() {
let va = X86ValueAnalysis::new();
let add12 = fix_binary(BinaryOp::Add, fix_int_lit(1), fix_int_lit(2));
let add34 = fix_binary(BinaryOp::Add, fix_int_lit(3), fix_int_lit(4));
let mul_l = fix_binary(BinaryOp::Mul, add12, add34);
let mul_r = fix_binary(BinaryOp::Mul, fix_int_lit(5), fix_int_lit(6));
let sub = fix_binary(BinaryOp::Sub, mul_l, mul_r);
let div = fix_binary(BinaryOp::Div, sub, fix_int_lit(2));
assert_eq!(va.constant_fold(&div), Some(-4));
}
#[test]
fn smoke_x86_deep_sema_fix_creation() {
let sema = X86DeepSemaFix::default();
assert!(sema.tu.target.is_64bit);
}
#[test]
fn smoke_x86_type_checker_creation() {
let tc = X86TypeChecker::default();
assert_eq!(tc.data_model, DataModel::LP64);
}
#[test]
fn smoke_x86_function_checker_creation() {
let fc = X86FunctionChecker::default();
assert!(fc.call_graph.is_empty());
}
#[test]
fn smoke_x86_value_analysis_creation() {
let va = X86ValueAnalysis::default();
assert!(va.dead_stores().is_empty());
}
#[test]
fn smoke_x86_attribute_checker_creation() {
let ac = X86AttributeChecker::default();
assert!(ac.errors.is_empty());
}
#[test]
fn smoke_x86_linkage_checker_creation() {
let lc = X86LinkageChecker::default();
assert!(!lc.is_cxx);
}
#[test]
fn smoke_all_convenience_functions() {
let sema64 = make_x86_64_deep_sema_fix();
assert!(sema64.tu.target.is_64bit);
let sema32 = make_x86_32_deep_sema_fix();
assert!(!sema32.tu.target.is_64bit);
let tu = fix_make_tu(vec![]);
let (ok, _, _) = analyze_x86_tu_fix(&tu);
assert!(ok);
let func = fix_make_function("f", int_ty(), vec![], None);
let (ok2, _, _) = analyze_x86_function_fix(&func);
assert!(ok2);
}
#[test]
fn smoke_type_checker_modes() {
let tc_strict = X86TypeChecker::default().with_mode(TypeCheckMode::Strict);
let tc_assign = X86TypeChecker::default().with_mode(TypeCheckMode::Assignment);
let tc_struct = X86TypeChecker::default().with_mode(TypeCheckMode::Structural);
let tc_oload = X86TypeChecker::default().with_mode(TypeCheckMode::Overload);
assert!(tc_strict.are_compatible(&int_ty(), &int_ty()));
assert!(tc_assign.are_compatible(&int_ty(), &int_ty()));
assert!(tc_struct.are_compatible(&int_ty(), &int_ty()));
assert!(tc_oload.are_compatible(&int_ty(), &int_ty()));
}
#[test]
fn smoke_attribute_sources() {
assert_eq!(AttributeName::NoReturn.source(), AttributeSource::Standard);
assert_eq!(AttributeName::Packed.source(), AttributeSource::Gnu);
assert_eq!(AttributeName::DllImport.source(), AttributeSource::Msvc);
assert_eq!(AttributeName::Interrupt.source(), AttributeSource::X86);
}
#[test]
fn smoke_x86_fix_int_rank_from_type() {
assert_eq!(
X86FixIntRank::from_type(&QualType::int()),
Some(X86FixIntRank::Int)
);
assert_eq!(
X86FixIntRank::from_type(&QualType::long()),
Some(X86FixIntRank::Long)
);
assert_eq!(X86FixIntRank::from_type(&QualType::float()), None);
}
#[test]
fn smoke_no_panic_empty_analysis() {
let mut sema = make_sema();
let tu = fix_make_tu(vec![]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
let mut fc = X86FunctionChecker::new();
let cf = fc.analyze_control_flow("empty", &[], &void_ty());
assert!(!cf.is_noreturn);
}
#[test]
fn test_x86_abi_long_size_lp64() {
let tc = X86TypeChecker::new(DataModel::LP64);
assert_eq!(tc.size_of(&QualType::long()), 8);
}
#[test]
fn test_x86_abi_long_size_ilp32() {
let tc = X86TypeChecker::new(DataModel::ILP32);
assert_eq!(tc.size_of(&QualType::long()), 4);
}
#[test]
fn test_x86_abi_pointer_size_lp64() {
let tc = X86TypeChecker::new(DataModel::LP64);
assert_eq!(tc.size_of(&QualType::pointer_to(QualType::void())), 8);
}
#[test]
fn test_x86_abi_pointer_size_ilp32() {
let tc = X86TypeChecker::new(DataModel::ILP32);
assert_eq!(tc.size_of(&QualType::pointer_to(QualType::void())), 4);
}
#[test]
fn test_x86_abi_long_long_always_8() {
let tc_lp64 = X86TypeChecker::new(DataModel::LP64);
let tc_ilp32 = X86TypeChecker::new(DataModel::ILP32);
assert_eq!(tc_lp64.size_of(&QualType::long_long()), 8);
assert_eq!(tc_ilp32.size_of(&QualType::long_long()), 8);
}
#[test]
fn test_x86_abi_int_always_4() {
let tc_lp64 = X86TypeChecker::new(DataModel::LP64);
let tc_ilp32 = X86TypeChecker::new(DataModel::ILP32);
assert_eq!(tc_lp64.size_of(&QualType::int()), 4);
assert_eq!(tc_ilp32.size_of(&QualType::int()), 4);
}
#[test]
fn test_x86_interrupt_attribute() {
let mut checker = X86AttributeChecker::new();
checker.add_attribute(
"isr_handler",
AttrApplyTarget::Function,
AttributeInstance::new(AttributeName::Interrupt, AttrApplyTarget::Function, 1),
);
assert!(checker.has_attribute("isr_handler", AttributeName::Interrupt));
}
#[test]
fn test_x86_signal_attribute() {
let mut checker = X86AttributeChecker::new();
checker.add_attribute(
"sig_handler",
AttrApplyTarget::Function,
AttributeInstance::new(AttributeName::Signal, AttrApplyTarget::Function, 1),
);
assert!(checker.has_attribute("sig_handler", AttributeName::Signal));
}
#[test]
fn test_x86_target_attributes() {
let mut checker = X86AttributeChecker::new();
checker.add_attribute(
"sse4_func",
AttrApplyTarget::Function,
AttributeInstance::new(AttributeName::CpuTarget, AttrApplyTarget::Function, 1)
.with_args(vec!["sse4.2".into()]),
);
checker.add_attribute(
"tuned_func",
AttrApplyTarget::Function,
AttributeInstance::new(AttributeName::CpuTune, AttrApplyTarget::Function, 2)
.with_args(vec!["znver3".into()]),
);
assert!(checker.has_attribute("sse4_func", AttributeName::CpuTarget));
assert!(checker.has_attribute("tuned_func", AttributeName::CpuTune));
}
#[test]
fn test_llp64_data_model() {
let tc = X86TypeChecker::new(DataModel::LLP64);
assert_eq!(tc.size_of(&QualType::long()), 4);
assert_eq!(tc.size_of(&QualType::pointer_to(QualType::void())), 8);
assert_eq!(tc.size_of(&QualType::long_long()), 8);
}
#[test]
fn test_msvc_attributes_recognized() {
let mut checker = X86AttributeChecker::new();
let result =
checker.parse_msvc_attribute("f", AttrApplyTarget::Function, "__declspec(noinline)", 1);
assert!(result.is_ok());
assert!(checker.has_attribute("f", AttributeName::NoInline));
}
#[test]
fn test_msvc_dll_attributes() {
let mut checker = X86AttributeChecker::new();
checker
.parse_msvc_attribute(
"exported",
AttrApplyTarget::Function,
"__declspec(dllexport)",
1,
)
.ok();
checker
.parse_msvc_attribute(
"imported",
AttrApplyTarget::Function,
"__declspec(dllimport)",
2,
)
.ok();
assert!(checker.has_attribute("exported", AttributeName::DllExport));
assert!(checker.has_attribute("imported", AttributeName::DllImport));
}
#[test]
fn test_all_components_initialized() {
let sema = make_sema();
let _ = &sema.tu;
let _ = &sema.type_checker;
let _ = &sema.function_checker;
let _ = &sema.value_analysis;
let _ = &sema.attribute_checker;
let _ = &sema.linkage_checker;
let _ = &sema.sema;
}
#[test]
fn test_independent_instances() {
let mut s1 = make_sema();
let mut s2 = make_sema_32();
let tu1 = fix_make_tu(vec![]);
let tu2 = fix_make_tu(vec![]);
let ok1 = s1.analyze_tu(&tu1);
let ok2 = s2.analyze_tu(&tu2);
assert!(ok1);
assert!(ok2);
assert!(s1.tu.target.is_64bit);
assert!(!s2.tu.target.is_64bit);
}
#[test]
fn test_qualifier_compat_adding_const() {
let tc = X86TypeChecker::default();
let nonconst = QualType::int();
let mut const_ty = QualType::int();
const_ty.is_const = true;
assert!(!tc.check_qualifier_compat(&nonconst, &const_ty));
assert!(tc.check_qualifier_compat(&const_ty, &nonconst));
}
#[test]
fn test_resolve_typedef_chain() {
let mut tc = X86TypeChecker::default();
tc.register_typedef("myint", QualType::int());
let td_ty = QualType {
base: TypeNode::Typedef {
name: "myint".into(),
underlying: Box::new(TypeNode::Int),
},
..QualType::int()
};
let resolved = tc.resolve_typedef(&td_ty);
assert!(matches!(resolved.base, TypeNode::Int));
}
#[test]
fn test_strip_qualifiers() {
let tc = X86TypeChecker::default();
let mut ty = QualType::int();
ty.is_const = true;
ty.is_volatile = true;
let stripped = tc.strip_qualifiers(&ty);
assert!(!stripped.is_const);
assert!(!stripped.is_volatile);
}
#[test]
fn test_is_character_type() {
let tc = X86TypeChecker::default();
assert!(tc.is_character_type(&QualType::char()));
assert!(tc.is_character_type(&QualType::schar()));
assert!(tc.is_character_type(&QualType::uchar()));
assert!(!tc.is_character_type(&QualType::int()));
}
#[test]
fn test_to_signed_unsigned() {
let tc = X86TypeChecker::default();
assert!(matches!(
tc.to_signed(&QualType::uint()).base,
TypeNode::Int
));
assert!(matches!(
tc.to_unsigned(&QualType::int()).base,
TypeNode::UInt
));
}
#[test]
fn test_abi_classification_int() {
let tc = X86TypeChecker::default();
assert_eq!(
tc.abi_classification(&QualType::int()),
X86AbiTypeClass::Integer
);
}
#[test]
fn test_abi_classification_float() {
let tc = X86TypeChecker::default();
assert_eq!(
tc.abi_classification(&QualType::float()),
X86AbiTypeClass::Sse
);
assert_eq!(
tc.abi_classification(&QualType::double()),
X86AbiTypeClass::Sse
);
}
#[test]
fn test_abi_classification_long_double() {
let tc = X86TypeChecker::default();
assert_eq!(
tc.abi_classification(&QualType::long_double()),
X86AbiTypeClass::X87
);
}
#[test]
fn test_abi_classification_void() {
let tc = X86TypeChecker::default();
assert_eq!(
tc.abi_classification(&QualType::void()),
X86AbiTypeClass::NoClass
);
}
#[test]
fn test_all_primitive_sizes_lp64_coherent() {
let sizes = X86TypeChecker::all_primitive_sizes_lp64();
assert_eq!(sizes.iter().find(|(n, _)| *n == "int").unwrap().1, 4);
assert_eq!(sizes.iter().find(|(n, _)| *n == "long").unwrap().1, 8);
assert_eq!(sizes.iter().find(|(n, _)| *n == "pointer").unwrap().1, 8);
}
#[test]
fn test_all_primitive_sizes_ilp32_coherent() {
let sizes = X86TypeChecker::all_primitive_sizes_ilp32();
assert_eq!(sizes.iter().find(|(n, _)| *n == "int").unwrap().1, 4);
assert_eq!(sizes.iter().find(|(n, _)| *n == "long").unwrap().1, 4);
assert_eq!(sizes.iter().find(|(n, _)| *n == "pointer").unwrap().1, 4);
}
#[test]
fn test_type_checker_default_mode_is_strict() {
let tc = X86TypeChecker::default();
let result = tc.check_compatibility(&QualType::int(), &QualType::long());
assert!(!result.compatible);
}
#[test]
fn test_build_cfg_empty_function() {
let fc = X86FunctionChecker::new();
let cfg = fc.build_cfg("empty", &[]);
assert_eq!(cfg.function_name, "empty");
assert_eq!(cfg.entry, 0);
assert_eq!(cfg.blocks.len(), 2); }
#[test]
fn test_build_cfg_simple() {
let fc = X86FunctionChecker::new();
let stmts = vec![Stmt::Expr(fix_int_lit(1)), fix_return(Some(fix_int_lit(0)))];
let cfg = fc.build_cfg("simple", &stmts);
assert!(cfg.blocks.len() >= 2);
}
#[test]
fn test_cfg_is_reachable() {
let fc = X86FunctionChecker::new();
let cfg = fc.build_cfg("f", &[fix_return(Some(fix_int_lit(0)))]);
assert!(cfg.is_reachable(cfg.entry));
}
#[test]
fn test_interprocedural_side_effects_propagation() {
let mut fc = X86FunctionChecker::new();
fc.register_function("leaf", &[]);
fc.set_side_effects("leaf", SideEffectClass::Pure);
fc.register_function("caller", &["leaf".to_string()]);
fc.set_side_effects("caller", SideEffectClass::Unknown);
fc.interprocedural_side_effects();
assert_eq!(fc.get_side_effects("caller"), SideEffectClass::Pure);
}
#[test]
fn test_interprocedural_side_effects_worst_case() {
let mut fc = X86FunctionChecker::new();
fc.register_function("pure_leaf", &[]);
fc.set_side_effects("pure_leaf", SideEffectClass::Pure);
fc.register_function("effect_leaf", &[]);
fc.set_side_effects("effect_leaf", SideEffectClass::HasEffects);
fc.register_function(
"caller",
&["pure_leaf".to_string(), "effect_leaf".to_string()],
);
fc.set_side_effects("caller", SideEffectClass::Unknown);
fc.interprocedural_side_effects();
assert_eq!(fc.get_side_effects("caller"), SideEffectClass::HasEffects);
}
#[test]
fn test_may_be_recursive_indirect() {
let mut fc = X86FunctionChecker::new();
fc.register_function("a", &["b".to_string()]);
fc.register_function("b", &["c".to_string()]);
fc.register_function("c", &["a".to_string()]);
assert!(fc.may_be_recursive_indirect("a"));
}
#[test]
fn test_may_be_recursive_indirect_no_cycle() {
let mut fc = X86FunctionChecker::new();
fc.register_function("a", &["b".to_string()]);
fc.register_function("b", &["c".to_string()]);
fc.register_function("c", &[]);
assert!(!fc.may_be_recursive_indirect("a"));
}
#[test]
fn test_call_depth_linear() {
let mut fc = X86FunctionChecker::new();
fc.register_function("f0", &["f1".to_string()]);
fc.register_function("f1", &["f2".to_string()]);
fc.register_function("f2", &["f3".to_string()]);
fc.register_function("f3", &[]);
assert_eq!(fc.call_depth("f0"), 3);
}
#[test]
fn test_call_depth_leaf() {
let mut fc = X86FunctionChecker::new();
fc.register_function("leaf", &[]);
assert_eq!(fc.call_depth("leaf"), 0);
}
#[test]
fn test_parameter_usage_assignment_write() {
let fc = X86FunctionChecker::new();
let stmts = vec![
Stmt::Expr(fix_assign(fix_ident("x"), fix_int_lit(1))),
Stmt::Expr(fix_ident("x")), ];
let params = vec![("x".into(), int_ty())];
let usage = fc.analyze_parameter_usage(¶ms, &stmts);
assert!(usage[0].is_read);
assert!(usage[0].is_written);
assert!(usage[0].is_used);
}
#[test]
fn test_side_effects_merge() {
let mut fc = X86FunctionChecker::new();
fc.set_side_effects("a", SideEffectClass::HasEffects);
fc.set_side_effects("b", SideEffectClass::Pure);
fc.register_function("a", &[]);
fc.register_function("b", &[]);
fc.register_function("c", &["a".to_string(), "b".to_string()]);
fc.set_side_effects("c", SideEffectClass::Unknown);
fc.interprocedural_side_effects();
assert_eq!(fc.get_side_effects("c"), SideEffectClass::HasEffects);
}
#[test]
fn test_range_propagation_assign_constant() {
let mut va = X86ValueAnalysis::new();
let stmts = vec![Stmt::Expr(fix_assign(fix_ident("x"), fix_int_lit(42)))];
va.propagate_ranges(&stmts);
assert_eq!(va.get_constant("x"), Some(42));
}
#[test]
fn test_range_propagation_assign_binary() {
let mut va = X86ValueAnalysis::new();
let stmts = vec![Stmt::Expr(fix_assign(
fix_ident("y"),
fix_binary(BinaryOp::Add, fix_int_lit(2), fix_int_lit(3)),
))];
va.propagate_ranges(&stmts);
assert_eq!(va.get_constant("y"), Some(5));
}
#[test]
fn test_range_propagation_compound() {
let mut va = X86ValueAnalysis::new();
let stmts = vec![fix_compound(vec![
Stmt::Expr(fix_assign(fix_ident("a"), fix_int_lit(10))),
Stmt::Expr(fix_assign(fix_ident("b"), fix_int_lit(20))),
])];
va.propagate_ranges(&stmts);
assert_eq!(va.get_constant("a"), Some(10));
assert_eq!(va.get_constant("b"), Some(20));
}
#[test]
fn test_dead_store_detection_unused() {
let mut va = X86ValueAnalysis::new();
let stmts = vec![Stmt::Expr(fix_assign(fix_ident("unused"), fix_int_lit(99)))];
let dead = va.run_dead_store_detection(&stmts);
assert!(dead.iter().any(|d| d.variable == "unused"));
}
#[test]
fn test_dead_store_detection_used() {
let mut va = X86ValueAnalysis::new();
let stmts = vec![
Stmt::Expr(fix_assign(fix_ident("used"), fix_int_lit(99))),
Stmt::Expr(fix_ident("used")),
];
let dead = va.run_dead_store_detection(&stmts);
assert!(!dead.iter().any(|d| d.variable == "used"));
}
#[test]
fn test_live_variable_analysis_basic() {
let mut va = X86ValueAnalysis::new();
let stmts = vec![
Stmt::Expr(fix_assign(fix_ident("x"), fix_int_lit(1))),
Stmt::Expr(fix_ident("x")),
];
va.live_variable_analysis(&stmts);
assert!(va.live_vars.contains("x"));
}
#[test]
fn test_range_compute_expr_conditional() {
let va = X86ValueAnalysis::new();
let cond = Expr::Conditional(
Box::new(fix_int_lit(1)),
Box::new(fix_int_lit(0)),
Box::new(fix_int_lit(10)),
);
let range = va.compute_expr_range(&cond);
assert_eq!(range.min, 0);
assert_eq!(range.max, 10);
}
#[test]
fn test_range_contains_boundary() {
let range = ValueRange::bounded(0, 100);
assert!(range.contains(0));
assert!(range.contains(100));
assert!(range.contains(50));
assert!(!range.contains(-1));
assert!(!range.contains(101));
}
#[test]
fn test_range_is_subset() {
let small = ValueRange::bounded(10, 20);
let large = ValueRange::bounded(0, 100);
assert!(small.is_subset_of(&large));
assert!(!large.is_subset_of(&small));
}
#[test]
fn test_deep_sema_fix_interprocedural() {
let mut sema = make_sema();
let leaf = fix_make_function("leaf", int_ty(), vec![], Some(CompoundStmt::new()));
let mut caller_body = CompoundStmt::new();
caller_body.push(fix_return(Some(fix_call(fix_ident("leaf"), vec![]))));
let caller = fix_make_function("caller", int_ty(), vec![], Some(caller_body));
let tu = fix_make_tu(vec![Decl::Function(leaf), Decl::Function(caller)]);
let ok = sema.analyze_tu_deep(&tu);
assert!(ok);
}
#[test]
fn test_deep_sema_fix_call_graph_summary() {
let mut sema = make_sema();
let mut body = CompoundStmt::new();
body.push(fix_return(Some(fix_call(fix_ident("helper"), vec![]))));
let caller = fix_make_function("caller", int_ty(), vec![], Some(body));
let tu = fix_make_tu(vec![Decl::Function(caller)]);
sema.analyze_tu(&tu);
let summary = sema.call_graph_summary();
assert!(summary.contains("caller"));
assert!(summary.contains("helper"));
}
#[test]
fn test_deep_sema_fix_attribute_summary() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "noreturn_func".into(),
ret_ty: void_ty(),
params: vec![],
body: None,
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: true,
};
let tu = fix_make_tu(vec![Decl::Function(func)]);
sema.analyze_tu(&tu);
let summary = sema.attribute_summary();
assert!(summary.contains("noreturn"));
}
#[test]
fn test_deep_sema_fix_analysis_report() {
let mut sema = make_sema();
let tu = fix_make_tu(vec![]);
sema.analyze_tu_deep(&tu);
let report = sema.analysis_report();
assert!(report.contains("X86 Deep Semantic Analysis Report"));
assert!(report.contains("Target:"));
assert!(report.contains("Errors:"));
}
#[test]
fn test_deep_sema_fix_build_cfg() {
let mut sema = make_sema();
let mut body = CompoundStmt::new();
body.push(fix_return(Some(fix_int_lit(0))));
let func = fix_make_function("cfg_test", int_ty(), vec![], Some(body));
let tu = fix_make_tu(vec![Decl::Function(func)]);
sema.analyze_tu(&tu);
let cfg = sema.build_cfg("cfg_test");
assert!(cfg.is_some());
let cfg = cfg.unwrap();
assert_eq!(cfg.function_name, "cfg_test");
}
#[test]
fn test_deep_sema_fix_check_uninit_use() {
let mut sema = make_sema();
sema.value_analysis
.set_init_state("x", InitState::Uninitialized);
assert!(sema.check_uninit_use("x"));
sema.value_analysis
.set_init_state("y", InitState::Initialized);
assert!(!sema.check_uninit_use("y"));
}
#[test]
fn test_deep_sema_fix_default_is_64bit() {
let sema = X86DeepSemaFix::default();
assert!(sema.tu.target.is_64bit);
assert_eq!(sema.tu.target.data_model, DataModel::LP64);
}
#[test]
fn test_deep_sema_fix_with_extern_variable() {
let mut sema = make_sema();
let var = VarDecl {
name: "external_var".into(),
ty: int_ty(),
init: None,
linkage: Linkage::External,
is_global: true,
is_extern: true,
is_static: false,
};
let tu = fix_make_tu(vec![Decl::Variable(var)]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
}
#[test]
fn test_deep_sema_fix_recursion_detection() {
let mut sema = make_sema();
let mut body = CompoundStmt::new();
body.push(fix_return(Some(fix_call(
fix_ident("recurse"),
vec![fix_int_lit(1)],
))));
let func = fix_make_function("recurse", int_ty(), vec![int_ty()], Some(body));
let tu = fix_make_tu(vec![Decl::Function(func)]);
sema.analyze_tu(&tu);
let recursion = sema.function_checker.detect_recursion("recurse");
assert!(recursion.direct);
}
#[test]
fn test_odr_inline_multiple_definitions_ok() {
let mut sema = X86DeepSemaFix::new("c++17", "x86_64-unknown-linux-gnu");
sema.linkage_checker.register_symbol(SymbolDef {
name: "inline_func".into(),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Strong,
comdat_group: None,
file: Some("a.h".into()),
line: 1,
is_inline: true,
is_template: false,
});
sema.linkage_checker.register_symbol(SymbolDef {
name: "inline_func".into(),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Strong,
comdat_group: None,
file: Some("b.h".into()),
line: 1,
is_inline: true,
is_template: false,
});
sema.linkage_checker.analyze();
assert!(sema.linkage_checker.odr_violations.is_empty());
}
#[test]
fn test_analysis_with_two_structs() {
let mut sema = make_sema();
let s1 = StructDecl {
name: "A".into(),
fields: vec![FieldDecl::new("x", QualType::int())],
is_union: false,
};
let s2 = StructDecl {
name: "B".into(),
fields: vec![FieldDecl::new("y", QualType::float())],
is_union: false,
};
let tu = fix_make_tu(vec![Decl::Struct(s1), Decl::Struct(s2)]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
}
#[test]
fn test_type_checker_large_array() {
let tc = X86TypeChecker::new(DataModel::LP64);
let arr_ty = QualType {
base: TypeNode::Array {
elem: Box::new(TypeNode::Int),
size: 1024,
},
..QualType::int()
};
assert_eq!(tc.size_of(&arr_ty), 4096);
}
#[test]
fn test_type_checker_union_properties() {
let tc = X86TypeChecker::new(DataModel::LP64);
let union_ty = QualType {
base: TypeNode::Struct {
name: "U".into(),
fields: vec![
FieldDecl::new("a", QualType::int()),
FieldDecl::new("b", QualType::long()),
],
is_union: true,
},
..QualType::int()
};
assert_eq!(tc.size_of(&union_ty), 8);
}
#[test]
fn test_x86_fix_size_constants_lp64() {
let m = x86_fix_size_constants_lp64();
assert_eq!(m["char"], 8);
assert_eq!(m["short"], 16);
assert_eq!(m["int"], 32);
assert_eq!(m["long"], 64);
assert_eq!(m["pointer"], 64);
assert_eq!(m["sse"], 128);
assert_eq!(m["avx"], 256);
assert_eq!(m["avx512"], 512);
}
#[test]
fn test_x86_fix_size_constants_ilp32() {
let m = x86_fix_size_constants_ilp32();
assert_eq!(m["long"], 32);
assert_eq!(m["pointer"], 32);
}
#[test]
fn test_x86_fix_max_int_bits() {
assert_eq!(X86_FIX_MAX_INT_BITS, 128);
}
#[test]
fn test_x86_fix_calling_convention_display() {
assert_eq!(format!("{}", X86FixCallingConvention::CDecl), "cdecl");
assert_eq!(format!("{}", X86FixCallingConvention::SysV), "sysv_abi");
assert_eq!(format!("{}", X86FixCallingConvention::Win64), "ms_abi");
}
#[test]
fn test_x86_fix_low_level_type_bits() {
assert_eq!(X86FixLowLevelType::I8.bits(), 8);
assert_eq!(X86FixLowLevelType::I32.bits(), 32);
assert_eq!(X86FixLowLevelType::I64.bits(), 64);
assert_eq!(X86FixLowLevelType::F64.bits(), 64);
assert_eq!(X86FixLowLevelType::F80.bits(), 80);
}
#[test]
fn test_x86_fix_low_level_type_from_qual_type() {
assert_eq!(
X86FixLowLevelType::from_qual_type(&QualType::void()),
X86FixLowLevelType::Void
);
assert_eq!(
X86FixLowLevelType::from_qual_type(&QualType::char()),
X86FixLowLevelType::I8
);
assert_eq!(
X86FixLowLevelType::from_qual_type(&QualType::short()),
X86FixLowLevelType::I16
);
assert_eq!(
X86FixLowLevelType::from_qual_type(&QualType::int()),
X86FixLowLevelType::I32
);
assert_eq!(
X86FixLowLevelType::from_qual_type(&QualType::float()),
X86FixLowLevelType::F32
);
assert_eq!(
X86FixLowLevelType::from_qual_type(&QualType::double()),
X86FixLowLevelType::F64
);
}
#[test]
fn test_x86_fix_type_to_string_primitive() {
assert_eq!(x86_fix_type_to_string(&QualType::int()), "int");
assert_eq!(x86_fix_type_to_string(&QualType::void()), "void");
assert_eq!(x86_fix_type_to_string(&QualType::long()), "long");
}
#[test]
fn test_x86_fix_type_to_string_const() {
let mut ty = QualType::int();
ty.is_const = true;
assert_eq!(x86_fix_type_to_string(&ty), "const int");
}
#[test]
fn test_x86_fix_type_to_string_volatile() {
let mut ty = QualType::int();
ty.is_volatile = true;
assert_eq!(x86_fix_type_to_string(&ty), "volatile int");
}
#[test]
fn test_x86_fix_type_to_string_pointer() {
let ptr = QualType::pointer_to(QualType::int());
assert_eq!(x86_fix_type_to_string(&ptr), "int*");
}
#[test]
fn test_x86_fix_type_to_string_array() {
let arr = QualType {
base: TypeNode::Array {
elem: Box::new(TypeNode::Char),
size: 10,
},
..QualType::char()
};
assert_eq!(x86_fix_type_to_string(&arr), "char[10]");
}
#[test]
fn test_x86_abi_type_class_display() {
assert_eq!(format!("{}", X86AbiTypeClass::Integer), "INTEGER");
assert_eq!(format!("{}", X86AbiTypeClass::Sse), "SSE");
assert_eq!(format!("{}", X86AbiTypeClass::Memory), "MEMORY");
}
#[test]
fn regression_no_panic_empty_cfg_lookup() {
let fc = X86FunctionChecker::new();
let cfg = fc.build_cfg("empty", &[]);
assert!(!cfg.is_reachable(999));
}
#[test]
fn regression_no_panic_constant_fold_nested() {
let va = X86ValueAnalysis::new();
let expr = fix_binary(
BinaryOp::Add,
fix_binary(BinaryOp::Add, fix_int_lit(1), fix_int_lit(2)),
fix_binary(BinaryOp::Add, fix_int_lit(3), fix_int_lit(4)),
);
assert_eq!(va.constant_fold(&expr), Some(10));
}
#[test]
fn regression_no_panic_empty_linkage() {
let lc = X86LinkageChecker::default();
assert!(!lc.has_multiple_defs("nonexistent"));
assert!(!lc.is_exported("nonexistent"));
assert!(lc.effective_linkage("nonexistent").is_none());
}
#[test]
fn regression_no_panic_empty_attributes() {
let checker = X86AttributeChecker::new();
let attrs = checker.get_function_attributes("nonexistent");
assert!(attrs.is_empty());
assert!(!checker.is_noreturn("nonexistent"));
}
#[test]
fn regression_no_panic_range_intersection_edge() {
let r1 = ValueRange::bounded(i64::MIN, 0);
let r2 = ValueRange::bounded(0, i64::MAX);
let i = r1.intersection(&r2);
assert!(i.is_some());
assert_eq!(i.unwrap().min, 0);
assert_eq!(i.unwrap().max, 0);
}
#[test]
fn regression_no_panic_div_by_zero_range() {
let va = X86ValueAnalysis::new();
let r1 = ValueRange::bounded(1, 10);
let r2 = ValueRange::bounded(0, 0);
let result = va.range_of_binary(&BinaryOp::Div, &r1, &r2);
assert_eq!(result, ValueRange::unknown());
}
#[test]
fn prop_all_attribute_names_have_description() {
for name in X86AttributeChecker::all_attribute_names() {
let desc = name.description();
assert!(!desc.is_empty(), "No description for {:?}", name);
}
}
#[test]
fn prop_all_attribute_names_have_source() {
for name in X86AttributeChecker::all_attribute_names() {
let _source = name.source();
}
}
#[test]
fn prop_constant_range_is_precise() {
let range = ValueRange::constant(42);
assert!(range.is_constant);
assert_eq!(range.min, range.max);
}
#[test]
fn prop_compatible_is_reflexive() {
let tc = X86TypeChecker::default();
let types = vec![
QualType::int(),
QualType::char(),
QualType::short(),
QualType::long(),
QualType::float(),
QualType::double(),
QualType::void(),
];
for ty in &types {
assert!(tc.are_compatible(ty, ty), "Type {} not reflexive", ty);
}
}
#[test]
fn prop_size_of_always_non_negative() {
let tc_lp64 = X86TypeChecker::new(DataModel::LP64);
let tc_ilp32 = X86TypeChecker::new(DataModel::ILP32);
for tc in &[tc_lp64, tc_ilp32] {
assert!(tc.size_of(&QualType::int()) > 0);
assert!(tc.size_of(&QualType::void()) == 0);
assert!(tc.size_of(&QualType::char()) > 0);
}
}
#[test]
fn prop_range_union_contains_both() {
let r1 = ValueRange::bounded(0, 10);
let r2 = ValueRange::bounded(20, 30);
let u = r1.union(&r2);
assert!(u.contains(5));
assert!(u.contains(25));
assert!(!u.contains(15));
}
#[test]
fn prop_detect_recursion_self_loop() {
let mut fc = X86FunctionChecker::new();
fc.register_function("self", &["self".to_string()]);
let info = fc.detect_recursion("self");
assert!(info.direct);
}
#[test]
fn test_fix_sema_interop_with_base_sema() {
let mut sema = X86DeepSemaFix::new_x86_64();
let tu = TranslationUnit::new("test.c");
sema.sema.analyze(&tu);
let ok = sema.analyze_tu(&tu);
assert!(ok);
}
#[test]
fn test_fix_sema_handles_c_standard() {
let sema = X86DeepSemaFix::new("c17", "x86_64-unknown-linux-gnu");
assert!(!sema.linkage_checker.is_cxx);
}
#[test]
fn test_fix_sema_handles_cxx_standard() {
let sema = X86DeepSemaFix::new("c++17", "x86_64-unknown-linux-gnu");
assert!(sema.linkage_checker.is_cxx);
}
#[test]
fn burnin_many_comdat_groups() {
let mut lc = X86LinkageChecker::new(false, "x86_64-unknown-linux-gnu");
for i in 0..50 {
lc.register_comdat(ComdatGroup {
name: format!("grp_{}", i),
selection_kind: ComdatSelection::Any,
members: vec![format!("sym_{}", i)],
});
lc.register_symbol(SymbolDef {
name: format!("sym_{}", i),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Strong,
comdat_group: Some(format!("grp_{}", i)),
file: Some(format!("f{}.c", i)),
line: i,
is_inline: true,
is_template: false,
});
}
lc.analyze();
assert_eq!(lc.stats().comdat_groups, 50);
}
#[test]
fn burnin_nested_constant_fold() {
let va = X86ValueAnalysis::new();
let mut expr = fix_int_lit(5);
for i in (1..=4).rev() {
expr = fix_binary(BinaryOp::Add, fix_int_lit(i), expr);
}
assert_eq!(va.constant_fold(&expr), Some(15));
}
#[test]
fn burnin_call_graph_linear_chain() {
let mut fc = X86FunctionChecker::new();
for i in 0..100 {
let next = if i < 99 {
vec![format!("f{}", i + 1)]
} else {
vec![]
};
fc.register_function(&format!("f{}", i), &next);
}
assert_eq!(fc.call_depth("f0"), 99);
}
#[test]
fn burnin_all_binary_ops_constant_fold() {
let va = X86ValueAnalysis::new();
let tests: Vec<(BinaryOp, i64, i64, Option<i64>)> = vec![
(BinaryOp::Add, 100, 50, Some(150)),
(BinaryOp::Sub, 100, 50, Some(50)),
(BinaryOp::Mul, 12, 12, Some(144)),
(BinaryOp::Div, 100, 4, Some(25)),
(BinaryOp::Mod, 100, 7, Some(2)),
(BinaryOp::And, 0xFF00, 0x0FF0, Some(0x0F00)),
(BinaryOp::Or, 0xF000, 0x0F00, Some(0xFF00)),
(BinaryOp::Xor, 0xFFFF, 0x00FF, Some(0xFF00)),
(BinaryOp::Shl, 1, 10, Some(1024)),
(BinaryOp::Shr, 1024, 10, Some(1)),
(BinaryOp::Eq, 5, 5, Some(1)),
(BinaryOp::Ne, 5, 3, Some(1)),
(BinaryOp::Lt, 3, 5, Some(1)),
(BinaryOp::Gt, 5, 3, Some(1)),
(BinaryOp::Le, 5, 5, Some(1)),
(BinaryOp::Ge, 5, 5, Some(1)),
(BinaryOp::LogicAnd, 1, 1, Some(1)),
(BinaryOp::LogicOr, 0, 1, Some(1)),
];
for (op, a, b, expected) in &tests {
let expr = fix_binary(op.clone(), fix_int_lit(*a), fix_int_lit(*b));
assert_eq!(va.constant_fold(&expr), *expected, "Failed for {:?}", op);
}
}
#[test]
fn burnin_many_attributes_on_single_function() {
let mut checker = X86AttributeChecker::new();
let attrs = vec![
AttributeName::NoReturn,
AttributeName::Cold,
AttributeName::NoInline,
AttributeName::NoThrow,
];
for &attr in &attrs {
checker.add_attribute(
"f",
AttrApplyTarget::Function,
AttributeInstance::new(attr, AttrApplyTarget::Function, 1),
);
}
checker.validate_all();
for &attr in &attrs {
assert!(checker.has_attribute("f", attr));
}
}
#[test]
fn burnin_fix_tu_exhaustive() {
let mut fix = X86TranslationUnitFix::new(TranslationUnit::new("main.c"));
for i in 0..20 {
fix.add_system_include(IncludeRecord::system(&format!("sys_{}.h", i), i));
fix.add_user_include(IncludeRecord::user(&format!("usr_{}.h", i), i));
fix.add_define(DefineRecord::new(
&format!("D{}", i),
Some(&format!("{}", i * 2)),
i,
));
fix.add_flag(&format!("-O{}", i % 3));
fix.add_warning(&format!("-Wwarning-{}", i));
}
assert_eq!(fix.system_includes.len(), 20);
assert_eq!(fix.user_includes.len(), 20);
assert_eq!(fix.defines.len(), 20);
assert_eq!(fix.compiler_flags.len(), 20);
assert_eq!(fix.warning_flags.len(), 20);
assert_eq!(fix.total_include_count(), 40);
}
#[test]
fn doc_example_basic_fix_analysis() {
let mut sema = X86DeepSemaFix::new_x86_64();
let mut body = CompoundStmt::new();
body.push(fix_return(Some(fix_int_lit(0))));
let main_fn = fix_make_function("main", int_ty(), vec![], Some(body));
let tu = fix_make_tu(vec![Decl::Function(main_fn)]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
}
#[test]
fn doc_example_type_checking() {
let tc = X86TypeChecker::new(DataModel::LP64);
let result = tc.check_compatibility(&QualType::int(), &QualType::long());
assert!(!result.compatible);
assert_eq!(tc.size_of(&QualType::int()), 4);
assert_eq!(tc.size_of(&QualType::long()), 8);
}
#[test]
fn doc_example_attribute_validation() {
let mut checker = X86AttributeChecker::new();
checker.add_attribute(
"my_func",
AttrApplyTarget::Function,
AttributeInstance::new(AttributeName::NoReturn, AttrApplyTarget::Function, 10),
);
checker.validate_all();
assert!(checker.is_noreturn("my_func"));
assert!(checker.errors.is_empty());
}
#[test]
fn doc_example_linkage_checking() {
let mut lc = X86LinkageChecker::new(true, "x86_64-unknown-linux-gnu");
lc.register_symbol(SymbolDef {
name: "unique_func".into(),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Strong,
comdat_group: None,
file: Some("a.c".into()),
line: 1,
is_inline: false,
is_template: false,
});
lc.analyze();
assert!(lc.odr_violations.is_empty());
assert!(lc.is_exported("unique_func"));
}
#[test]
fn doc_example_value_range_analysis() {
let mut va = X86ValueAnalysis::new();
va.set_range("i", ValueRange::bounded(0, 100));
let range = va.get_range("i");
assert!(range.contains(50));
assert!(!range.contains(101));
}
#[test]
fn doc_example_control_flow() {
let mut fc = X86FunctionChecker::new();
let stmts = vec![Stmt::If {
cond: Box::new(fix_int_lit(1)),
then: Box::new(fix_return(Some(fix_int_lit(0)))),
els: Some(Box::new(fix_return(Some(fix_int_lit(1))))),
}];
let cf = fc.analyze_control_flow("branched", &stmts, &int_ty());
assert!(cf.all_paths_return);
}
#[test]
fn edge_void_parameter_sole() {
let mut sema = make_sema();
let func = fix_make_function("f", void_ty(), vec![void_ty()], None);
let tu = fix_make_tu(vec![Decl::Function(func)]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
}
#[test]
fn edge_no_parameters() {
let mut sema = make_sema();
let func = fix_make_function("f", int_ty(), vec![], Some(CompoundStmt::new()));
let tu = fix_make_tu(vec![Decl::Function(func)]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
}
#[test]
fn edge_multiple_params() {
let mut sema = make_sema();
let func = fix_make_function(
"add",
int_ty(),
vec![int_ty(), int_ty(), int_ty()],
Some(CompoundStmt::new()),
);
let tu = fix_make_tu(vec![Decl::Function(func)]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
}
#[test]
fn edge_max_include_nesting() {
let mut root = IncludeRecord::system("root.h", 0);
let mut current = IncludeRecord::system("level_1.h", 1);
for i in 2..=10 {
let next = IncludeRecord::system(&format!("level_{}.h", i), i as u32);
current.add_nested(next);
if i == 10 {
break;
}
}
root.add_nested(current);
assert!(root.total_count() >= 2);
}
#[test]
fn edge_very_large_value_range() {
let range = ValueRange::bounded(i64::MIN, i64::MAX);
assert!(!range.is_constant);
assert!(range.contains(0));
assert!(range.contains(-1));
assert!(range.contains(i64::MAX));
}
#[test]
fn edge_void_returning_noreturn() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "abort".into(),
ret_ty: void_ty(),
params: vec![],
body: None,
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: true,
};
let tu = fix_make_tu(vec![Decl::Function(func)]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
}
#[test]
fn edge_double_pointer() {
let tc = X86TypeChecker::default();
let ptr_to_ptr = QualType::pointer_to(QualType::pointer_to(QualType::int()));
assert_eq!(tc.size_of(&ptr_to_ptr), 8); assert!(tc.are_compatible(&ptr_to_ptr, &ptr_to_ptr));
}
#[test]
fn edge_conversion_rank_char_to_long_long() {
let tc = X86TypeChecker::default();
let (ok, _) = tc.can_implicitly_convert(&QualType::char(), &QualType::long_long());
assert!(ok);
}
#[test]
fn edge_common_symbol_tracking() {
let mut lc = X86LinkageChecker::new(false, "x86_64-unknown-linux-gnu");
lc.register_symbol(SymbolDef {
name: "common_var".into(),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Common,
comdat_group: None,
file: Some("a.c".into()),
line: 1,
is_inline: false,
is_template: false,
});
let stats = lc.stats();
assert_eq!(stats.common_syms, 1);
}
#[test]
fn edge_weak_reference() {
let mut lc = X86LinkageChecker::new(false, "x86_64-unknown-linux-gnu");
lc.register_symbol(SymbolDef {
name: "weakref_sym".into(),
is_definition: false,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::WeakRef,
comdat_group: None,
file: Some("a.c".into()),
line: 1,
is_inline: false,
is_template: false,
});
let stats = lc.stats();
assert_eq!(stats.weak_refs, 1);
}
#[test]
fn edge_comdat_exactmatch_multiple_defs() {
let mut lc = X86LinkageChecker::new(false, "x86_64-unknown-linux-gnu");
lc.register_comdat(ComdatGroup {
name: "exact_grp".into(),
selection_kind: ComdatSelection::ExactMatch,
members: vec!["exact_sym".into()],
});
lc.register_symbol(SymbolDef {
name: "exact_sym".into(),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Strong,
comdat_group: Some("exact_grp".into()),
file: Some("a.c".into()),
line: 1,
is_inline: true,
is_template: false,
});
lc.register_symbol(SymbolDef {
name: "exact_sym".into(),
is_definition: true,
linkage: Linkage::External,
visibility: SymbolVisibility::Default,
strength: SymbolStrength::Strong,
comdat_group: Some("exact_grp".into()),
file: Some("b.c".into()),
line: 2,
is_inline: true,
is_template: false,
});
lc.analyze();
assert!(lc.warnings.iter().any(|w| w.contains("exactmatch")));
}
#[test]
fn edge_llp64_windows_target() {
let fix = X86TranslationUnitFix::new(TranslationUnit::new("test.c")).with_target(
TargetTripleInfo {
triple: "x86_64-pc-windows-msvc".into(),
arch: "x86_64".into(),
vendor: "pc".into(),
os: "windows".into(),
environment: "msvc".into(),
is_64bit: true,
data_model: DataModel::LLP64,
},
);
assert!(fix.target.is_64bit);
assert_eq!(fix.target.data_model, DataModel::LLP64);
}
#[test]
fn edge_dominator_single_block() {
let fc = X86FunctionChecker::new();
let cfg = fc.build_cfg("single", &[fix_return(Some(fix_int_lit(0)))]);
let doms = cfg.dominators(cfg.entry);
assert!(doms.contains(&cfg.entry));
}
#[test]
fn test_include_kind_display() {
assert_eq!(format!("{}", IncludeKind::System), "system");
assert_eq!(format!("{}", IncludeKind::User), "user");
assert_eq!(format!("{}", IncludeKind::Framework), "framework");
}
#[test]
fn test_data_model_display() {
assert_eq!(format!("{}", DataModel::LP64), "LP64");
assert_eq!(format!("{}", DataModel::ILP32), "ILP32");
assert_eq!(format!("{}", DataModel::LLP64), "LLP64");
}
#[test]
fn test_source_language_display() {
assert_eq!(format!("{}", SourceLanguage::C), "C");
assert_eq!(format!("{}", SourceLanguage::Cxx), "C++");
}
#[test]
fn test_side_effect_class_display() {
assert_eq!(format!("{}", SideEffectClass::Pure), "pure");
assert_eq!(format!("{}", SideEffectClass::HasEffects), "has_effects");
}
#[test]
fn test_symbol_visibility_display() {
assert_eq!(format!("{}", SymbolVisibility::Hidden), "hidden");
assert_eq!(format!("{}", SymbolVisibility::Default), "default");
}
#[test]
fn test_symbol_strength_display() {
assert_eq!(format!("{}", SymbolStrength::Strong), "strong");
assert_eq!(format!("{}", SymbolStrength::Weak), "weak");
}
#[test]
fn test_comdat_selection_display() {
assert_eq!(format!("{}", ComdatSelection::Any), "any");
assert_eq!(format!("{}", ComdatSelection::ExactMatch), "exactmatch");
}
#[test]
fn test_attribute_source_display() {
assert_eq!(format!("{}", AttributeSource::Standard), "standard");
assert_eq!(format!("{}", AttributeSource::Gnu), "GNU");
assert_eq!(format!("{}", AttributeSource::Msvc), "MSVC");
}
#[test]
fn test_value_range_display() {
assert_eq!(format!("{}", ValueRange::constant(5)), "5");
assert_eq!(format!("{}", ValueRange::unknown()), "<unknown>");
assert_eq!(format!("{}", ValueRange::bounded(0, 10)), "[0, 10]");
}
}