use std::collections::{HashMap, HashSet};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum UBSanCheckKind {
SignedIntegerOverflow,
UnsignedIntegerOverflow,
IntegerDivideByZero,
FloatDivideByZero,
ImplicitSignedIntegerTruncation,
ImplicitIntegerSignChange,
ImplicitUnsignedIntegerTruncation,
ShiftBase,
ShiftExponent,
ArrayBounds,
LocalBounds,
ObjectSize,
Alignment,
Null,
ReturnsNonNullAttribute,
NonnullAttribute,
VLABound,
FloatCastOverflow,
Enum,
Bool,
Builtin,
Return,
Unreachable,
Function,
Vptr,
PointerOverflow,
}
impl fmt::Display for UBSanCheckKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
UBSanCheckKind::SignedIntegerOverflow => "signed-integer-overflow",
UBSanCheckKind::UnsignedIntegerOverflow => "unsigned-integer-overflow",
UBSanCheckKind::IntegerDivideByZero => "integer-divide-by-zero",
UBSanCheckKind::FloatDivideByZero => "float-divide-by-zero",
UBSanCheckKind::ImplicitSignedIntegerTruncation => "implicit-signed-integer-truncation",
UBSanCheckKind::ImplicitIntegerSignChange => "implicit-integer-sign-change",
UBSanCheckKind::ImplicitUnsignedIntegerTruncation => "implicit-unsigned-integer-truncation",
UBSanCheckKind::ShiftBase => "shift-base",
UBSanCheckKind::ShiftExponent => "shift-exponent",
UBSanCheckKind::ArrayBounds => "array-bounds",
UBSanCheckKind::LocalBounds => "local-bounds",
UBSanCheckKind::ObjectSize => "object-size",
UBSanCheckKind::Alignment => "alignment",
UBSanCheckKind::Null => "null",
UBSanCheckKind::ReturnsNonNullAttribute => "returns-nonnull-attribute",
UBSanCheckKind::NonnullAttribute => "nonnull-attribute",
UBSanCheckKind::VLABound => "vla-bound",
UBSanCheckKind::FloatCastOverflow => "float-cast-overflow",
UBSanCheckKind::Enum => "enum",
UBSanCheckKind::Bool => "bool",
UBSanCheckKind::Builtin => "builtin",
UBSanCheckKind::Return => "return",
UBSanCheckKind::Unreachable => "unreachable",
UBSanCheckKind::Function => "function",
UBSanCheckKind::Vptr => "vptr",
UBSanCheckKind::PointerOverflow => "pointer-overflow",
};
write!(f, "{}", s)
}
}
#[derive(Debug, Clone)]
pub struct UBSanConfig {
pub enabled: HashSet<UBSanCheckKind>,
pub verbose: bool,
pub halt_on_error: bool,
pub print_stacktrace: bool,
pub minimal_runtime: bool,
pub report_error_type: bool,
pub recovery_handler: Option<String>,
pub blacklist: HashSet<String>,
pub suppression_map: HashMap<String, HashSet<UBSanCheckKind>>,
}
impl Default for UBSanConfig {
fn default() -> Self {
UBSanConfig {
enabled: HashSet::new(),
verbose: true,
halt_on_error: false,
print_stacktrace: true,
minimal_runtime: false,
report_error_type: true,
recovery_handler: None,
blacklist: HashSet::new(),
suppression_map: HashMap::new(),
}
}
}
impl UBSanConfig {
pub fn all() -> Self {
let mut config = UBSanConfig::default();
let all_checks = [
UBSanCheckKind::SignedIntegerOverflow,
UBSanCheckKind::IntegerDivideByZero,
UBSanCheckKind::ImplicitSignedIntegerTruncation,
UBSanCheckKind::ImplicitIntegerSignChange,
UBSanCheckKind::ShiftBase,
UBSanCheckKind::ShiftExponent,
UBSanCheckKind::ArrayBounds,
UBSanCheckKind::Alignment,
UBSanCheckKind::Null,
UBSanCheckKind::VLABound,
UBSanCheckKind::FloatCastOverflow,
UBSanCheckKind::Enum,
UBSanCheckKind::Bool,
UBSanCheckKind::Builtin,
UBSanCheckKind::Return,
UBSanCheckKind::Unreachable,
UBSanCheckKind::Function,
];
for check in &all_checks {
config.enabled.insert(*check);
}
config
}
pub fn is_enabled(&self, check: UBSanCheckKind) -> bool {
self.enabled.contains(&check)
}
pub fn enable_by_name(&mut self, name: &str) -> bool {
let check = match name {
"signed-integer-overflow" => UBSanCheckKind::SignedIntegerOverflow,
"unsigned-integer-overflow" => UBSanCheckKind::UnsignedIntegerOverflow,
"integer-divide-by-zero" => UBSanCheckKind::IntegerDivideByZero,
"float-divide-by-zero" => UBSanCheckKind::FloatDivideByZero,
"implicit-signed-integer-truncation" => UBSanCheckKind::ImplicitSignedIntegerTruncation,
"implicit-integer-sign-change" => UBSanCheckKind::ImplicitIntegerSignChange,
"shift-base" => UBSanCheckKind::ShiftBase,
"shift-exponent" => UBSanCheckKind::ShiftExponent,
"array-bounds" => UBSanCheckKind::ArrayBounds,
"local-bounds" => UBSanCheckKind::LocalBounds,
"object-size" => UBSanCheckKind::ObjectSize,
"alignment" => UBSanCheckKind::Alignment,
"null" => UBSanCheckKind::Null,
"returns-nonnull-attribute" => UBSanCheckKind::ReturnsNonNullAttribute,
"vla-bound" => UBSanCheckKind::VLABound,
"float-cast-overflow" => UBSanCheckKind::FloatCastOverflow,
"enum" => UBSanCheckKind::Enum,
"bool" => UBSanCheckKind::Bool,
"builtin" => UBSanCheckKind::Builtin,
"return" => UBSanCheckKind::Return,
"unreachable" => UBSanCheckKind::Unreachable,
"function" => UBSanCheckKind::Function,
"vptr" => UBSanCheckKind::Vptr,
"pointer-overflow" => UBSanCheckKind::PointerOverflow,
_ => return false,
};
self.enabled.insert(check);
true
}
}
#[derive(Debug, Clone)]
pub struct SourceLocation {
pub filename: String,
pub line: u32,
pub column: u32,
pub function_name: Option<String>,
}
impl SourceLocation {
pub fn new(filename: &str, line: u32, column: u32) -> Self {
SourceLocation {
filename: filename.to_string(),
line,
column,
function_name: None,
}
}
pub fn with_function(mut self, func: &str) -> Self {
self.function_name = Some(func.to_string());
self
}
pub fn format(&self) -> String {
match &self.function_name {
Some(f) => format!("{}:{}:{} in function '{}'", self.filename, self.line, self.column, f),
None => format!("{}:{}:{}", self.filename, self.line, self.column),
}
}
pub fn format_runtime(&self) -> String {
format!("{}:{}:{}: runtime error: ", self.filename, self.line, self.column)
}
}
impl fmt::Display for SourceLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.format())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TypeKind {
Integer { bit_width: u32, signed: bool },
Float { bit_width: u32 },
Pointer,
Enumeration,
Bool,
Void,
Unknown,
}
#[derive(Debug, Clone)]
pub struct TypeDescriptor {
pub type_kind: u16,
pub type_info: u16,
pub type_name: String,
}
impl TypeDescriptor {
pub fn new(kind: TypeKind, name: &str) -> Self {
let (type_kind, type_info) = match kind {
TypeKind::Integer { bit_width, signed } => {
let tk = if signed { 0x0000 } else { 0x0001 };
(tk, bit_width as u16)
}
TypeKind::Float { bit_width } => (0x0002, bit_width as u16),
TypeKind::Pointer => (0x0003, 0),
TypeKind::Enumeration => (0x0004, 0),
TypeKind::Bool => (0x0005, 0),
TypeKind::Void => (0x0006, 0),
TypeKind::Unknown => (0xFFFF, 0),
};
TypeDescriptor {
type_kind,
type_info,
type_name: name.to_string(),
}
}
pub fn mangled_name(&self) -> String {
format!("__ubsan_type_{}", self.type_name.replace(' ', "_"))
}
pub fn emit_llvm_global(&self) -> String {
format!(
"@__ubsan_type_{} = private constant {{ i16 {}, i16 {}, [{} x i8] c\"{}\" }}, align 2",
self.type_name.replace(' ', "_"),
self.type_kind,
self.type_info,
self.type_name.len(),
self.type_name
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UBSanErrorType {
IntegerOverflow,
DivisionByZero,
ShiftOutOfBounds,
OutOfBounds,
MisalignedPointer,
NullPointerUse,
TypeMismatch,
VLABoundNotPositive,
FloatCastOverflow,
InvalidBool,
InvalidEnum,
InvalidBuiltin,
MissingReturn,
UnreachableReached,
FunctionTypeMismatch,
NonNullReturn,
PointerOverflow,
}
impl fmt::Display for UBSanErrorType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
UBSanErrorType::IntegerOverflow => "signed integer overflow",
UBSanErrorType::DivisionByZero => "division by zero",
UBSanErrorType::ShiftOutOfBounds => "shift out of bounds",
UBSanErrorType::OutOfBounds => "out of bounds access",
UBSanErrorType::MisalignedPointer => "misaligned pointer",
UBSanErrorType::NullPointerUse => "null pointer use",
UBSanErrorType::TypeMismatch => "type mismatch",
UBSanErrorType::VLABoundNotPositive => "VLA bound is not positive",
UBSanErrorType::FloatCastOverflow => "float cast overflow",
UBSanErrorType::InvalidBool => "invalid bool value",
UBSanErrorType::InvalidEnum => "invalid enum value",
UBSanErrorType::InvalidBuiltin => "invalid builtin call",
UBSanErrorType::MissingReturn => "missing return value",
UBSanErrorType::UnreachableReached => "unreachable code reached",
UBSanErrorType::FunctionTypeMismatch => "function type mismatch",
UBSanErrorType::NonNullReturn => "non-null return was null",
UBSanErrorType::PointerOverflow => "pointer overflow",
};
write!(f, "{}", s)
}
}
#[derive(Debug, Clone)]
pub struct UBSanInstrumentationPoint {
pub check_kind: UBSanCheckKind,
pub error_type: UBSanErrorType,
pub location: SourceLocation,
pub condition: String,
pub operands: Vec<String>,
pub type_descriptor: Option<TypeDescriptor>,
pub recovery_enabled: bool,
}
impl UBSanInstrumentationPoint {
pub fn new(
check_kind: UBSanCheckKind,
error_type: UBSanErrorType,
location: SourceLocation,
condition: &str,
) -> Self {
UBSanInstrumentationPoint {
check_kind,
error_type,
location,
condition: condition.to_string(),
operands: Vec::new(),
type_descriptor: None,
recovery_enabled: false,
}
}
}
#[derive(Debug, Clone)]
pub struct IntegerOverflowInstrumenter {
pub config: UBSanConfig,
}
impl IntegerOverflowInstrumenter {
pub fn new(config: UBSanConfig) -> Self {
IntegerOverflowInstrumenter { config }
}
pub fn check_signed_add_overflow(&self, a: i64, b: i64) -> Option<UBSanInstrumentationPoint> {
if !self.config.is_enabled(UBSanCheckKind::SignedIntegerOverflow) {
return None;
}
let overflows = (a > 0 && b > 0 && a.wrapping_add(b) < 0)
|| (a < 0 && b < 0 && a.wrapping_add(b) >= 0);
if overflows {
Some(UBSanInstrumentationPoint::new(
UBSanCheckKind::SignedIntegerOverflow,
UBSanErrorType::IntegerOverflow,
SourceLocation::new("<unknown>", 0, 0),
&format!("{} + {}", a, b),
))
} else {
None
}
}
pub fn check_signed_sub_overflow(&self, a: i64, b: i64) -> Option<UBSanInstrumentationPoint> {
if !self.config.is_enabled(UBSanCheckKind::SignedIntegerOverflow) {
return None;
}
let overflows = (a < 0 && b > 0 && a.wrapping_sub(b) > 0)
|| (a > 0 && b < 0 && a.wrapping_sub(b) < 0);
if overflows {
Some(UBSanInstrumentationPoint::new(
UBSanCheckKind::SignedIntegerOverflow,
UBSanErrorType::IntegerOverflow,
SourceLocation::new("<unknown>", 0, 0),
&format!("{} - {}", a, b),
))
} else {
None
}
}
pub fn check_signed_mul_overflow(&self, a: i64, b: i64) -> Option<UBSanInstrumentationPoint> {
if !self.config.is_enabled(UBSanCheckKind::SignedIntegerOverflow) {
return None;
}
let overflows = a != 0 && a.wrapping_mul(b).wrapping_div(a) != b;
if overflows {
Some(UBSanInstrumentationPoint::new(
UBSanCheckKind::SignedIntegerOverflow,
UBSanErrorType::IntegerOverflow,
SourceLocation::new("<unknown>", 0, 0),
&format!("{} * {}", a, b),
))
} else {
None
}
}
pub fn check_signed_neg_overflow(&self, a: i64) -> Option<UBSanInstrumentationPoint> {
if !self.config.is_enabled(UBSanCheckKind::SignedIntegerOverflow) {
return None;
}
if a == i64::MIN {
Some(UBSanInstrumentationPoint::new(
UBSanCheckKind::SignedIntegerOverflow,
UBSanErrorType::IntegerOverflow,
SourceLocation::new("<unknown>", 0, 0),
&format!("-({})", a),
))
} else {
None
}
}
pub fn emit_llvm_sadd_overflow_check(
&self,
loc: &SourceLocation,
a_reg: &str,
b_reg: &str,
) -> Vec<String> {
let mut ops = Vec::new();
ops.push(format!(
" ; UBSan: signed-integer-overflow check for add {} + {}",
a_reg, b_reg
));
ops.push(format!(
" %sadd_ov = call {{ i64, i1 }} @llvm.sadd.with.overflow.i64(i64 {}, i64 {})",
a_reg, b_reg
));
ops.push(" %sadd_res = extractvalue { i64, i1 } %sadd_ov, 0".to_string());
ops.push(" %sadd_flag = extractvalue { i64, i1 } %sadd_ov, 1".to_string());
ops.push(" br i1 %sadd_flag, label %ubsan_overflow_handler, label %sadd_ok".to_string());
ops.push(format!(
" ; Location: {}",
loc.format()
));
ops
}
pub fn emit_llvm_smul_overflow_check(
&self,
loc: &SourceLocation,
a_reg: &str,
b_reg: &str,
) -> Vec<String> {
vec![
format!(" ; UBSan: signed-integer-overflow check for mul {} * {}", a_reg, b_reg),
format!(
" %smul_ov = call {{ i64, i1 }} @llvm.smul.with.overflow.i64(i64 {}, i64 {})",
a_reg, b_reg
),
" %smul_flag = extractvalue { i64, i1 } %smul_ov, 1".to_string(),
format!(" br i1 %smul_flag, label %ubsan_handler_{}, label %smul_ok", loc.line),
]
}
}
#[derive(Debug, Clone)]
pub struct DivisionByZeroInstrumenter {
pub config: UBSanConfig,
}
impl DivisionByZeroInstrumenter {
pub fn new(config: UBSanConfig) -> Self {
DivisionByZeroInstrumenter { config }
}
pub fn check_integer_div_zero(&self, divisor: i64, loc: &SourceLocation) -> Option<UBSanInstrumentationPoint> {
if !self.config.is_enabled(UBSanCheckKind::IntegerDivideByZero) {
return None;
}
if divisor == 0 {
Some(UBSanInstrumentationPoint::new(
UBSanCheckKind::IntegerDivideByZero,
UBSanErrorType::DivisionByZero,
loc.clone(),
"divisor == 0",
))
} else {
None
}
}
pub fn check_signed_div_overflow(&self, dividend: i64, divisor: i64, loc: &SourceLocation) -> Option<UBSanInstrumentationPoint> {
if !self.config.is_enabled(UBSanCheckKind::IntegerDivideByZero) {
return None;
}
if divisor == -1 && dividend == i64::MIN {
Some(UBSanInstrumentationPoint::new(
UBSanCheckKind::IntegerDivideByZero,
UBSanErrorType::IntegerOverflow,
loc.clone(),
"INT_MIN / -1",
))
} else {
None
}
}
pub fn emit_llvm_div_zero_check(
&self,
loc: &SourceLocation,
divisor_reg: &str,
bit_width: u32,
) -> Vec<String> {
vec![
format!(" ; UBSan: integer-divide-by-zero check ({}-bit)", bit_width),
format!(" %div_zero = icmp eq i{} {}, 0", bit_width, divisor_reg),
format!(
" br i1 %div_zero, label %ubsan_div_zero_{}, label %div_ok_{}",
loc.line, loc.line
),
]
}
pub fn emit_llvm_float_div_zero_check(
&self,
loc: &SourceLocation,
divisor_reg: &str,
) -> Vec<String> {
vec![
" ; UBSan: float-divide-by-zero check".to_string(),
format!(" %fdiv_zero = fcmp oeq double {}, 0.0", divisor_reg),
format!(
" br i1 %fdiv_zero, label %ubsan_fdiv_zero_{}, label %fdiv_ok_{}",
loc.line, loc.line
),
]
}
}
#[derive(Debug, Clone)]
pub struct ShiftBoundsInstrumenter {
pub config: UBSanConfig,
}
impl ShiftBoundsInstrumenter {
pub fn new(config: UBSanConfig) -> Self {
ShiftBoundsInstrumenter { config }
}
pub fn check_shift_base(&self, shift_amount: u32, bit_width: u32, loc: &SourceLocation) -> Option<UBSanInstrumentationPoint> {
if !self.config.is_enabled(UBSanCheckKind::ShiftBase) {
return None;
}
if shift_amount >= bit_width {
Some(UBSanInstrumentationPoint::new(
UBSanCheckKind::ShiftBase,
UBSanErrorType::ShiftOutOfBounds,
loc.clone(),
&format!("shift amount {} >= bit width {}", shift_amount, bit_width),
))
} else {
None
}
}
pub fn check_shift_exponent(&self, shift_amount: u32, bit_width: u32, signed: bool, loc: &SourceLocation) -> Option<UBSanInstrumentationPoint> {
if !self.config.is_enabled(UBSanCheckKind::ShiftExponent) {
return None;
}
if shift_amount >= bit_width {
Some(UBSanInstrumentationPoint::new(
UBSanCheckKind::ShiftExponent,
UBSanErrorType::ShiftOutOfBounds,
loc.clone(),
&format!("shift exponent {} >= bit width {}", shift_amount, bit_width),
))
} else {
None
}
}
pub fn emit_llvm_shift_check(
&self,
loc: &SourceLocation,
shift_reg: &str,
bit_width: u32,
) -> Vec<String> {
vec![
format!(" ; UBSan: shift-base check (max {} bits)", bit_width),
format!(" %shift_ok = icmp ult i{} {}, {}", bit_width, shift_reg, bit_width),
format!(" br i1 %shift_ok, label %shift_ok_{}, label %ubsan_shift_{}",
loc.line, loc.line),
]
}
}
#[derive(Debug, Clone)]
pub struct ArrayBoundsInstrumenter {
pub config: UBSanConfig,
}
impl ArrayBoundsInstrumenter {
pub fn new(config: UBSanConfig) -> Self {
ArrayBoundsInstrumenter { config }
}
pub fn check_array_bounds(&self, index: i64, array_size: usize, loc: &SourceLocation) -> Option<UBSanInstrumentationPoint> {
if !self.config.is_enabled(UBSanCheckKind::ArrayBounds) {
return None;
}
if index < 0 || index as usize >= array_size {
Some(UBSanInstrumentationPoint::new(
UBSanCheckKind::ArrayBounds,
UBSanErrorType::OutOfBounds,
loc.clone(),
&format!("index {} out of bounds for array of size {}", index, array_size),
))
} else {
None
}
}
pub fn emit_llvm_array_bounds_check(
&self,
loc: &SourceLocation,
index_reg: &str,
size_reg: &str,
) -> Vec<String> {
vec![
" ; UBSan: array-bounds check".to_string(),
format!(" %idx_ge_zero = icmp sge i64 {}, 0", index_reg),
format!(" %idx_lt_size = icmp slt i64 {}, {}", index_reg, size_reg),
" %in_bounds = and i1 %idx_ge_zero, %idx_lt_size".to_string(),
format!(" br i1 %in_bounds, label %bounds_ok_{}, label %ubsan_oob_{}",
loc.line, loc.line),
]
}
pub fn emit_llvm_object_size_check(
&self,
loc: &SourceLocation,
ptr_reg: &str,
object_size: u64,
) -> Vec<String> {
vec![
" ; UBSan: object-size check".to_string(),
format!(
" %obj_size = call i64 @llvm.objectsize.i64.p0(ptr {}, i1 true)",
ptr_reg
),
format!(
" %size_ok = icmp uge i64 %obj_size, {}",
object_size
),
format!(
" br i1 %size_ok, label %objsize_ok_{}, label %ubsan_objsize_{}",
loc.line, loc.line
),
]
}
}
#[derive(Debug, Clone)]
pub struct AlignmentInstrumenter {
pub config: UBSanConfig,
}
impl AlignmentInstrumenter {
pub fn new(config: UBSanConfig) -> Self {
AlignmentInstrumenter { config }
}
pub fn check_alignment(&self, ptr_val: u64, alignment: u64, loc: &SourceLocation) -> Option<UBSanInstrumentationPoint> {
if !self.config.is_enabled(UBSanCheckKind::Alignment) {
return None;
}
if ptr_val % alignment != 0 {
Some(UBSanInstrumentationPoint::new(
UBSanCheckKind::Alignment,
UBSanErrorType::MisalignedPointer,
loc.clone(),
&format!("pointer {:x} is not aligned to {}", ptr_val, alignment),
))
} else {
None
}
}
pub fn emit_llvm_alignment_check(
&self,
loc: &SourceLocation,
ptr_reg: &str,
alignment: u64,
) -> Vec<String> {
vec![
format!(" ; UBSan: alignment check (align={})", alignment),
format!(" %ptr_int = ptrtoint ptr {} to i64", ptr_reg),
format!(" %aligned_mask = and i64 %ptr_int, {}", alignment - 1),
" %is_aligned = icmp eq i64 %aligned_mask, 0".to_string(),
format!(
" br i1 %is_aligned, label %align_ok_{}, label %ubsan_align_{}",
loc.line, loc.line
),
]
}
}
#[derive(Debug, Clone)]
pub struct NullCheckInstrumenter {
pub config: UBSanConfig,
}
impl NullCheckInstrumenter {
pub fn new(config: UBSanConfig) -> Self {
NullCheckInstrumenter { config }
}
pub fn emit_llvm_null_check(&self, loc: &SourceLocation, ptr_reg: &str) -> Vec<String> {
vec![
" ; UBSan: null pointer check".to_string(),
format!(" %is_null = icmp eq ptr {}, null", ptr_reg),
format!(
" br i1 %is_null, label %ubsan_null_{}, label %null_ok_{}",
loc.line, loc.line
),
]
}
pub fn emit_llvm_nonnull_return_check(&self, loc: &SourceLocation, ret_reg: &str) -> Vec<String> {
vec![
" ; UBSan: returns-nonnull-attribute check".to_string(),
format!(" %ret_null = icmp eq ptr {}, null", ret_reg),
format!(
" br i1 %ret_null, label %ubsan_nonnull_ret_{}, label %nonnull_ok_{}",
loc.line, loc.line
),
]
}
}
#[derive(Debug, Clone)]
pub struct FloatCastInstrumenter {
pub config: UBSanConfig,
}
impl FloatCastInstrumenter {
pub fn new(config: UBSanConfig) -> Self {
FloatCastInstrumenter { config }
}
pub fn check_float_to_int_overflow(&self, value: f64, target_min: i64, target_max: i64, loc: &SourceLocation) -> Option<UBSanInstrumentationPoint> {
if !self.config.is_enabled(UBSanCheckKind::FloatCastOverflow) {
return None;
}
if value.is_nan() || value.is_infinite() || value < target_min as f64 || value > target_max as f64 {
Some(UBSanInstrumentationPoint::new(
UBSanCheckKind::FloatCastOverflow,
UBSanErrorType::FloatCastOverflow,
loc.clone(),
&format!("float {} out of int range [{}, {}]", value, target_min, target_max),
))
} else {
None
}
}
pub fn emit_llvm_float_cast_check(
&self,
loc: &SourceLocation,
float_reg: &str,
int_type: &str,
) -> Vec<String> {
vec![
" ; UBSan: float-cast-overflow check".to_string(),
format!(
" %cast_ok = call i1 @__ubsan_float_cast_overflow({}, {})",
float_reg, int_type
),
format!(
" br i1 %cast_ok, label %fcast_ok_{}, label %ubsan_fcast_{}",
loc.line, loc.line
),
]
}
}
#[derive(Debug, Clone)]
pub struct VLABoundInstrumenter {
pub config: UBSanConfig,
}
impl VLABoundInstrumenter {
pub fn new(config: UBSanConfig) -> Self {
VLABoundInstrumenter { config }
}
pub fn check_vla_bound(&self, bound: i64, loc: &SourceLocation) -> Option<UBSanInstrumentationPoint> {
if !self.config.is_enabled(UBSanCheckKind::VLABound) {
return None;
}
if bound <= 0 {
Some(UBSanInstrumentationPoint::new(
UBSanCheckKind::VLABound,
UBSanErrorType::VLABoundNotPositive,
loc.clone(),
&format!("VLA bound {} is not positive", bound),
))
} else {
None
}
}
pub fn emit_llvm_vla_bound_check(&self, loc: &SourceLocation, bound_reg: &str) -> Vec<String> {
vec![
" ; UBSan: vla-bound check".to_string(),
format!(" %vla_pos = icmp sgt i64 {}, 0", bound_reg),
format!(
" br i1 %vla_pos, label %vla_ok_{}, label %ubsan_vla_{}",
loc.line, loc.line
),
]
}
}
#[derive(Debug, Clone)]
pub struct EnumInstrumenter {
pub config: UBSanConfig,
pub enum_ranges: HashMap<String, (i64, i64)>,
}
impl EnumInstrumenter {
pub fn new(config: UBSanConfig) -> Self {
EnumInstrumenter {
config,
enum_ranges: HashMap::new(),
}
}
pub fn register_enum(&mut self, name: &str, min_val: i64, max_val: i64) {
self.enum_ranges.insert(name.to_string(), (min_val, max_val));
}
pub fn emit_llvm_enum_check(
&self,
loc: &SourceLocation,
value_reg: &str,
enum_name: &str,
) -> Vec<String> {
if let Some(&(min, max)) = self.enum_ranges.get(enum_name) {
vec![
format!(" ; UBSan: enum check for {}", enum_name),
format!(" %enum_ge_min = icmp sge i64 {}, {}", value_reg, min),
format!(" %enum_le_max = icmp sle i64 {}, {}", value_reg, max),
" %enum_valid = and i1 %enum_ge_min, %enum_le_max".to_string(),
format!(
" br i1 %enum_valid, label %enum_ok_{}, label %ubsan_enum_{}",
loc.line, loc.line
),
]
} else {
vec![format!(" ; UBSan: enum {} not registered — no check", enum_name)]
}
}
}
pub fn emit_llvm_bool_check(loc: &SourceLocation, value_reg: &str, bit_width: u32) -> Vec<String> {
vec![
" ; UBSan: bool check".to_string(),
format!(" %bool_val = icmp ule i{} {}, 1", bit_width, value_reg),
format!(
" br i1 %bool_val, label %bool_ok_{}, label %ubsan_bool_{}",
loc.line, loc.line
),
]
}
#[derive(Debug, Clone)]
pub struct ReturnInstrumenter {
pub config: UBSanConfig,
}
impl ReturnInstrumenter {
pub fn new(config: UBSanConfig) -> Self {
ReturnInstrumenter { config }
}
pub fn emit_llvm_return_check(&self, loc: &SourceLocation) -> Vec<String> {
vec![
" ; UBSan: return check (missing return in non-void function)".to_string(),
format!(
" call void @__ubsan_handle_missing_return({}* @{}, ...)",
"__ubsan_source_location",
format!("__ubsan_src_loc_{}", loc.line)
),
" unreachable".to_string(),
]
}
}
pub fn emit_llvm_unreachable_check(loc: &SourceLocation) -> Vec<String> {
vec![
" ; UBSan: unreachable check".to_string(),
format!(
" call void @__ubsan_handle_builtin_unreachable({}* @{})",
"__ubsan_source_location",
format!("__ubsan_src_loc_{}", loc.line)
),
]
}
#[derive(Debug, Clone)]
pub struct ImplicitConversionInstrumenter {
pub config: UBSanConfig,
}
impl ImplicitConversionInstrumenter {
pub fn new(config: UBSanConfig) -> Self {
ImplicitConversionInstrumenter { config }
}
pub fn check_signed_truncation(&self, value: i64, target_bit_width: u32, loc: &SourceLocation) -> Option<UBSanInstrumentationPoint> {
if !self.config.is_enabled(UBSanCheckKind::ImplicitSignedIntegerTruncation) {
return None;
}
let min_val = -(1i64 << (target_bit_width - 1));
let max_val = (1i64 << (target_bit_width - 1)) - 1;
if value < min_val || value > max_val {
Some(UBSanInstrumentationPoint::new(
UBSanCheckKind::ImplicitSignedIntegerTruncation,
UBSanErrorType::TypeMismatch,
loc.clone(),
&format!("signed truncation {} to {}-bit", value, target_bit_width),
))
} else {
None
}
}
pub fn check_sign_change(&self, value: u64, target_signed: bool, loc: &SourceLocation) -> Option<UBSanInstrumentationPoint> {
if !self.config.is_enabled(UBSanCheckKind::ImplicitIntegerSignChange) {
return None;
}
if target_signed && value > (i64::MAX as u64) {
Some(UBSanInstrumentationPoint::new(
UBSanCheckKind::ImplicitIntegerSignChange,
UBSanErrorType::TypeMismatch,
loc.clone(),
"implicit sign change",
))
} else {
None
}
}
pub fn emit_llvm_truncation_check(
&self,
loc: &SourceLocation,
value_reg: &str,
src_bit_width: u32,
dst_bit_width: u32,
) -> Vec<String> {
vec![
format!(
" ; UBSan: implicit-signed-integer-truncation check ({} -> {} bits)",
src_bit_width, dst_bit_width
),
format!(
" %trunc = trunc i{} {} to i{}",
src_bit_width, value_reg, dst_bit_width
),
format!(
" %ext = sext i{} %trunc to i{}",
dst_bit_width, src_bit_width
),
format!(
" %no_trunc = icmp eq i{} %ext, {}",
src_bit_width, value_reg
),
format!(
" br i1 %no_trunc, label %trunc_ok_{}, label %ubsan_trunc_{}",
loc.line, loc.line
),
]
}
}
pub fn emit_llvm_function_type_check(
loc: &SourceLocation,
fn_ptr_reg: &str,
expected_type: &str,
) -> Vec<String> {
vec![
" ; UBSan: function type mismatch check".to_string(),
format!(" %fn_type_ok = call i1 @__ubsan_function_type_mismatch(ptr {}, {})",
fn_ptr_reg, expected_type),
format!(
" br i1 %fn_type_ok, label %ftype_ok_{}, label %ubsan_ftype_{}",
loc.line, loc.line
),
]
}
#[derive(Debug, Clone)]
pub struct VptrInstrumenter {
pub config: UBSanConfig,
}
impl VptrInstrumenter {
pub fn new(config: UBSanConfig) -> Self {
VptrInstrumenter { config }
}
pub fn emit_llvm_vptr_check(
&self,
loc: &SourceLocation,
obj_ptr_reg: &str,
vtable_type: &str,
) -> Vec<String> {
vec![
" ; UBSan: vptr check".to_string(),
format!(
" %vptr = load ptr, ptr {}",
obj_ptr_reg
),
format!(
" %vptr_ok = call i1 @__ubsan_vptr_type_cache(ptr %vptr, {})",
vtable_type
),
format!(
" br i1 %vptr_ok, label %vptr_ok_{}, label %ubsan_vptr_{}",
loc.line, loc.line
),
]
}
}
#[derive(Debug, Clone)]
pub struct UBSanRuntimeDeclarations;
impl UBSanRuntimeDeclarations {
pub fn all_declarations() -> Vec<String> {
vec![
"declare void @__ubsan_handle_add_overflow({}*, i64, i64)".to_string(),
"declare void @__ubsan_handle_sub_overflow({}*, i64, i64)".to_string(),
"declare void @__ubsan_handle_mul_overflow({}*, i64, i64)".to_string(),
"declare void @__ubsan_handle_negate_overflow({}*, i64)".to_string(),
"declare void @__ubsan_handle_divrem_overflow({}*, i64, i64)".to_string(),
"declare void @__ubsan_handle_shift_out_of_bounds({}*, i64, i64)".to_string(),
"declare void @__ubsan_handle_out_of_bounds({}*, i64)".to_string(),
"declare void @__ubsan_handle_type_mismatch_v1({}*, i64)".to_string(),
"declare void @__ubsan_handle_alignment_assumption({}*, i64, i64, i64)".to_string(),
"declare void @__ubsan_handle_load_invalid_value({}*, i64)".to_string(),
"declare void @__ubsan_handle_float_cast_overflow({}*, i64, i64)".to_string(),
"declare void @__ubsan_handle_missing_return({}*)".to_string(),
"declare void @__ubsan_handle_builtin_unreachable({}*)".to_string(),
"declare void @__ubsan_handle_invalid_builtin({}*)".to_string(),
"declare void @__ubsan_handle_function_type_mismatch({}*, i64)".to_string(),
"declare void @__ubsan_handle_nonnull_return({}*)".to_string(),
"declare void @__ubsan_handle_nonnull_arg({}*)".to_string(),
"declare void @__ubsan_handle_pointer_overflow({}*, i64, i64)".to_string(),
"declare void @__ubsan_handle_vla_bound_not_positive({}*, i64)".to_string(),
"declare void @__ubsan_handle_dynamic_type_cache_miss({}*, i64, i64)".to_string(),
]
}
pub fn source_location_type() -> String {
"type __ubsan_source_location = { [256 x i8], i32, i32 }".to_string()
}
pub fn emit_source_location(loc: &SourceLocation) -> String {
format!(
"@__ubsan_src_loc_{} = private constant {{ [{} x i8], i32, i32 }} \
{{ [{} x i8] c\"{}\", i32 {}, i32 {} }}, align 4",
loc.line,
loc.filename.len(),
loc.filename.len(),
loc.filename,
loc.line,
loc.column,
)
}
}
#[derive(Debug, Clone)]
pub struct UBSanMinimalRuntime {
pub output_fd: i32,
pub error_count: usize,
pub max_errors: usize,
message_buffer: Vec<u8>,
}
impl UBSanMinimalRuntime {
pub fn new() -> Self {
UBSanMinimalRuntime {
output_fd: 2, error_count: 0,
max_errors: 100,
message_buffer: Vec::new(),
}
}
pub fn report_error(&mut self, error_type: UBSanErrorType, loc: &SourceLocation, message: &str) {
if self.error_count >= self.max_errors {
return;
}
self.error_count += 1;
let formatted = format!(
"{}runtime error: {}: {}\n",
loc.format_runtime(),
error_type,
message
);
self.message_buffer.extend(formatted.as_bytes());
}
pub fn get_messages(&self) -> String {
String::from_utf8_lossy(&self.message_buffer).to_string()
}
pub fn clear(&mut self) {
self.message_buffer.clear();
}
pub fn generate_minimal_runtime_source() -> String {
let mut src = String::new();
src.push_str("/* UBSan Minimal Runtime — generated by llvm-native-core */\n\n");
src.push_str("#include <stdint.h>\n");
src.push_str("#include <stddef.h>\n\n");
src.push_str("static void __ubsan_minimal_report(const char *kind,\n");
src.push_str(" const char *file, unsigned line, unsigned col,\n");
src.push_str(" const char *message) {\n");
src.push_str(" /* Write to stderr or custom output */\n");
src.push_str(" // In minimal runtime, we may just trap or log via UART\n");
src.push_str(" __builtin_trap();\n");
src.push_str("}\n\n");
let handles = [
"add_overflow", "sub_overflow", "mul_overflow", "negate_overflow",
"divrem_overflow", "shift_out_of_bounds", "out_of_bounds",
"type_mismatch_v1", "alignment_assumption", "load_invalid_value",
"float_cast_overflow", "missing_return", "builtin_unreachable",
"invalid_builtin", "function_type_mismatch", "nonnull_return",
"nonnull_arg", "pointer_overflow", "vla_bound_not_positive",
"dynamic_type_cache_miss",
];
for handle in &handles {
src.push_str(&format!(
"void __ubsan_handle_{}(void *data, ...) {{\n",
handle
));
src.push_str(" __builtin_trap();\n");
src.push_str("}\n\n");
}
src
}
pub fn emit_llvm_init(&self) -> Vec<String> {
vec![
" ; UBSan minimal runtime initialization".to_string(),
" call void @__ubsan_minimal_init()".to_string(),
]
}
}
impl Default for UBSanMinimalRuntime {
fn default() -> Self {
UBSanMinimalRuntime::new()
}
}
#[derive(Debug, Clone)]
pub struct UBSanPass {
pub config: UBSanConfig,
pub int_overflow: IntegerOverflowInstrumenter,
pub div_zero: DivisionByZeroInstrumenter,
pub shift_bounds: ShiftBoundsInstrumenter,
pub array_bounds: ArrayBoundsInstrumenter,
pub alignment: AlignmentInstrumenter,
pub null_check: NullCheckInstrumenter,
pub float_cast: FloatCastInstrumenter,
pub vla_bound: VLABoundInstrumenter,
pub enum_check: EnumInstrumenter,
pub return_check: ReturnInstrumenter,
pub implicit_conv: ImplicitConversionInstrumenter,
pub vptr_check: VptrInstrumenter,
pub minimal_runtime: Option<UBSanMinimalRuntime>,
pub instrumentation_points: Vec<UBSanInstrumentationPoint>,
}
impl UBSanPass {
pub fn new(config: UBSanConfig) -> Self {
UBSanPass {
enum_check: EnumInstrumenter::new(config.clone()),
int_overflow: IntegerOverflowInstrumenter::new(config.clone()),
div_zero: DivisionByZeroInstrumenter::new(config.clone()),
shift_bounds: ShiftBoundsInstrumenter::new(config.clone()),
array_bounds: ArrayBoundsInstrumenter::new(config.clone()),
alignment: AlignmentInstrumenter::new(config.clone()),
null_check: NullCheckInstrumenter::new(config.clone()),
float_cast: FloatCastInstrumenter::new(config.clone()),
vla_bound: VLABoundInstrumenter::new(config.clone()),
return_check: ReturnInstrumenter::new(config.clone()),
implicit_conv: ImplicitConversionInstrumenter::new(config.clone()),
vptr_check: VptrInstrumenter::new(config.clone()),
minimal_runtime: if config.minimal_runtime {
Some(UBSanMinimalRuntime::new())
} else {
None
},
instrumentation_points: Vec::new(),
config,
}
}
pub fn run(&mut self) -> usize {
self.instrumentation_points.clear();
self.config.enabled.len()
}
pub fn emit_llvm_module(&self, module_name: &str) -> String {
let mut out = String::new();
out.push_str(&format!("; UBSan instrumented module: {}\n", module_name));
out.push_str(&format!("; Enabled checks: {}\n", self.config.enabled.len()));
for check in &self.config.enabled {
out.push_str(&format!("; - {}\n", check));
}
out.push_str("\n; Source Location Data\n");
out.push_str(&UBSanRuntimeDeclarations::source_location_type());
out.push('\n');
out.push_str("\n; Runtime Declarations\n");
for decl in UBSanRuntimeDeclarations::all_declarations() {
out.push_str(&decl);
out.push('\n');
}
if self.config.minimal_runtime {
out.push_str("\n; Minimal Runtime\n");
out.push_str("declare void @__ubsan_minimal_init()\n");
}
out
}
pub fn summary(&self) -> String {
let mut s = String::new();
s.push_str("UBSan Configuration Summary\n");
s.push_str("==========================\n");
s.push_str(&format!("Enabled checks: {}\n", self.config.enabled.len()));
s.push_str(&format!("Verbose errors: {}\n", self.config.verbose));
s.push_str(&format!("Halt on error: {}\n", self.config.halt_on_error));
s.push_str(&format!("Minimal runtime: {}\n", self.config.minimal_runtime));
s.push_str(&format!("Blacklist size: {}\n", self.config.blacklist.len()));
s
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_all() {
let config = UBSanConfig::all();
assert!(!config.enabled.is_empty());
assert!(config.is_enabled(UBSanCheckKind::SignedIntegerOverflow));
assert!(config.is_enabled(UBSanCheckKind::Null));
}
#[test]
fn test_config_enable_by_name() {
let mut config = UBSanConfig::default();
assert!(config.enable_by_name("signed-integer-overflow"));
assert!(config.is_enabled(UBSanCheckKind::SignedIntegerOverflow));
assert!(!config.enable_by_name("nonexistent-check"));
}
#[test]
fn test_source_location_format() {
let loc = SourceLocation::new("test.c", 42, 10);
assert_eq!(loc.format(), "test.c:42:10");
}
#[test]
fn test_source_location_with_function() {
let loc = SourceLocation::new("test.c", 100, 5)
.with_function("main");
assert!(loc.format().contains("main"));
}
#[test]
fn test_type_descriptor() {
let td = TypeDescriptor::new(TypeKind::Integer { bit_width: 32, signed: true }, "int");
assert_eq!(td.type_kind, 0x0000);
assert_eq!(td.type_info, 32);
}
#[test]
fn test_type_descriptor_float() {
let td = TypeDescriptor::new(TypeKind::Float { bit_width: 64 }, "double");
assert_eq!(td.type_kind, 0x0002);
assert_eq!(td.type_info, 64);
}
#[test]
fn test_check_signed_add_overflow() {
let config = UBSanConfig::all();
let instrumenter = IntegerOverflowInstrumenter::new(config);
assert!(instrumenter.check_signed_add_overflow(i64::MAX, 1).is_some());
assert!(instrumenter.check_signed_add_overflow(1, 1).is_none());
assert!(instrumenter.check_signed_add_overflow(i64::MIN, -1).is_some());
}
#[test]
fn test_check_signed_mul_overflow() {
let config = UBSanConfig::all();
let instrumenter = IntegerOverflowInstrumenter::new(config);
assert!(instrumenter.check_signed_mul_overflow(i64::MAX, 2).is_some());
assert!(instrumenter.check_signed_mul_overflow(0, i64::MAX).is_none());
}
#[test]
fn test_check_signed_neg_overflow() {
let config = UBSanConfig::all();
let instrumenter = IntegerOverflowInstrumenter::new(config);
assert!(instrumenter.check_signed_neg_overflow(i64::MIN).is_some());
assert!(instrumenter.check_signed_neg_overflow(5).is_none());
}
#[test]
fn test_check_div_zero() {
let config = UBSanConfig::all();
let instrumenter = DivisionByZeroInstrumenter::new(config);
let loc = SourceLocation::new("test.c", 1, 1);
assert!(instrumenter.check_integer_div_zero(0, &loc).is_some());
assert!(instrumenter.check_integer_div_zero(5, &loc).is_none());
}
#[test]
fn test_check_shift_base() {
let config = UBSanConfig::all();
let instrumenter = ShiftBoundsInstrumenter::new(config);
let loc = SourceLocation::new("test.c", 1, 1);
assert!(instrumenter.check_shift_base(32, 32, &loc).is_some());
assert!(instrumenter.check_shift_base(31, 32, &loc).is_none());
}
#[test]
fn test_check_array_bounds() {
let config = UBSanConfig::all();
let instrumenter = ArrayBoundsInstrumenter::new(config);
let loc = SourceLocation::new("test.c", 1, 1);
assert!(instrumenter.check_array_bounds(10, 5, &loc).is_some());
assert!(instrumenter.check_array_bounds(3, 5, &loc).is_none());
assert!(instrumenter.check_array_bounds(-1, 5, &loc).is_some());
}
#[test]
fn test_check_alignment() {
let config = UBSanConfig::all();
let instrumenter = AlignmentInstrumenter::new(config);
let loc = SourceLocation::new("test.c", 1, 1);
assert!(instrumenter.check_alignment(0x1001, 8, &loc).is_some()); assert!(instrumenter.check_alignment(0x1000, 8, &loc).is_none()); }
#[test]
fn test_check_float_cast_overflow() {
let config = UBSanConfig::all();
let instrumenter = FloatCastInstrumenter::new(config);
let loc = SourceLocation::new("test.c", 1, 1);
assert!(instrumenter.check_float_to_int_overflow(1e20, -100, 100, &loc).is_some());
assert!(instrumenter.check_float_to_int_overflow(42.0, -100, 100, &loc).is_none());
assert!(instrumenter.check_float_to_int_overflow(f64::NAN, -100, 100, &loc).is_some());
}
#[test]
fn test_check_vla_bound() {
let config = UBSanConfig::all();
let instrumenter = VLABoundInstrumenter::new(config);
let loc = SourceLocation::new("test.c", 1, 1);
assert!(instrumenter.check_vla_bound(0, &loc).is_some());
assert!(instrumenter.check_vla_bound(-1, &loc).is_some());
assert!(instrumenter.check_vla_bound(100, &loc).is_none());
}
#[test]
fn test_check_signed_truncation() {
let config = UBSanConfig::all();
let instrumenter = ImplicitConversionInstrumenter::new(config);
let loc = SourceLocation::new("test.c", 1, 1);
assert!(instrumenter.check_signed_truncation(1000, 8, &loc).is_some());
assert!(instrumenter.check_signed_truncation(50, 8, &loc).is_none());
}
#[test]
fn test_ubsan_error_type_display() {
assert_eq!(UBSanErrorType::IntegerOverflow.to_string(), "signed integer overflow");
assert_eq!(UBSanErrorType::NullPointerUse.to_string(), "null pointer use");
}
#[test]
fn test_ubsan_check_display() {
let s = UBSanCheckKind::SignedIntegerOverflow.to_string();
assert_eq!(s, "signed-integer-overflow");
}
#[test]
fn test_minimal_runtime_source_generation() {
let src = UBSanMinimalRuntime::generate_minimal_runtime_source();
assert!(src.contains("__ubsan_handle_add_overflow"));
assert!(src.contains("__builtin_trap"));
assert!(src.contains("__ubsan_handle_pointer_overflow"));
}
#[test]
fn test_minimal_runtime_report() {
let mut rt = UBSanMinimalRuntime::new();
let loc = SourceLocation::new("test.c", 10, 5);
rt.report_error(UBSanErrorType::NullPointerUse, &loc, "dereferenced NULL");
assert_eq!(rt.error_count, 1);
let msgs = rt.get_messages();
assert!(msgs.contains("null pointer use"));
assert!(msgs.contains("test.c:10:5"));
}
#[test]
fn test_runtime_declarations_count() {
let decls = UBSanRuntimeDeclarations::all_declarations();
assert!(decls.iter().any(|d| d.contains("__ubsan_handle_add_overflow")));
assert!(decls.iter().any(|d| d.contains("__ubsan_handle_missing_return")));
}
#[test]
fn test_source_location_emit() {
let loc = SourceLocation::new("main.c", 15, 3);
let s = UBSanRuntimeDeclarations::emit_source_location(&loc);
assert!(s.contains("main.c"));
assert!(s.contains("15"));
}
#[test]
fn test_pass_summary() {
let config = UBSanConfig::all();
let pass = UBSanPass::new(config);
let summary = pass.summary();
assert!(summary.contains("UBSan Configuration Summary"));
assert!(summary.contains("Enabled checks"));
}
#[test]
fn test_pass_emit_llvm() {
let config = UBSanConfig::all();
let pass = UBSanPass::new(config);
let llvm = pass.emit_llvm_module("test_module");
assert!(llvm.contains("UBSan instrumented module"));
assert!(llvm.contains("__ubsan_handle_add_overflow"));
}
#[test]
fn test_disabled_checks_not_instrumented() {
let config = UBSanConfig::default();
let instrumenter = IntegerOverflowInstrumenter::new(config);
assert!(instrumenter.check_signed_add_overflow(i64::MAX, 1).is_none());
}
#[test]
fn test_multiple_enabled_checks() {
let mut config = UBSanConfig::default();
config.enable_by_name("signed-integer-overflow");
config.enable_by_name("null");
config.enable_by_name("alignment");
assert_eq!(config.enabled.len(), 3);
}
#[test]
fn test_enum_instrumenter() {
let mut inst = EnumInstrumenter::new(UBSanConfig::all());
inst.register_enum("Color", 0, 2);
let loc = SourceLocation::new("test.c", 1, 1);
let result = inst.emit_llvm_enum_check(&loc, "%val", "Color");
assert!(!result.is_empty());
assert!(result[0].contains("enum check"));
}
#[test]
fn test_llvm_gen_add_overflow() {
let config = UBSanConfig::all();
let inst = IntegerOverflowInstrumenter::new(config);
let loc = SourceLocation::new("test.c", 42, 1);
let ops = inst.emit_llvm_sadd_overflow_check(&loc, "%a", "%b");
assert!(ops.iter().any(|s| s.contains("sadd.with.overflow")));
}
#[test]
fn test_llvm_gen_div_zero() {
let config = UBSanConfig::all();
let inst = DivisionByZeroInstrumenter::new(config);
let loc = SourceLocation::new("test.c", 42, 1);
let ops = inst.emit_llvm_div_zero_check(&loc, "%divisor", 64);
assert!(ops.iter().any(|s| s.contains("icmp eq i64 %divisor, 0")));
}
#[test]
fn test_llvm_gen_shift_check() {
let config = UBSanConfig::all();
let inst = ShiftBoundsInstrumenter::new(config);
let loc = SourceLocation::new("test.c", 42, 1);
let ops = inst.emit_llvm_shift_check(&loc, "%shamt", 64);
assert!(ops.iter().any(|s| s.contains("icmp ult")));
}
#[test]
fn test_llvm_gen_array_bounds() {
let config = UBSanConfig::all();
let inst = ArrayBoundsInstrumenter::new(config);
let loc = SourceLocation::new("test.c", 42, 1);
let ops = inst.emit_llvm_array_bounds_check(&loc, "%idx", "%size");
assert!(ops.iter().any(|s| s.contains("icmp sge i64")));
assert!(ops.iter().any(|s| s.contains("icmp slt i64")));
}
#[test]
fn test_llvm_gen_alignment() {
let config = UBSanConfig::all();
let inst = AlignmentInstrumenter::new(config);
let loc = SourceLocation::new("test.c", 42, 1);
let ops = inst.emit_llvm_alignment_check(&loc, "%ptr", 8);
assert!(ops.iter().any(|s| s.contains("ptrtoint")));
assert!(ops.iter().any(|s| s.contains("and i64")));
}
#[test]
fn test_llvm_gen_null_check() {
let inst = NullCheckInstrumenter::new(UBSanConfig::all());
let loc = SourceLocation::new("test.c", 42, 1);
let ops = inst.emit_llvm_null_check(&loc, "%ptr");
assert!(ops.iter().any(|s| s.contains("icmp eq ptr %ptr, null")));
}
#[test]
fn test_check_signed_div_overflow() {
let config = UBSanConfig::all();
let inst = DivisionByZeroInstrumenter::new(config);
let loc = SourceLocation::new("test.c", 1, 1);
assert!(inst.check_signed_div_overflow(i64::MIN, -1, &loc).is_some());
assert!(inst.check_signed_div_overflow(10, 2, &loc).is_none());
}
#[test]
fn test_check_sign_change() {
let config = UBSanConfig::all();
let inst = ImplicitConversionInstrumenter::new(config);
let loc = SourceLocation::new("test.c", 1, 1);
assert!(inst.check_sign_change(i64::MAX as u64 + 1, true, &loc).is_some());
assert!(inst.check_sign_change(42, true, &loc).is_none());
}
#[test]
fn test_minimal_runtime_clear() {
let mut rt = UBSanMinimalRuntime::new();
rt.report_error(UBSanErrorType::ShiftOutOfBounds, &SourceLocation::new("t.c", 1, 1), "test");
rt.clear();
assert!(rt.get_messages().is_empty());
}
#[test]
fn test_minimal_runtime_max_errors() {
let mut rt = UBSanMinimalRuntime::new();
rt.max_errors = 5;
for i in 0..10 {
rt.report_error(UBSanErrorType::OutOfBounds, &SourceLocation::new("t.c", i, 1), "");
}
assert_eq!(rt.error_count, 5);
}
}