use std::collections::{HashMap, HashSet};
use std::fmt;
use super::ast::*;
impl QualType {
pub fn is_bool(&self) -> bool {
matches!(self.inner(), TypeNode::Bool)
}
pub fn is_scalar(&self) -> bool {
self.is_arithmetic() || self.is_pointer()
}
pub fn is_arithmetic(&self) -> bool {
self.is_integer() || self.is_floating()
}
pub fn is_long_double(&self) -> bool {
matches!(self.inner(), TypeNode::LongDouble)
}
pub fn is_unsigned(&self) -> bool {
matches!(
self.inner(),
TypeNode::UChar
| TypeNode::UShort
| TypeNode::UInt
| TypeNode::ULong
| TypeNode::ULongLong
)
}
pub fn is_function_type(&self) -> bool {
matches!(self.inner(), TypeNode::Function { .. })
}
pub fn is_void_ptr(&self) -> bool {
if let TypeNode::Pointer(pointee) = self.inner() {
return pointee.is_void();
}
false
}
pub fn is_void_pointer(&self) -> bool {
self.is_void_ptr()
}
}
pub const X86_LP64_CHAR_SIZE: u64 = 1;
pub const X86_LP64_SHORT_SIZE: u64 = 2;
pub const X86_LP64_INT_SIZE: u64 = 4;
pub const X86_LP64_LONG_SIZE: u64 = 8;
pub const X86_LP64_LONG_LONG_SIZE: u64 = 8;
pub const X86_LP64_POINTER_SIZE: u64 = 8;
pub const X86_LP64_FLOAT_SIZE: u64 = 4;
pub const X86_LP64_DOUBLE_SIZE: u64 = 8;
pub const X86_LP64_LONG_DOUBLE_SIZE: u64 = 16;
pub const X86_LP64_SIZE_T_SIZE: u64 = 8;
pub const X86_LP64_WCHAR_T_SIZE: u64 = 4;
pub const X86_ILP32_LONG_SIZE: u64 = 4;
pub const X86_ILP32_POINTER_SIZE: u64 = 4;
pub const X86_ILP32_SIZE_T_SIZE: u64 = 4;
pub const X86_MAX_INT_WIDTH: u32 = 128;
pub const X86_MMX_WIDTH: u32 = 64;
pub const X86_SSE_WIDTH: u32 = 128;
pub const X86_AVX_WIDTH: u32 = 256;
pub const X86_AVX512_WIDTH: u32 = 512;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86ScopeKind {
File,
Function,
FunctionPrototype,
Block,
Namespace,
Class,
TemplateParam,
Enum,
TryBlock,
CatchBlock,
Lambda,
}
impl fmt::Display for X86ScopeKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86ScopeKind::File => write!(f, "file"),
X86ScopeKind::Function => write!(f, "function"),
X86ScopeKind::FunctionPrototype => write!(f, "function-prototype"),
X86ScopeKind::Block => write!(f, "block"),
X86ScopeKind::Namespace => write!(f, "namespace"),
X86ScopeKind::Class => write!(f, "class"),
X86ScopeKind::TemplateParam => write!(f, "template-param"),
X86ScopeKind::Enum => write!(f, "enum"),
X86ScopeKind::TryBlock => write!(f, "try-block"),
X86ScopeKind::CatchBlock => write!(f, "catch-block"),
X86ScopeKind::Lambda => write!(f, "lambda"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum X86ConversionRank {
ExactMatch = 0,
Promotion = 1,
Conversion = 2,
UserDefined = 3,
Ellipsis = 4,
NotViable = 5,
}
impl fmt::Display for X86ConversionRank {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86ConversionRank::ExactMatch => write!(f, "ExactMatch"),
X86ConversionRank::Promotion => write!(f, "Promotion"),
X86ConversionRank::Conversion => write!(f, "Conversion"),
X86ConversionRank::UserDefined => write!(f, "UserDefined"),
X86ConversionRank::Ellipsis => write!(f, "Ellipsis"),
X86ConversionRank::NotViable => write!(f, "NotViable"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86DeclCategory {
Variable,
Function,
Typedef,
Struct,
Union,
Enum,
EnumConstant,
Parameter,
Field,
TemplateParam,
Namespace,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ValueCategory {
LValue,
RValue,
XValue,
PrValue,
}
impl fmt::Display for X86ValueCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86ValueCategory::LValue => write!(f, "lvalue"),
X86ValueCategory::RValue => write!(f, "rvalue"),
X86ValueCategory::XValue => write!(f, "xvalue"),
X86ValueCategory::PrValue => write!(f, "prvalue"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86InstantiationDepth {
pub current: u32,
pub maximum: u32,
pub stack: Vec<String>,
}
impl X86InstantiationDepth {
pub fn new(maximum: u32) -> Self {
Self {
current: 0,
maximum,
stack: Vec::new(),
}
}
pub fn enter(&mut self, name: String) -> Result<(), String> {
if self.current >= self.maximum {
return Err(format!(
"template instantiation depth {} exceeds maximum {}",
self.current, self.maximum
));
}
self.current += 1;
self.stack.push(name);
Ok(())
}
pub fn leave(&mut self) {
if self.current > 0 {
self.current -= 1;
self.stack.pop();
}
}
pub fn is_at_max(&self) -> bool {
self.current >= self.maximum
}
}
impl Default for X86InstantiationDepth {
fn default() -> Self {
Self::new(1024)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum X86DiagSeverity {
Ignored = 0,
Note = 1,
Warning = 2,
Error = 3,
Fatal = 4,
}
impl fmt::Display for X86DiagSeverity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86DiagSeverity::Ignored => write!(f, "ignored"),
X86DiagSeverity::Note => write!(f, "note"),
X86DiagSeverity::Warning => write!(f, "warning"),
X86DiagSeverity::Error => write!(f, "error"),
X86DiagSeverity::Fatal => write!(f, "fatal error"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86Diagnostic {
pub severity: X86DiagSeverity,
pub message: String,
pub file: Option<String>,
pub line: u32,
pub column: u32,
pub notes: Vec<X86DiagNote>,
pub fixit_hints: Vec<X86FixIt>,
pub code_snippet: Option<String>,
pub diag_id: Option<String>,
pub category: X86DiagCategory,
}
#[derive(Debug, Clone)]
pub struct X86DiagNote {
pub message: String,
pub file: Option<String>,
pub line: u32,
pub column: u32,
}
#[derive(Debug, Clone)]
pub struct X86FixIt {
pub file: String,
pub start_line: u32,
pub start_col: u32,
pub end_line: u32,
pub end_col: u32,
pub replacement: String,
pub description: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86DiagCategory {
Semantic,
Parse,
Lex,
TypeCheck,
Template,
Overload,
Constexpr,
ODR,
Shadow,
Other,
}
impl fmt::Display for X86DiagCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86DiagCategory::Semantic => write!(f, "semantic"),
X86DiagCategory::Parse => write!(f, "parse"),
X86DiagCategory::Lex => write!(f, "lex"),
X86DiagCategory::TypeCheck => write!(f, "type-check"),
X86DiagCategory::Template => write!(f, "template"),
X86DiagCategory::Overload => write!(f, "overload"),
X86DiagCategory::Constexpr => write!(f, "constexpr"),
X86DiagCategory::ODR => write!(f, "odr"),
X86DiagCategory::Shadow => write!(f, "shadow"),
X86DiagCategory::Other => write!(f, "other"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86DiagnosticEmitter {
pub diagnostics: Vec<X86Diagnostic>,
pub warnings_as_errors: bool,
pub error_limit: usize,
pub suppressed_categories: HashSet<X86DiagCategory>,
pub enabled_only: Option<HashSet<X86DiagCategory>>,
pub source_files: HashMap<String, Vec<String>>,
pub template_backtrace: Vec<X86InstantiationRecord>,
pub overload_notes: Vec<String>,
pub type_names: HashMap<String, String>,
pub generate_fixits: bool,
pub color: bool,
}
#[derive(Debug, Clone)]
pub struct X86InstantiationRecord {
pub template_name: String,
pub arguments: Vec<String>,
pub file: Option<String>,
pub line: u32,
pub column: u32,
}
impl X86DiagnosticEmitter {
pub fn new() -> Self {
Self {
diagnostics: Vec::new(),
warnings_as_errors: false,
error_limit: 256,
suppressed_categories: HashSet::new(),
enabled_only: None,
source_files: HashMap::new(),
template_backtrace: Vec::new(),
overload_notes: Vec::new(),
type_names: HashMap::new(),
generate_fixits: true,
color: false,
}
}
pub fn error(&mut self, message: impl Into<String>) {
self.emit(X86DiagSeverity::Error, message.into(), None, 0, 0);
}
pub fn error_at(
&mut self,
message: impl Into<String>,
file: Option<impl Into<String>>,
line: u32,
column: u32,
) {
self.emit(
X86DiagSeverity::Error,
message.into(),
file.map(|f| f.into()),
line,
column,
);
}
pub fn warning(&mut self, message: impl Into<String>) {
let severity = if self.warnings_as_errors {
X86DiagSeverity::Error
} else {
X86DiagSeverity::Warning
};
self.emit(severity, message.into(), None, 0, 0);
}
pub fn warning_at(
&mut self,
message: impl Into<String>,
file: Option<impl Into<String>>,
line: u32,
column: u32,
) {
let severity = if self.warnings_as_errors {
X86DiagSeverity::Error
} else {
X86DiagSeverity::Warning
};
self.emit(
severity,
message.into(),
file.map(|f| f.into()),
line,
column,
);
}
pub fn note(&mut self, message: impl Into<String>) {
self.emit(X86DiagSeverity::Note, message.into(), None, 0, 0);
}
pub fn fatal(&mut self, message: impl Into<String>) {
self.emit(X86DiagSeverity::Fatal, message.into(), None, 0, 0);
}
fn emit(
&mut self,
severity: X86DiagSeverity,
message: String,
file: Option<String>,
line: u32,
column: u32,
) {
if self.error_count() >= self.error_limit && severity >= X86DiagSeverity::Error {
return;
}
let category = X86DiagnosticEmitter::categorize_message(&message);
if self.suppressed_categories.contains(&category) {
return;
}
if let Some(ref enabled) = self.enabled_only {
if !enabled.contains(&category) {
return;
}
}
let mut diag = X86Diagnostic {
severity,
message: message.clone(),
file,
line,
column,
notes: Vec::new(),
fixit_hints: Vec::new(),
code_snippet: None,
diag_id: None,
category,
};
for record in self.template_backtrace.iter().rev() {
diag.notes.push(X86DiagNote {
message: format!(
"in instantiation of template '{}' with arguments [{}]",
record.template_name,
record.arguments.join(", ")
),
file: record.file.clone(),
line: record.line,
column: record.column,
});
}
for note_msg in &self.overload_notes {
diag.notes.push(X86DiagNote {
message: note_msg.clone(),
file: None,
line: 0,
column: 0,
});
}
self.diagnostics.push(diag);
}
fn categorize_message(message: &str) -> X86DiagCategory {
let lower = message.to_lowercase();
if lower.contains("template") {
X86DiagCategory::Template
} else if lower.contains("overload") || lower.contains("ambiguous") {
X86DiagCategory::Overload
} else if lower.contains("constexpr") || lower.contains("consteval") {
X86DiagCategory::Constexpr
} else if lower.contains("odr") || lower.contains("one definition") {
X86DiagCategory::ODR
} else if lower.contains("shadow") {
X86DiagCategory::Shadow
} else if lower.contains("type")
|| lower.contains("incompatible")
|| lower.contains("conversion")
{
X86DiagCategory::TypeCheck
} else {
X86DiagCategory::Semantic
}
}
pub fn push_template_backtrace(
&mut self,
name: impl Into<String>,
args: Vec<String>,
file: Option<String>,
line: u32,
col: u32,
) {
self.template_backtrace.push(X86InstantiationRecord {
template_name: name.into(),
arguments: args,
file,
line,
column: col,
});
}
pub fn pop_template_backtrace(&mut self) {
self.template_backtrace.pop();
}
pub fn add_overload_note(&mut self, note: impl Into<String>) {
self.overload_notes.push(note.into());
}
pub fn clear_overload_notes(&mut self) {
self.overload_notes.clear();
}
pub fn register_type_name(&mut self, key: impl Into<String>, display: impl Into<String>) {
self.type_names.insert(key.into(), display.into());
}
pub fn pretty_type_name(&self, ty: &str) -> String {
self.type_names
.get(ty)
.cloned()
.unwrap_or_else(|| ty.to_string())
}
pub fn type_mismatch(
&mut self,
expected: &str,
actual: &str,
context: &str,
file: Option<impl Into<String>>,
line: u32,
column: u32,
) {
let expected_pretty = self.pretty_type_name(expected);
let actual_pretty = self.pretty_type_name(actual);
let msg = format!(
"type mismatch in {}: expected '{}', got '{}'",
context, expected_pretty, actual_pretty
);
self.error_at(msg, file, line, column);
}
pub fn note_previous_declaration(
&mut self,
name: &str,
file: Option<impl Into<String>>,
line: u32,
column: u32,
) {
let note = X86DiagNote {
message: format!("previous declaration of '{}' here", name),
file: file.map(|f| f.into()),
line,
column,
};
if let Some(diag) = self.diagnostics.last_mut() {
diag.notes.push(note);
}
}
pub fn add_fixit(&mut self, fixit: X86FixIt) {
if self.generate_fixits {
if let Some(diag) = self.diagnostics.last_mut() {
diag.fixit_hints.push(fixit);
}
}
}
pub fn format_all(&self) -> String {
let mut output = String::new();
for diag in &self.diagnostics {
output.push_str(&self.format_diagnostic(diag));
output.push('\n');
}
output
}
pub fn format_diagnostic(&self, diag: &X86Diagnostic) -> String {
let mut out = String::new();
if let Some(ref file) = diag.file {
out.push_str(&format!("{}:{}:{}: ", file, diag.line, diag.column));
} else if diag.line > 0 {
out.push_str(&format!("<input>:{}:{}: ", diag.line, diag.column));
}
out.push_str(&format!("{}: ", diag.severity));
out.push_str(&diag.message);
if let Some(ref snippet) = diag.code_snippet {
out.push('\n');
for line in snippet.lines() {
out.push_str(&format!(" {}\n", line));
}
}
for note in &diag.notes {
out.push_str(&format!("\n note: {}", note.message));
if let Some(ref file) = note.file {
out.push_str(&format!(" at {}:{}:{}", file, note.line, note.column));
}
}
for fixit in &diag.fixit_hints {
out.push_str(&format!(
"\n fixit: {}:{}:{}-{}:{}: \"{}\" ({})",
fixit.file,
fixit.start_line,
fixit.start_col,
fixit.end_line,
fixit.end_col,
fixit.replacement,
fixit.description
));
}
out
}
pub fn print_all(&self) {
println!("{}", self.format_all());
}
pub fn error_count(&self) -> usize {
self.diagnostics
.iter()
.filter(|d| d.severity >= X86DiagSeverity::Error)
.count()
}
pub fn warning_count(&self) -> usize {
self.diagnostics
.iter()
.filter(|d| d.severity == X86DiagSeverity::Warning)
.count()
}
pub fn has_fatal(&self) -> bool {
self.diagnostics
.iter()
.any(|d| d.severity == X86DiagSeverity::Fatal)
}
pub fn has_no_errors(&self) -> bool {
self.error_count() == 0
}
pub fn clear(&mut self) {
self.diagnostics.clear();
self.template_backtrace.clear();
self.overload_notes.clear();
}
pub fn summary(&self) -> String {
let errors = self.error_count();
let warnings = self.warning_count();
if errors > 0 && warnings > 0 {
format!("{} error(s), {} warning(s) generated.", errors, warnings)
} else if errors > 0 {
format!("{} error(s) generated.", errors)
} else if warnings > 0 {
format!("{} warning(s) generated.", warnings)
} else {
"no errors or warnings.".to_string()
}
}
pub fn should_fail(&self) -> bool {
self.error_count() > 0 || self.has_fatal()
}
pub fn suppress_category(&mut self, category: X86DiagCategory) {
self.suppressed_categories.insert(category);
}
pub fn enable_only(&mut self, categories: HashSet<X86DiagCategory>) {
self.enabled_only = Some(categories);
}
pub fn set_error_limit(&mut self, limit: usize) {
self.error_limit = limit;
}
pub fn add_source_file(&mut self, name: impl Into<String>, content: impl Into<String>) {
self.source_files.insert(
name.into(),
content.into().lines().map(|l| l.to_string()).collect(),
);
}
pub fn suggest_const(&mut self, var_name: &str, file: &str, line: u32, col: u32) {
if !self.generate_fixits {
return;
}
self.add_fixit(X86FixIt {
file: file.to_string(),
start_line: line,
start_col: col,
end_line: line,
end_col: col,
replacement: format!("const {}", var_name),
description: "add 'const' qualifier".to_string(),
});
}
pub fn warn_deprecated(
&mut self,
name: &str,
replacement: Option<&str>,
file: Option<impl Into<String>>,
line: u32,
col: u32,
) {
let msg = if let Some(rep) = replacement {
format!("'{}' is deprecated; use '{}' instead", name, rep)
} else {
format!("'{}' is deprecated", name)
};
self.warning_at(msg, file, line, col);
}
pub fn warn_unreachable(&mut self, file: Option<impl Into<String>>, line: u32, col: u32) {
self.warning_at("unreachable code", file, line, col);
}
pub fn warn_unused_variable(
&mut self,
name: &str,
file: Option<impl Into<String>>,
line: u32,
col: u32,
) {
self.warning_at(format!("unused variable '{}'", name), file, line, col);
}
pub fn warn_unused_parameter(
&mut self,
name: &str,
file: Option<impl Into<String>>,
line: u32,
col: u32,
) {
self.warning_at(format!("unused parameter '{}'", name), file, line, col);
}
pub fn warn_missing_return(
&mut self,
func_name: &str,
file: Option<impl Into<String>>,
line: u32,
col: u32,
) {
self.warning_at(
format!(
"non-void function '{}' may exit without returning a value",
func_name
),
file,
line,
col,
);
}
pub fn warn_division_by_zero(&mut self, file: Option<impl Into<String>>, line: u32, col: u32) {
self.warning_at("division by zero is undefined", file, line, col);
}
pub fn warn_implicit_conversion(
&mut self,
from: &str,
to: &str,
file: Option<impl Into<String>>,
line: u32,
col: u32,
) {
self.warning_at(
format!(
"implicit conversion from '{}' to '{}' may lose data or change sign",
from, to
),
file,
line,
col,
);
}
pub fn note_conflicting_type(
&mut self,
expected: &str,
actual: &str,
file: Option<impl Into<String>>,
line: u32,
col: u32,
) {
let note = X86DiagNote {
message: format!(
"conflicting types: expected '{}', got '{}'",
expected, actual
),
file: file.map(|f| f.into()),
line,
column: col,
};
if let Some(diag) = self.diagnostics.last_mut() {
diag.notes.push(note);
}
}
pub fn explain_type_difference(
&mut self,
name: &str,
type_a: &str,
type_b: &str,
loc_a: (Option<String>, u32, u32),
loc_b: (Option<String>, u32, u32),
) {
self.error_at(
format!("redefinition of '{}' with a different type", name),
loc_a.0.clone(),
loc_a.1,
loc_a.2,
);
self.note_conflicting_type(type_a, type_b, loc_b.0, loc_b.1, loc_b.2);
}
}
impl Default for X86DiagnosticEmitter {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub enum X86ScopeEntry {
Variable {
name: String,
ty: QualType,
is_extern: bool,
is_static: bool,
declared_line: u32,
declared_col: u32,
declared_file: Option<String>,
},
Function {
name: String,
ret_ty: QualType,
params: Vec<QualType>,
is_vararg: bool,
is_inline: bool,
linkage: String,
declared_line: u32,
declared_col: u32,
declared_file: Option<String>,
},
Typedef {
name: String,
underlying: QualType,
},
StructTag {
name: String,
is_union: bool,
},
EnumTag {
name: String,
},
EnumConstant {
name: String,
value: i64,
},
Label {
name: String,
defined: bool,
},
Namespace {
name: String,
},
TemplateParam {
name: String,
kind: String, },
ClassMember {
name: String,
ty: QualType,
access: String,
},
}
impl X86ScopeEntry {
pub fn name(&self) -> &str {
match self {
X86ScopeEntry::Variable { name, .. } => name,
X86ScopeEntry::Function { name, .. } => name,
X86ScopeEntry::Typedef { name, .. } => name,
X86ScopeEntry::StructTag { name, .. } => name,
X86ScopeEntry::EnumTag { name, .. } => name,
X86ScopeEntry::EnumConstant { name, .. } => name,
X86ScopeEntry::Label { name, .. } => name,
X86ScopeEntry::Namespace { name, .. } => name,
X86ScopeEntry::TemplateParam { name, .. } => name,
X86ScopeEntry::ClassMember { name, .. } => name,
}
}
pub fn is_function(&self) -> bool {
matches!(self, X86ScopeEntry::Function { .. })
}
pub fn is_variable(&self) -> bool {
matches!(self, X86ScopeEntry::Variable { .. })
}
pub fn is_type(&self) -> bool {
matches!(
self,
X86ScopeEntry::Typedef { .. }
| X86ScopeEntry::StructTag { .. }
| X86ScopeEntry::EnumTag { .. }
)
}
}
#[derive(Debug, Clone)]
pub struct X86Scope {
pub kind: X86ScopeKind,
pub entries: HashMap<String, X86ScopeEntry>,
pub labels: HashMap<String, bool>,
pub has_return_stmt: bool,
pub is_closed: bool,
pub parent_namespace: Option<String>,
}
impl X86Scope {
pub fn new(kind: X86ScopeKind) -> Self {
Self {
kind,
entries: HashMap::new(),
labels: HashMap::new(),
has_return_stmt: false,
is_closed: false,
parent_namespace: None,
}
}
pub fn insert(&mut self, entry: X86ScopeEntry) -> Option<X86ScopeEntry> {
self.entries.insert(entry.name().to_string(), entry)
}
pub fn lookup(&self, name: &str) -> Option<&X86ScopeEntry> {
self.entries.get(name)
}
pub fn define_label(&mut self, name: &str) {
self.labels.insert(name.to_string(), true);
}
pub fn has_label(&self, name: &str) -> bool {
self.labels.get(name).copied().unwrap_or(false)
}
pub fn mark_returned(&mut self) {
self.has_return_stmt = true;
}
}
#[derive(Debug, Clone)]
pub struct X86ScopeManager {
pub scopes: Vec<X86Scope>,
pub file_scope: HashMap<String, Vec<X86ScopeEntry>>,
pub namespaces: HashMap<String, HashMap<String, X86ScopeEntry>>,
pub current_function: Option<String>,
pub current_return_type: Option<QualType>,
pub loop_depth: u32,
pub switch_depth: u32,
pub warn_shadow: bool,
pub enforce_odr: bool,
pub odr_functions: HashMap<String, Vec<(Option<String>, u32, u32)>>,
pub odr_variables: HashMap<String, Vec<(Option<String>, u32, u32)>>,
pub label_scopes: Vec<HashMap<String, bool>>,
pub in_template_params: bool,
pub current_namespace: Vec<String>,
}
impl X86ScopeManager {
pub fn new() -> Self {
Self {
scopes: vec![X86Scope::new(X86ScopeKind::File)],
file_scope: HashMap::new(),
namespaces: HashMap::new(),
current_function: None,
current_return_type: None,
loop_depth: 0,
switch_depth: 0,
warn_shadow: true,
enforce_odr: true,
odr_functions: HashMap::new(),
odr_variables: HashMap::new(),
label_scopes: vec![HashMap::new()],
in_template_params: false,
current_namespace: Vec::new(),
}
}
pub fn enter_scope(&mut self, kind: X86ScopeKind) {
let mut scope = X86Scope::new(kind);
if kind == X86ScopeKind::Namespace || kind == X86ScopeKind::Class {
scope.parent_namespace = Some(self.current_namespace.join("::"));
}
self.scopes.push(scope);
if kind == X86ScopeKind::Function || kind == X86ScopeKind::Block {
self.label_scopes.push(HashMap::new());
}
}
pub fn leave_scope(&mut self) {
if self.scopes.len() > 1 {
let closed = self.scopes.pop().unwrap();
if closed.kind == X86ScopeKind::Function || closed.kind == X86ScopeKind::Block {
self.label_scopes.pop();
}
}
}
pub fn current_scope(&self) -> &X86Scope {
self.scopes.last().expect("scope stack is empty")
}
pub fn current_scope_mut(&mut self) -> &mut X86Scope {
self.scopes.last_mut().expect("scope stack is empty")
}
pub fn scope_depth(&self) -> usize {
self.scopes.len()
}
pub fn at_file_scope(&self) -> bool {
self.scopes.len() == 1 && self.scopes[0].kind == X86ScopeKind::File
}
pub fn in_function(&self) -> bool {
self.current_function.is_some()
}
pub fn nearest_scope_of_kind(&self, kind: X86ScopeKind) -> Option<&X86Scope> {
self.scopes.iter().rev().find(|s| s.kind == kind)
}
pub fn in_scope_kind(&self, kind: X86ScopeKind) -> bool {
self.nearest_scope_of_kind(kind).is_some()
}
pub fn declare(&mut self, entry: X86ScopeEntry, diag: &mut X86DiagnosticEmitter) -> bool {
let name = entry.name().to_string();
if self.warn_shadow {
for scope in self.scopes.iter().rev().skip(1) {
if let Some(_existing) = scope.lookup(&name) {
diag.warning_at(
format!("declaration of '{}' shadows a previous declaration", name),
None::<String>,
0,
0,
);
break;
}
}
}
if let Some(existing) = self.current_scope().lookup(&name) {
if self.can_merge_redeclaration(existing, &entry) {
self.current_scope_mut().insert(entry);
return true;
} else {
diag.error_at(
format!("redefinition of '{}' in the same scope", name),
None::<String>,
0,
0,
);
return false;
}
}
if self.enforce_odr && self.at_file_scope() {
match &entry {
X86ScopeEntry::Function {
name, is_inline, ..
} if !is_inline => {
if let Some(locations) = self.odr_functions.get(name) {
if !locations.is_empty() {
diag.error_at(
format!("duplicate definition of function '{}' violates ODR", name),
None::<String>,
0,
0,
);
return false;
}
}
self.odr_functions
.entry(name.clone())
.or_default()
.push((None, 0, 0));
}
X86ScopeEntry::Variable {
name,
is_extern,
is_static,
..
} => {
if !is_extern && !*is_static {
self.odr_variables
.entry(name.clone())
.or_default()
.push((None, 0, 0));
}
}
_ => {}
}
}
self.current_scope_mut().insert(entry);
true
}
fn can_merge_redeclaration(&self, existing: &X86ScopeEntry, new_entry: &X86ScopeEntry) -> bool {
match (existing, new_entry) {
(
X86ScopeEntry::Variable {
is_extern: ext_a, ..
},
X86ScopeEntry::Variable {
is_extern: ext_b, ..
},
) => {
*ext_a || *ext_b
}
(X86ScopeEntry::Function { .. }, X86ScopeEntry::Function { .. }) => {
true
}
_ => false,
}
}
pub fn declare_variable(
&mut self,
name: &str,
ty: QualType,
is_extern: bool,
is_static: bool,
diag: &mut X86DiagnosticEmitter,
) -> bool {
self.declare(
X86ScopeEntry::Variable {
name: name.to_string(),
ty,
is_extern,
is_static,
declared_line: 0,
declared_col: 0,
declared_file: None,
},
diag,
)
}
pub fn declare_function(
&mut self,
name: &str,
ret_ty: QualType,
params: Vec<QualType>,
is_vararg: bool,
is_inline: bool,
linkage: &str,
diag: &mut X86DiagnosticEmitter,
) -> bool {
self.declare(
X86ScopeEntry::Function {
name: name.to_string(),
ret_ty,
params,
is_vararg,
is_inline,
linkage: linkage.to_string(),
declared_line: 0,
declared_col: 0,
declared_file: None,
},
diag,
)
}
pub fn declare_typedef(
&mut self,
name: &str,
underlying: QualType,
diag: &mut X86DiagnosticEmitter,
) -> bool {
self.declare(
X86ScopeEntry::Typedef {
name: name.to_string(),
underlying,
},
diag,
)
}
pub fn declare_struct_tag(
&mut self,
name: &str,
is_union: bool,
diag: &mut X86DiagnosticEmitter,
) -> bool {
self.declare(
X86ScopeEntry::StructTag {
name: name.to_string(),
is_union,
},
diag,
)
}
pub fn declare_enum_tag(&mut self, name: &str, diag: &mut X86DiagnosticEmitter) -> bool {
self.declare(
X86ScopeEntry::EnumTag {
name: name.to_string(),
},
diag,
)
}
pub fn declare_enum_constant(
&mut self,
name: &str,
value: i64,
diag: &mut X86DiagnosticEmitter,
) -> bool {
self.declare(
X86ScopeEntry::EnumConstant {
name: name.to_string(),
value,
},
diag,
)
}
pub fn lookup(&self, name: &str) -> Option<&X86ScopeEntry> {
for scope in self.scopes.iter().rev() {
if let Some(entry) = scope.lookup(name) {
return Some(entry);
}
}
None
}
pub fn lookup_file_scope(&self, name: &str) -> Option<&X86ScopeEntry> {
for scope in self.scopes.iter() {
if scope.kind == X86ScopeKind::File {
if let Some(entry) = scope.lookup(name) {
return Some(entry);
}
}
}
None
}
pub fn lookup_in_namespace(&self, namespace: &str, name: &str) -> Option<&X86ScopeEntry> {
self.namespaces.get(namespace)?.get(name)
}
pub fn qualified_lookup(&self, qualifier: &str, name: &str) -> Option<&X86ScopeEntry> {
self.lookup_in_namespace(qualifier, name)
}
pub fn lookup_variable(&self, name: &str) -> Option<&X86ScopeEntry> {
self.lookup(name).filter(|e| e.is_variable())
}
pub fn lookup_function(&self, name: &str) -> Option<&X86ScopeEntry> {
self.lookup(name).filter(|e| e.is_function())
}
pub fn lookup_type(&self, name: &str) -> Option<&X86ScopeEntry> {
self.lookup(name).filter(|e| e.is_type())
}
pub fn find_label(&self, name: &str) -> bool {
self.label_scopes
.iter()
.rev()
.any(|scope| scope.get(name).copied().unwrap_or(false))
}
pub fn define_label(&mut self, name: &str) {
if let Some(scope) = self.label_scopes.last_mut() {
scope.insert(name.to_string(), true);
}
self.current_scope_mut().define_label(name);
}
pub fn enter_loop(&mut self) {
self.loop_depth += 1;
}
pub fn leave_loop(&mut self) {
if self.loop_depth > 0 {
self.loop_depth -= 1;
}
}
pub fn in_loop(&self) -> bool {
self.loop_depth > 0
}
pub fn enter_switch(&mut self) {
self.switch_depth += 1;
}
pub fn leave_switch(&mut self) {
if self.switch_depth > 0 {
self.switch_depth -= 1;
}
}
pub fn in_switch(&self) -> bool {
self.switch_depth > 0
}
pub fn enter_function(&mut self, name: &str, ret_ty: QualType) {
self.current_function = Some(name.to_string());
self.current_return_type = Some(ret_ty);
}
pub fn leave_function(&mut self) {
self.current_function = None;
self.current_return_type = None;
}
pub fn register_namespace(&mut self, name: &str) {
self.namespaces.entry(name.to_string()).or_default();
}
pub fn declare_in_namespace(&mut self, ns: &str, entry: X86ScopeEntry) {
self.namespaces
.entry(ns.to_string())
.or_default()
.insert(entry.name().to_string(), entry);
}
pub fn enter_namespace(&mut self, name: &str) {
self.current_namespace.push(name.to_string());
self.enter_scope(X86ScopeKind::Namespace);
}
pub fn leave_namespace(&mut self) {
self.current_namespace.pop();
self.leave_scope();
}
pub fn current_namespace_string(&self) -> String {
if self.current_namespace.is_empty() {
"".to_string()
} else {
self.current_namespace.join("::")
}
}
pub fn dump_scopes(&self) -> String {
let mut out = String::new();
for (i, scope) in self.scopes.iter().enumerate() {
out.push_str(&format!(
"Scope {} ({:?}) [{} entries]:\n",
i,
scope.kind,
scope.entries.len()
));
for (name, entry) in &scope.entries {
out.push_str(&format!(
" {}: {:?}\n",
name,
std::mem::discriminant(entry)
));
}
}
out
}
pub fn total_declarations(&self) -> usize {
self.scopes.iter().map(|s| s.entries.len()).sum()
}
pub fn file_scope_names(&self) -> Vec<String> {
self.scopes
.iter()
.filter(|s| s.kind == X86ScopeKind::File)
.flat_map(|s| s.entries.keys().cloned())
.collect()
}
}
impl Default for X86ScopeManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86DeclValidator {
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub tentative_defs: HashMap<String, QualType>,
pub known_decls: HashMap<String, Vec<X86DeclRecord>>,
pub standard: String,
pub target: String,
pub is_64bit: bool,
}
#[derive(Debug, Clone)]
pub struct X86DeclRecord {
pub name: String,
pub category: X86DeclCategory,
pub ty: QualType, pub file: Option<String>,
pub line: u32,
pub column: u32,
pub is_definition: bool,
pub linkage: String,
}
impl X86DeclValidator {
pub fn new(standard: &str, target: &str) -> Self {
let is_64bit =
target.contains("64") || target.contains("x86_64") || target.contains("amd64");
Self {
errors: Vec::new(),
warnings: Vec::new(),
tentative_defs: HashMap::new(),
known_decls: HashMap::new(),
standard: standard.to_string(),
target: target.to_string(),
is_64bit,
}
}
pub fn check_var_decl(&mut self, decl: &VarDecl, diag: &mut X86DiagnosticEmitter) -> bool {
let mut valid = true;
if decl.ty.is_void() {
diag.error_at(
format!("variable '{}' has incomplete type 'void'", decl.name),
None::<String>,
0,
0,
);
valid = false;
}
if let Some(ref init) = decl.init {
if !self.validate_initializer_type(&decl.ty, init, diag) {
valid = false;
}
}
if decl.is_extern && decl.init.is_some() {
diag.warning_at(
format!("extern variable '{}' has an initializer", decl.name),
None::<String>,
0,
0,
);
}
let record = X86DeclRecord {
name: decl.name.clone(),
category: X86DeclCategory::Variable,
ty: decl.ty.clone(),
file: None,
line: 0,
column: 0,
is_definition: decl.init.is_some(),
linkage: if decl.is_extern {
"extern".into()
} else if decl.is_static {
"static".into()
} else {
"external".into()
},
};
valid &= self.check_redeclaration(&record, diag);
if decl.init.is_none() && !decl.is_extern {
self.tentative_defs
.insert(decl.name.clone(), decl.ty.clone());
}
self.known_decls
.entry(decl.name.clone())
.or_default()
.push(record);
valid
}
pub fn check_function_decl(
&mut self,
func: &FunctionDecl,
diag: &mut X86DiagnosticEmitter,
) -> bool {
let mut valid = true;
if !self.is_valid_return_type(&func.ret_ty, diag) {
valid = false;
}
for param in &func.params {
if param.ty.is_void() && func.params.len() > 1 {
diag.error_at(
"void must be the only parameter in a function prototype",
None::<String>,
0,
0,
);
valid = false;
}
if !self.is_valid_parameter_type(¶m.ty, diag) {
valid = false;
}
}
if func.body.is_none() && func.linkage.as_str() != "external" {
diag.warning_at(
format!("function '{}' declared but never defined", func.name),
None::<String>,
0,
0,
);
}
if func.is_noreturn && !func.ret_ty.is_void() {
}
let record = X86DeclRecord {
name: func.name.clone(),
category: X86DeclCategory::Function,
ty: func.ret_ty.clone(),
file: None,
line: 0,
column: 0,
is_definition: func.body.is_some(),
linkage: func.linkage.as_str().to_string(),
};
valid &= self.check_redeclaration(&record, diag);
self.known_decls
.entry(func.name.clone())
.or_default()
.push(record);
valid
}
pub fn check_struct_decl(
&mut self,
decl: &StructDecl,
diag: &mut X86DiagnosticEmitter,
) -> bool {
let mut valid = true;
let mut field_names = HashSet::new();
for field in &decl.fields {
if !field_names.insert(field.name.clone()) {
diag.error_at(
format!(
"duplicate member '{}' in {}",
field.name,
if decl.is_union { "union" } else { "struct" }
),
None::<String>,
0,
0,
);
valid = false;
}
if let Some(bit_width) = field.bit_width {
if bit_width == 0 {
if !field.name.is_empty() {
diag.error_at(
"named zero-width bit-field is not allowed",
None::<String>,
0,
0,
);
valid = false;
}
}
if bit_width > 128 {
diag.warning_at(
format!("bit-field width {} exceeds maximum 128 on x86", bit_width),
None::<String>,
0,
0,
);
}
}
if field.ty.is_void() {
diag.error_at(
format!("field '{}' has incomplete type 'void'", field.name),
None::<String>,
0,
0,
);
valid = false;
}
}
if self.is_64bit && decl.fields.len() > 32 {
}
let record = X86DeclRecord {
name: decl.name.clone().unwrap_or_else(|| "<anonymous>".into()),
category: if decl.is_union {
X86DeclCategory::Union
} else {
X86DeclCategory::Struct
},
ty: QualType::void(),
file: None,
line: 0,
column: 0,
is_definition: !decl.fields.is_empty(),
linkage: "external".into(),
};
self.known_decls
.entry(decl.name.clone().unwrap_or_else(|| "<anonymous>".into()))
.or_default()
.push(record);
valid
}
pub fn check_enum_decl(&mut self, decl: &EnumDecl, diag: &mut X86DiagnosticEmitter) -> bool {
let mut valid = true;
let mut variant_names = HashSet::new();
let mut last_value: i64 = -1;
for variant in &decl.variants {
if !variant_names.insert(variant.name.clone()) {
diag.error_at(
format!(
"duplicate enumerator '{}' in enum '{}'",
variant.name,
decl.name.as_deref().unwrap_or("<anonymous>")
),
None::<String>,
0,
0,
);
valid = false;
}
let value = variant.value.unwrap_or(last_value + 1);
if value > i32::MAX as i64 || value < i32::MIN as i64 {
diag.warning_at(
format!(
"enumerator value {} for '{}' exceeds range of 'int'",
value, variant.name
),
None::<String>,
0,
0,
);
}
if value != last_value + 1 && last_value != -1 {
}
last_value = value;
}
let record = X86DeclRecord {
name: decl.name.clone().unwrap_or_else(|| "<anonymous>".into()),
category: X86DeclCategory::Enum,
ty: QualType::int(),
file: None,
line: 0,
column: 0,
is_definition: true,
linkage: "external".into(),
};
self.known_decls
.entry(decl.name.clone().unwrap_or_else(|| "<anonymous>".into()))
.or_default()
.push(record);
valid
}
pub fn check_typedef(&mut self, decl: &TypedefDecl, _diag: &mut X86DiagnosticEmitter) -> bool {
let record = X86DeclRecord {
name: decl.name.clone(),
category: X86DeclCategory::Typedef,
ty: decl.underlying.clone(),
file: None,
line: 0,
column: 0,
is_definition: true,
linkage: "none".into(),
};
self.known_decls
.entry(decl.name.clone())
.or_default()
.push(record);
true
}
fn is_valid_return_type(&self, ty: &QualType, diag: &mut X86DiagnosticEmitter) -> bool {
if ty.is_array() {
diag.error("function cannot return array type");
return false;
}
if ty.is_function_type() {
diag.error("function cannot return function type");
return false;
}
true
}
fn is_valid_parameter_type(&self, ty: &QualType, _diag: &mut X86DiagnosticEmitter) -> bool {
if ty.is_array() {
return true;
}
if ty.is_function_type() {
return true;
}
true
}
fn validate_initializer_type(
&self,
var_ty: &QualType,
_init: &Expr,
diag: &mut X86DiagnosticEmitter,
) -> bool {
if var_ty.is_integer() || var_ty.is_floating() || var_ty.is_pointer() {
return true;
}
if var_ty.is_void() {
diag.error("cannot initialize void variable");
return false;
}
true
}
fn check_redeclaration(&self, record: &X86DeclRecord, diag: &mut X86DiagnosticEmitter) -> bool {
if let Some(previous_decls) = self.known_decls.get(&record.name) {
for prev in previous_decls {
if record.is_definition && prev.is_definition {
diag.error_at(
format!("redefinition of '{}'", record.name),
None::<String>,
record.line,
record.column,
);
diag.note_previous_declaration(
&record.name,
prev.file.as_ref().map(|s| s.as_str()),
prev.line,
prev.column,
);
return false;
}
if record.category == prev.category && !self.types_compatible(&record.ty, &prev.ty)
{
diag.explain_type_difference(
&record.name,
&self.type_to_string(&prev.ty),
&self.type_to_string(&record.ty),
(prev.file.clone(), prev.line, prev.column),
(record.file.clone(), record.line, record.column),
);
return false;
}
}
}
true
}
pub fn types_compatible(&self, a: &QualType, b: &QualType) -> bool {
let a_str = self.type_to_string(a);
let b_str = self.type_to_string(b);
a_str == b_str
}
pub fn type_to_string(&self, ty: &QualType) -> String {
ty.to_string()
}
pub fn finalize_tentative_defs(&mut self) -> Vec<(String, QualType)> {
let result: Vec<_> = self.tentative_defs.drain().collect();
result
}
pub fn check_call_args(
&self,
func_name: &str,
param_tys: &[QualType],
args: &[Expr],
is_vararg: bool,
diag: &mut X86DiagnosticEmitter,
) -> bool {
let mut valid = true;
if !is_vararg && args.len() > param_tys.len() {
diag.error_at(
format!(
"too many arguments to function '{}': expected {}, got {}",
func_name,
param_tys.len(),
args.len()
),
None::<String>,
0,
0,
);
valid = false;
}
if args.len() < param_tys.len() {
diag.error_at(
format!(
"too few arguments to function '{}': expected {}, got {}",
func_name,
param_tys.len(),
args.len()
),
None::<String>,
0,
0,
);
valid = false;
}
valid
}
pub fn check_array_size(&self, size_expr: &Expr, diag: &mut X86DiagnosticEmitter) -> bool {
match size_expr {
Expr::IntLiteral(val) => {
if *val <= 0 {
diag.error("array size must be a positive integer");
return false;
}
if *val > i64::MAX {
diag.error("array size is too large");
return false;
}
if self.is_64bit && *val > 0x7FFFFFFF {
diag.warning("array size may exceed reasonable bounds for x86-64");
}
true
}
_ => {
if self.standard.contains("c++") {
diag.error("array size must be a constant expression in C++");
return false;
}
true
}
}
}
pub fn check_bitfield_width(
&self,
width: u32,
field_ty: &QualType,
diag: &mut X86DiagnosticEmitter,
) -> bool {
let type_width = self.type_width_bits(field_ty);
if width > type_width {
diag.error_at(
format!(
"bit-field width {} exceeds width of type ({} bits)",
width, type_width
),
None::<String>,
0,
0,
);
return false;
}
if width == 0 {
return true;
}
true
}
pub fn type_width_bits(&self, ty: &QualType) -> u32 {
if ty.is_char() {
return 8;
}
if ty.is_short() {
return 16;
}
if ty.is_int() {
return 32;
}
if ty.is_long() {
return if self.is_64bit { 64 } else { 32 };
}
if ty.is_long_long() {
return 64;
}
if ty.is_float() {
return 32;
}
if ty.is_double() {
return 64;
}
if ty.is_pointer() {
return if self.is_64bit { 64 } else { 32 };
}
32 }
pub fn is_complete_type(&self, ty: &QualType) -> bool {
!ty.is_void() && !(ty.is_array() && false) }
pub fn compute_linkage(&self, is_static: bool, _is_extern: bool) -> String {
if is_static {
"internal".into()
} else {
"external".into()
}
}
}
impl Default for X86DeclValidator {
fn default() -> Self {
Self::new("c17", "x86_64-unknown-linux-gnu")
}
}
#[derive(Debug, Clone)]
pub struct X86ExprValidator {
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub standard: String,
pub is_64bit: bool,
pub current_function_ret: Option<QualType>,
}
impl X86ExprValidator {
pub fn new(standard: &str, is_64bit: bool) -> Self {
Self {
errors: Vec::new(),
warnings: Vec::new(),
standard: standard.to_string(),
is_64bit,
current_function_ret: None,
}
}
pub fn expr_type(&self, expr: &Expr) -> QualType {
match expr {
Expr::IntLiteral(_) => QualType::int(),
Expr::UIntLiteral(..) => QualType::uint(),
Expr::FloatLiteral(_) => QualType::float_(),
Expr::DoubleLiteral(_) => QualType::double_(),
Expr::CharLiteral(_) => QualType::char(),
Expr::StringLiteral(_) => QualType::const_char_ptr(),
Expr::Ident(_) => QualType::int(), Expr::Unary(op, operand) => self.unary_expr_type(*op, operand),
Expr::Binary(op, lhs, rhs) => self.binary_expr_type(*op, lhs, rhs),
Expr::Call { callee: _, args: _ } => {
QualType::int()
}
Expr::Conditional(_, then_expr, _) => self.expr_type(then_expr),
Expr::Cast(ty, _) => ty.clone(),
Expr::SizeOf(_) => QualType::ulong(), Expr::SizeOfType(_) => QualType::ulong(),
Expr::AlignOf(_) => QualType::ulong(),
Expr::AlignOfType(_) => QualType::ulong(),
Expr::Subscript { base, index: _ } => {
let base_ty = self.expr_type(base);
if base_ty.is_pointer() {
base_ty.unqualified() } else {
QualType::int()
}
}
Expr::Member {
base,
field: _,
is_arrow: _,
} => self.expr_type(base),
Expr::Assign(_, lhs, _) => self.expr_type(lhs),
_ => QualType::int(),
}
}
pub fn is_lvalue(&self, expr: &Expr) -> bool {
matches!(
expr,
Expr::Ident(_) | Expr::Subscript { .. } | Expr::Member { .. } | Expr::Unary(..)
)
}
pub fn is_rvalue(&self, expr: &Expr) -> bool {
!self.is_lvalue(expr)
}
pub fn value_category(&self, expr: &Expr) -> X86ValueCategory {
if self.is_lvalue(expr) {
X86ValueCategory::LValue
} else {
X86ValueCategory::RValue
}
}
pub fn is_null_pointer_constant(&self, expr: &Expr) -> bool {
match expr {
Expr::IntLiteral(0) => true,
Expr::Cast(ty, expr) if ty.is_pointer() => {
matches!(expr.as_ref(), Expr::IntLiteral(0))
}
_ => false,
}
}
pub fn is_integer_constant_expr(&self, expr: &Expr) -> bool {
match expr {
Expr::IntLiteral(_) => true,
Expr::UIntLiteral(..) => true,
Expr::CharLiteral(_) => true,
Expr::Binary(op, lhs, rhs) => {
if op.is_assignment() || op.is_comparison() || op.is_logical() {
return false;
}
self.is_integer_constant_expr(lhs) && self.is_integer_constant_expr(rhs)
}
Expr::Unary(op, operand) => match op {
UnaryOp::Plus | UnaryOp::Minus | UnaryOp::BitNot => {
self.is_integer_constant_expr(operand)
}
_ => false,
},
Expr::Conditional(cond, then_expr, else_expr) => {
self.is_integer_constant_expr(cond)
&& self.is_integer_constant_expr(then_expr)
&& self.is_integer_constant_expr(else_expr)
}
Expr::SizeOf(_) | Expr::SizeOfType(_) | Expr::AlignOf(_) | Expr::AlignOfType(_) => true,
_ => false,
}
}
pub fn evaluate_integer_constant(&self, expr: &Expr) -> Option<i64> {
match expr {
Expr::IntLiteral(val) => Some(*val),
Expr::UIntLiteral(val, _) => {
if *val <= i64::MAX as u64 {
Some(*val as i64)
} else {
None
}
}
Expr::CharLiteral(c) => Some(*c as i64),
Expr::Binary(op, lhs, rhs) => {
let l = self.evaluate_integer_constant(lhs)?;
let r = self.evaluate_integer_constant(rhs)?;
match op {
BinaryOp::Add => l.checked_add(r),
BinaryOp::Sub => l.checked_sub(r),
BinaryOp::Mul => l.checked_mul(r),
BinaryOp::Div => {
if r == 0 {
None
} else {
l.checked_div(r)
}
}
BinaryOp::Mod => {
if r == 0 {
None
} else {
l.checked_rem(r)
}
}
BinaryOp::And => Some(l & r),
BinaryOp::Or => Some(l | r),
BinaryOp::Xor => Some(l ^ r),
BinaryOp::Shl => {
if r >= 0 && r < 64 {
l.checked_shl(r as u32)
} else {
None
}
}
BinaryOp::Shr => {
if r >= 0 && r < 64 {
Some(l >> (r as u32))
} else {
None
}
}
BinaryOp::Eq => Some(if l == r { 1 } else { 0 }),
BinaryOp::Ne => Some(if l != r { 1 } else { 0 }),
BinaryOp::Lt => Some(if l < r { 1 } else { 0 }),
BinaryOp::Gt => Some(if l > r { 1 } else { 0 }),
BinaryOp::Le => Some(if l <= r { 1 } else { 0 }),
BinaryOp::Ge => Some(if l >= r { 1 } else { 0 }),
BinaryOp::LogicAnd => Some(if l != 0 && r != 0 { 1 } else { 0 }),
BinaryOp::LogicOr => Some(if l != 0 || r != 0 { 1 } else { 0 }),
_ => None,
}
}
Expr::Unary(op, operand) => {
let val = self.evaluate_integer_constant(operand)?;
match op {
UnaryOp::Plus => Some(val),
UnaryOp::Minus => val.checked_neg(),
UnaryOp::BitNot => Some(!val),
UnaryOp::Not => Some(if val == 0 { 1 } else { 0 }),
_ => None,
}
}
Expr::Conditional(cond, then_expr, else_expr) => {
let c = self.evaluate_integer_constant(cond)?;
if c != 0 {
self.evaluate_integer_constant(then_expr)
} else {
self.evaluate_integer_constant(else_expr)
}
}
Expr::Cast(_ty, expr) => {
self.evaluate_integer_constant(expr)
}
_ => None,
}
}
pub fn check_condition(&self, expr: &Expr, diag: &mut X86DiagnosticEmitter) -> bool {
let ty = self.expr_type(expr);
if !ty.is_integer() && !ty.is_floating() && !ty.is_pointer() {
diag.error("condition expression must have scalar type");
return false;
}
true
}
pub fn check_binary_op(
&self,
op: BinaryOp,
lhs: &Expr,
rhs: &Expr,
diag: &mut X86DiagnosticEmitter,
) -> bool {
let lhs_ty = self.expr_type(lhs);
let rhs_ty = self.expr_type(rhs);
match op {
BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div => {
if !lhs_ty.is_arithmetic() && !lhs_ty.is_pointer() {
diag.error(
"left operand of arithmetic operator must have arithmetic or pointer type",
);
return false;
}
if !rhs_ty.is_arithmetic() && !rhs_ty.is_pointer() {
diag.error(
"right operand of arithmetic operator must have arithmetic or pointer type",
);
return false;
}
if lhs_ty.is_pointer() && rhs_ty.is_pointer() && op != BinaryOp::Sub {
diag.error("cannot add two pointers");
return false;
}
if op == BinaryOp::Mod && (!lhs_ty.is_integer() || !rhs_ty.is_integer()) {
diag.error("modulo operator requires integer operands");
return false;
}
}
BinaryOp::Mod => {
if !lhs_ty.is_integer() || !rhs_ty.is_integer() {
diag.error("modulo operator requires integer operands");
return false;
}
}
BinaryOp::And | BinaryOp::Or | BinaryOp::Xor | BinaryOp::Shl | BinaryOp::Shr => {
if !lhs_ty.is_integer() {
diag.error("bitwise operator requires integer operands");
return false;
}
}
BinaryOp::Eq
| BinaryOp::Ne
| BinaryOp::Lt
| BinaryOp::Gt
| BinaryOp::Le
| BinaryOp::Ge => {
if !lhs_ty.is_arithmetic() && !lhs_ty.is_pointer() {
diag.error("comparison operator requires arithmetic or pointer operands");
return false;
}
}
BinaryOp::LogicAnd | BinaryOp::LogicOr => {
if !lhs_ty.is_scalar() {
diag.error("logical operator requires scalar operands");
return false;
}
}
BinaryOp::Comma => {}
_ => {}
}
true
}
fn unary_expr_type(&self, op: UnaryOp, operand: &Expr) -> QualType {
let inner_ty = self.expr_type(operand);
match op {
UnaryOp::Plus | UnaryOp::Minus | UnaryOp::BitNot => {
self.x86_integer_promotion(&inner_ty)
}
UnaryOp::Not => QualType::int(), UnaryOp::AddrOf => QualType::pointer_to(inner_ty),
UnaryOp::Deref => {
if inner_ty.is_pointer() {
inner_ty.unqualified()
} else {
QualType::int()
}
}
}
}
fn binary_expr_type(&self, op: BinaryOp, lhs: &Expr, rhs: &Expr) -> QualType {
let lhs_ty = self.expr_type(lhs);
let rhs_ty = self.expr_type(rhs);
match op {
BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod => {
if lhs_ty.is_pointer() && rhs_ty.is_integer() {
return lhs_ty.clone();
}
if lhs_ty.is_integer() && rhs_ty.is_pointer() && op == BinaryOp::Add {
return rhs_ty.clone();
}
self.x86_usual_arithmetic_conversion(&lhs_ty, &rhs_ty)
}
BinaryOp::And | BinaryOp::Or | BinaryOp::Xor | BinaryOp::Shl | BinaryOp::Shr => {
self.x86_integer_promotion(&lhs_ty)
}
BinaryOp::Eq
| BinaryOp::Ne
| BinaryOp::Lt
| BinaryOp::Gt
| BinaryOp::Le
| BinaryOp::Ge
| BinaryOp::LogicAnd
| BinaryOp::LogicOr => {
QualType::int() }
BinaryOp::Comma => rhs_ty,
_ => lhs_ty,
}
}
pub fn x86_integer_promotion(&self, ty: &QualType) -> QualType {
if ty.is_char() || ty.is_short() {
return QualType::int();
}
if ty.is_int() || ty.is_long() || ty.is_long_long() {
return ty.clone();
}
if ty.is_integer() {
return QualType::int();
}
ty.clone()
}
pub fn x86_usual_arithmetic_conversion(&self, a: &QualType, b: &QualType) -> QualType {
if a.is_double() && b.is_double() {
return QualType::double_();
}
if a.is_double() || b.is_double() {
return QualType::double_();
}
if a.is_float() || b.is_float() {
return QualType::float_();
}
let a_promoted = self.x86_integer_promotion(a);
let b_promoted = self.x86_integer_promotion(b);
if a_promoted.to_string() == b_promoted.to_string() {
return a_promoted;
}
if self.is_64bit {
if a.is_long() || b.is_long() {
return QualType::long();
}
if a.is_long_long() || b.is_long_long() {
return QualType::long_long();
}
}
QualType::int()
}
pub fn const_fold(&self, expr: &Expr) -> Option<i64> {
self.evaluate_integer_constant(expr)
}
}
impl Default for X86ExprValidator {
fn default() -> Self {
Self::new("c17", true)
}
}
#[derive(Debug, Clone)]
pub struct X86TypePromotion {
pub is_64bit: bool,
pub int_width: u32,
pub long_width: u32,
pub long_long_width: u32,
pub pointer_width: u32,
pub float_width: u32,
pub double_width: u32,
pub long_double_width: u32,
}
impl X86TypePromotion {
pub fn new_lp64() -> Self {
Self {
is_64bit: true,
int_width: 32,
long_width: 64,
long_long_width: 64,
pointer_width: 64,
float_width: 32,
double_width: 64,
long_double_width: 128,
}
}
pub fn new_ilp32() -> Self {
Self {
is_64bit: false,
int_width: 32,
long_width: 32,
long_long_width: 64,
pointer_width: 32,
float_width: 32,
double_width: 64,
long_double_width: 96,
}
}
pub fn integer_promotion(&self, ty: &QualType) -> QualType {
if ty.is_bool() {
return QualType::int();
}
if ty.is_char() || ty.is_short() {
return QualType::int();
}
ty.clone()
}
pub fn usual_arithmetic_conversions(&self, a: &QualType, b: &QualType) -> QualType {
if a.is_long_double() || b.is_long_double() {
return QualType::long_double();
}
if a.is_double() || b.is_double() {
return QualType::double_();
}
if a.is_float() || b.is_float() {
return QualType::float_();
}
let a_promoted = self.integer_promotion(a);
let b_promoted = self.integer_promotion(b);
if self.same_type(&a_promoted, &b_promoted) {
return a_promoted;
}
if self.is_signed_integer(&a_promoted) == self.is_signed_integer(&b_promoted) {
return if self.rank(&a_promoted) >= self.rank(&b_promoted) {
a_promoted
} else {
b_promoted
};
}
if !self.is_signed_integer(&a_promoted) && self.rank(&a_promoted) >= self.rank(&b_promoted)
{
return a_promoted;
}
if !self.is_signed_integer(&b_promoted) && self.rank(&b_promoted) >= self.rank(&a_promoted)
{
return b_promoted;
}
if self.is_signed_integer(&a_promoted) {
self.to_unsigned(&a_promoted)
} else {
self.to_unsigned(&b_promoted)
}
}
pub fn default_argument_promotion(&self, ty: &QualType) -> QualType {
if ty.is_float() {
return QualType::double_();
}
if ty.is_integer() {
return self.integer_promotion(ty);
}
ty.clone()
}
pub fn rank(&self, ty: &QualType) -> u32 {
if ty.is_bool() {
return 0;
}
if ty.is_char() {
return 1;
}
if ty.is_short() {
return 2;
}
if ty.is_int() {
return 3;
}
if ty.is_long() {
return if self.is_64bit { 5 } else { 4 };
}
if ty.is_long_long() {
return if self.is_64bit { 5 } else { 5 };
}
0
}
pub fn width(&self, ty: &QualType) -> u32 {
if ty.is_bool() {
return 8;
}
if ty.is_char() {
return 8;
}
if ty.is_short() {
return 16;
}
if ty.is_int() {
return 32;
}
if ty.is_long() {
return self.long_width;
}
if ty.is_long_long() {
return self.long_long_width;
}
if ty.is_float() {
return self.float_width;
}
if ty.is_double() {
return self.double_width;
}
if ty.is_long_double() {
return self.long_double_width;
}
if ty.is_pointer() {
return self.pointer_width;
}
32
}
fn same_type(&self, a: &QualType, b: &QualType) -> bool {
a.to_string() == b.to_string()
}
fn is_signed_integer(&self, ty: &QualType) -> bool {
ty.is_integer() && !ty.is_unsigned()
}
fn to_unsigned(&self, ty: &QualType) -> QualType {
if ty.is_char() {
return QualType::uchar();
}
if ty.is_short() {
return QualType::ushort();
}
if ty.is_int() {
return QualType::uint();
}
if ty.is_long() {
return QualType::ulong();
}
if ty.is_long_long() {
return QualType::ulonglong();
}
ty.clone()
}
}
impl Default for X86TypePromotion {
fn default() -> Self {
Self::new_lp64()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ConvKind {
Identity,
LvalueToRvalue,
ArrayToPointerDecay,
FunctionToPointerDecay,
IntegralPromotion,
FloatingPromotion,
IntegralConversion,
FloatingConversion,
FloatingIntegralConversion,
PointerConversion,
PointerToBoolConversion,
NullPointerConversion,
NullMemberPointerConversion,
BooleanConversion,
QualificationConversion,
DerivedToBaseConversion,
UserDefinedConversion,
}
impl fmt::Display for X86ConvKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86ConvKind::Identity => write!(f, "identity"),
X86ConvKind::LvalueToRvalue => write!(f, "lvalue-to-rvalue"),
X86ConvKind::ArrayToPointerDecay => write!(f, "array-to-pointer"),
X86ConvKind::FunctionToPointerDecay => write!(f, "function-to-pointer"),
X86ConvKind::IntegralPromotion => write!(f, "integral promotion"),
X86ConvKind::FloatingPromotion => write!(f, "floating promotion"),
X86ConvKind::IntegralConversion => write!(f, "integral conversion"),
X86ConvKind::FloatingConversion => write!(f, "floating conversion"),
X86ConvKind::FloatingIntegralConversion => write!(f, "floating-integral conversion"),
X86ConvKind::PointerConversion => write!(f, "pointer conversion"),
X86ConvKind::PointerToBoolConversion => write!(f, "pointer-to-bool"),
X86ConvKind::NullPointerConversion => write!(f, "null pointer conversion"),
X86ConvKind::NullMemberPointerConversion => write!(f, "null member pointer conversion"),
X86ConvKind::BooleanConversion => write!(f, "boolean conversion"),
X86ConvKind::QualificationConversion => write!(f, "qualification conversion"),
X86ConvKind::DerivedToBaseConversion => write!(f, "derived-to-base"),
X86ConvKind::UserDefinedConversion => write!(f, "user-defined"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86ConversionResult {
pub valid: bool,
pub kind: X86ConvKind,
pub rank: X86ConversionRank,
pub target_type: Option<QualType>,
pub warning: Option<String>,
}
impl X86ConversionResult {
pub fn valid(kind: X86ConvKind, rank: X86ConversionRank) -> Self {
Self {
valid: true,
kind,
rank,
target_type: None,
warning: None,
}
}
pub fn invalid(kind: X86ConvKind) -> Self {
Self {
valid: false,
kind,
rank: X86ConversionRank::NotViable,
target_type: None,
warning: None,
}
}
pub fn with_target(mut self, ty: QualType) -> Self {
self.target_type = Some(ty);
self
}
pub fn with_warning(mut self, warn: String) -> Self {
self.warning = Some(warn);
self
}
pub fn is_exact_match(&self) -> bool {
self.rank == X86ConversionRank::ExactMatch
}
pub fn is_promotion(&self) -> bool {
self.rank == X86ConversionRank::Promotion
}
}
#[derive(Debug, Clone)]
pub struct X86ImplicitConversion {
pub promotion: X86TypePromotion,
pub allow_lossy_conversions: bool,
}
impl X86ImplicitConversion {
pub fn new(is_64bit: bool) -> Self {
Self {
promotion: if is_64bit {
X86TypePromotion::new_lp64()
} else {
X86TypePromotion::new_ilp32()
},
allow_lossy_conversions: false,
}
}
pub fn check_implicit_conversion(&self, from: &QualType, to: &QualType) -> X86ConversionResult {
if self.same_type(from, to) {
return X86ConversionResult::valid(
X86ConvKind::Identity,
X86ConversionRank::ExactMatch,
);
}
if from.is_array() && to.is_pointer() {
return X86ConversionResult::valid(
X86ConvKind::ArrayToPointerDecay,
X86ConversionRank::ExactMatch,
);
}
if from.is_function_type() && to.is_pointer() {
return X86ConversionResult::valid(
X86ConvKind::FunctionToPointerDecay,
X86ConversionRank::ExactMatch,
);
}
if from.is_integer() && to.is_integer() {
let promoted = self.promotion.integer_promotion(from);
if self.same_type(&promoted, to) {
return X86ConversionResult::valid(
X86ConvKind::IntegralPromotion,
X86ConversionRank::Promotion,
);
}
}
if from.is_float() && to.is_double() {
return X86ConversionResult::valid(
X86ConvKind::FloatingPromotion,
X86ConversionRank::Promotion,
);
}
if from.is_integer() && to.is_integer() {
let result = X86ConversionResult::valid(
X86ConvKind::IntegralConversion,
X86ConversionRank::Conversion,
);
let from_width = self.promotion.width(from);
let to_width = self.promotion.width(to);
if to_width < from_width {
return result.with_warning(format!(
"implicit conversion from {} to {} may lose data (narrowing from {} to {} bits)",
from, to, from_width, to_width
));
}
return result;
}
if from.is_floating() && to.is_integer() {
return X86ConversionResult::valid(
X86ConvKind::FloatingIntegralConversion,
X86ConversionRank::Conversion,
)
.with_warning("implicit conversion from floating-point to integer".into());
}
if from.is_integer() && to.is_floating() {
return X86ConversionResult::valid(
X86ConvKind::FloatingIntegralConversion,
X86ConversionRank::Conversion,
);
}
if from.is_floating() && to.is_floating() {
let from_width = self.promotion.width(from);
let to_width = self.promotion.width(to);
let rank = if to_width > from_width {
X86ConversionRank::Promotion
} else {
X86ConversionRank::Conversion
};
let result = X86ConversionResult::valid(X86ConvKind::FloatingConversion, rank);
if to_width < from_width {
return result.with_warning(format!(
"implicit floating-point conversion from {} to {} may lose precision",
from, to
));
}
return result;
}
if from.is_pointer() && to.is_pointer() {
if from.is_void_ptr() || to.is_void_ptr() {
return X86ConversionResult::valid(
X86ConvKind::PointerConversion,
X86ConversionRank::Conversion,
);
}
if self.same_pointer_base(from, to) {
return X86ConversionResult::valid(
X86ConvKind::PointerConversion,
X86ConversionRank::Conversion,
);
}
return X86ConversionResult::invalid(X86ConvKind::PointerConversion)
.with_warning(format!("incompatible pointer types: {} and {}", from, to));
}
if to.is_bool() && (from.is_integer() || from.is_floating() || from.is_pointer()) {
return X86ConversionResult::valid(
X86ConvKind::BooleanConversion,
X86ConversionRank::Conversion,
);
}
if from.is_integer() && to.is_pointer() {
let int_width = self.promotion.width(from);
let ptr_width = self.promotion.pointer_width;
if int_width == ptr_width {
return X86ConversionResult::valid(
X86ConvKind::IntegralConversion,
X86ConversionRank::Conversion,
)
.with_warning("implicit conversion from integer to pointer".into());
}
return X86ConversionResult::invalid(X86ConvKind::IntegralConversion);
}
if from.is_pointer() && to.is_integer() {
let ptr_width = self.promotion.pointer_width;
let int_width = self.promotion.width(to);
if int_width >= ptr_width {
return X86ConversionResult::valid(
X86ConvKind::IntegralConversion,
X86ConversionRank::Conversion,
)
.with_warning("implicit conversion from pointer to integer".into());
}
return X86ConversionResult::invalid(X86ConvKind::IntegralConversion);
}
X86ConversionResult::invalid(X86ConvKind::Identity)
}
pub fn conversion_sequence_rank(&self, from: &QualType, to: &QualType) -> X86ConversionRank {
self.check_implicit_conversion(from, to).rank
}
pub fn is_better_sequence(&self, rank_a: X86ConversionRank, rank_b: X86ConversionRank) -> bool {
rank_a < rank_b
}
fn same_type(&self, a: &QualType, b: &QualType) -> bool {
a.to_string() == b.to_string()
}
fn same_pointer_base(&self, a: &QualType, b: &QualType) -> bool {
if a.is_pointer() && b.is_pointer() {
let a_str = a.to_string();
let b_str = b.to_string();
a_str.replace("const ", "").replace("volatile ", "")
== b_str.replace("const ", "").replace("volatile ", "")
} else {
false
}
}
}
impl Default for X86ImplicitConversion {
fn default() -> Self {
Self::new(true)
}
}
#[derive(Debug, Clone)]
pub struct X86OverloadCandidate {
pub name: String,
pub param_types: Vec<QualType>,
pub return_type: QualType,
pub is_variadic: bool,
pub is_template: bool,
pub template_params: Vec<String>,
pub viability: X86ConversionRank,
pub conversion_sequences: Vec<X86ConversionResult>,
pub num_exact_matches: usize,
pub num_promotions: usize,
pub num_conversions: usize,
}
impl X86OverloadCandidate {
pub fn new(
name: &str,
param_types: Vec<QualType>,
return_type: QualType,
is_variadic: bool,
) -> Self {
Self {
name: name.to_string(),
param_types,
return_type,
is_variadic,
is_template: false,
template_params: Vec::new(),
viability: X86ConversionRank::ExactMatch,
conversion_sequences: Vec::new(),
num_exact_matches: 0,
num_promotions: 0,
num_conversions: 0,
}
}
pub fn with_template(mut self, params: Vec<String>) -> Self {
self.is_template = true;
self.template_params = params;
self
}
pub fn is_viable(&self) -> bool {
self.viability != X86ConversionRank::NotViable
}
}
#[derive(Debug, Clone)]
pub struct X86OverloadResolution {
pub implicit_conversion: X86ImplicitConversion,
pub candidates: Vec<X86OverloadCandidate>,
pub enable_sfinae: bool,
pub max_candidates: usize,
}
impl X86OverloadResolution {
pub fn new(is_64bit: bool) -> Self {
Self {
implicit_conversion: X86ImplicitConversion::new(is_64bit),
candidates: Vec::new(),
enable_sfinae: true,
max_candidates: 256,
}
}
pub fn add_candidate(&mut self, candidate: X86OverloadCandidate) {
if self.candidates.len() < self.max_candidates {
self.candidates.push(candidate);
}
}
pub fn add_builtin_candidate(
&mut self,
name: &str,
param_types: Vec<QualType>,
return_type: QualType,
) {
self.add_candidate(X86OverloadCandidate::new(
name,
param_types,
return_type,
false,
));
}
pub fn resolve(
&mut self,
arg_types: &[QualType],
diag: &mut X86DiagnosticEmitter,
) -> Option<usize> {
if self.candidates.is_empty() {
return None;
}
let mut viable: Vec<(usize, X86OverloadCandidate)> = Vec::new();
for (i, candidate) in self.candidates.iter().enumerate() {
let viability = self.check_viability(candidate, arg_types);
if viability.0 != X86ConversionRank::NotViable {
let mut c = candidate.clone();
c.viability = viability.0;
c.conversion_sequences = viability.1;
viable.push((i, c));
} else {
diag.add_overload_note(format!(
"candidate '{}' not viable: no known conversion",
candidate.name
));
}
}
if viable.is_empty() {
diag.error_at("no viable overloaded function found", None::<String>, 0, 0);
return None;
}
if viable.len() == 1 {
return Some(viable[0].0);
}
let mut best = 0;
let mut ambiguous = false;
for j in 1..viable.len() {
let cmp = self.compare_candidates(&viable[best].1, &viable[j].1);
match cmp {
std::cmp::Ordering::Less => {
}
std::cmp::Ordering::Greater => {
best = j;
ambiguous = false;
}
std::cmp::Ordering::Equal => {
ambiguous = true;
}
}
}
if ambiguous {
diag.error_at(
format!("call to '{}' is ambiguous", viable[best].1.name),
None::<String>,
0,
0,
);
for (_, c) in &viable {
diag.add_overload_note(format!(
"candidate: {}({})",
c.name,
c.param_types
.iter()
.map(|t| t.to_string())
.collect::<Vec<_>>()
.join(", ")
));
}
return None;
}
Some(viable[best].0)
}
fn check_viability(
&self,
candidate: &X86OverloadCandidate,
arg_types: &[QualType],
) -> (X86ConversionRank, Vec<X86ConversionResult>) {
let mut sequences = Vec::new();
let mut worst_rank = X86ConversionRank::ExactMatch;
let param_count = candidate.param_types.len();
let arg_count = arg_types.len();
if !candidate.is_variadic && arg_count > param_count {
return (X86ConversionRank::NotViable, sequences);
}
if arg_count < param_count {
return (X86ConversionRank::NotViable, sequences);
}
for i in 0..param_count.min(arg_count) {
let param_ty = &candidate.param_types[i];
let arg_ty = &arg_types[i];
let conv = self
.implicit_conversion
.check_implicit_conversion(arg_ty, param_ty);
if !conv.valid {
if self.enable_sfinae && candidate.is_template {
return (X86ConversionRank::NotViable, sequences);
}
return (X86ConversionRank::NotViable, sequences);
}
let _ = &conv.rank;
if conv.rank > worst_rank {
worst_rank = conv.rank;
}
sequences.push(conv);
}
for _ in param_count..arg_count {
worst_rank = worst_rank.max(X86ConversionRank::Ellipsis);
sequences.push(X86ConversionResult::valid(
X86ConvKind::Identity,
X86ConversionRank::Ellipsis,
));
}
(worst_rank, sequences)
}
fn compare_candidates(
&self,
a: &X86OverloadCandidate,
b: &X86OverloadCandidate,
) -> std::cmp::Ordering {
use std::cmp::Ordering;
match a.viability.cmp(&b.viability) {
Ordering::Less => return Ordering::Less,
Ordering::Greater => return Ordering::Greater,
Ordering::Equal => {}
}
let n = a
.conversion_sequences
.len()
.min(b.conversion_sequences.len());
let mut a_better = false;
let mut b_better = false;
for i in 0..n {
let rank_a = a
.conversion_sequences
.get(i)
.map(|c| c.rank)
.unwrap_or(X86ConversionRank::NotViable);
let rank_b = b
.conversion_sequences
.get(i)
.map(|c| c.rank)
.unwrap_or(X86ConversionRank::NotViable);
match rank_a.cmp(&rank_b) {
Ordering::Less => a_better = true,
Ordering::Greater => b_better = true,
Ordering::Equal => {}
}
}
if a_better && !b_better {
Ordering::Less
} else if b_better && !a_better {
Ordering::Greater
} else {
if !a.is_template && b.is_template {
Ordering::Less
} else if a.is_template && !b.is_template {
Ordering::Greater
} else {
Ordering::Equal }
}
}
pub fn clear(&mut self) {
self.candidates.clear();
}
pub fn candidate_count(&self) -> usize {
self.candidates.len()
}
pub fn is_viable_at(&self, index: usize, arg_types: &[QualType]) -> bool {
if let Some(candidate) = self.candidates.get(index) {
self.check_viability(candidate, arg_types).0 != X86ConversionRank::NotViable
} else {
false
}
}
pub fn detect_ambiguity(&self, arg_types: &[QualType]) -> Vec<(usize, usize)> {
let mut ambiguous_pairs = Vec::new();
let viable: Vec<(usize, &X86OverloadCandidate)> = self
.candidates
.iter()
.enumerate()
.filter(|(_, c)| self.check_viability(c, arg_types).0 != X86ConversionRank::NotViable)
.collect();
for i in 0..viable.len() {
for j in (i + 1)..viable.len() {
if self.compare_candidates(viable[i].1, viable[j].1) == std::cmp::Ordering::Equal {
ambiguous_pairs.push((viable[i].0, viable[j].0));
}
}
}
ambiguous_pairs
}
}
impl Default for X86OverloadResolution {
fn default() -> Self {
Self::new(true)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum X86TemplateParamKind {
Type,
NonType,
TemplateTemplate,
}
#[derive(Debug, Clone)]
pub struct X86TemplateParam {
pub name: String,
pub kind: X86TemplateParamKind,
pub default_arg: Option<QualType>,
pub is_parameter_pack: bool,
}
impl X86TemplateParam {
pub fn type_param(name: &str) -> Self {
Self {
name: name.to_string(),
kind: X86TemplateParamKind::Type,
default_arg: None,
is_parameter_pack: false,
}
}
pub fn nontype_param(name: &str, ty: QualType) -> Self {
Self {
name: name.to_string(),
kind: X86TemplateParamKind::NonType,
default_arg: Some(ty),
is_parameter_pack: false,
}
}
pub fn pack(mut self) -> Self {
self.is_parameter_pack = true;
self
}
}
#[derive(Debug, Clone)]
pub enum X86TemplateArg {
Type(QualType),
NonType(i64),
Template(X86TemplateParam),
}
impl fmt::Display for X86TemplateArg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86TemplateArg::Type(ty) => write!(f, "{}", ty),
X86TemplateArg::NonType(val) => write!(f, "{}", val),
X86TemplateArg::Template(param) => write!(f, "template {}", param.name),
}
}
}
#[derive(Debug, Clone)]
pub struct X86DeductionResult {
pub success: bool,
pub deduced_args: Vec<(String, X86TemplateArg)>,
pub failure_reason: Option<String>,
}
impl X86DeductionResult {
pub fn success(args: Vec<(String, X86TemplateArg)>) -> Self {
Self {
success: true,
deduced_args: args,
failure_reason: None,
}
}
pub fn failure(reason: impl Into<String>) -> Self {
Self {
success: false,
deduced_args: Vec::new(),
failure_reason: Some(reason.into()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86LookupPhase {
Phase1, Phase2, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86NameDependence {
NonDependent,
TypeDependent,
ValueDependent,
FullyDependent,
}
#[derive(Debug, Clone)]
pub struct X86TemplateSema {
pub template_params: Vec<X86TemplateParam>,
pub deduced_args: HashMap<String, X86TemplateArg>,
pub depth: X86InstantiationDepth,
pub sfinae_active: bool,
pub sfinae_errors: Vec<String>,
pub lookup_phase: X86LookupPhase,
pub deferred_names: Vec<(String, String)>, pub instantiation_cache: HashMap<String, bool>,
pub name_dependence: HashMap<String, X86NameDependence>,
}
impl X86TemplateSema {
pub fn new(max_depth: u32) -> Self {
Self {
template_params: Vec::new(),
deduced_args: HashMap::new(),
depth: X86InstantiationDepth::new(max_depth),
sfinae_active: false,
sfinae_errors: Vec::new(),
lookup_phase: X86LookupPhase::Phase1,
deferred_names: Vec::new(),
instantiation_cache: HashMap::new(),
name_dependence: HashMap::new(),
}
}
pub fn begin_template_params(&mut self, params: Vec<X86TemplateParam>) {
self.template_params = params;
self.enter_scope(); }
pub fn end_template_params(&mut self) {
self.template_params.clear();
}
pub fn validate_template_params(&self, diag: &mut X86DiagnosticEmitter) -> bool {
let mut seen = HashSet::new();
let mut valid = true;
for param in &self.template_params {
if !seen.insert(param.name.clone()) {
diag.error_at(
format!("duplicate template parameter '{}'", param.name),
None::<String>,
0,
0,
);
valid = false;
}
}
valid
}
pub fn check_template_args(
&self,
args: &[X86TemplateArg],
diag: &mut X86DiagnosticEmitter,
) -> bool {
if args.len() > self.template_params.len() {
diag.error_at(
format!(
"too many template arguments: expected {}, got {}",
self.template_params.len(),
args.len()
),
None::<String>,
0,
0,
);
return false;
}
for (i, arg) in args.iter().enumerate() {
if i < self.template_params.len() {
let param = &self.template_params[i];
if !self.template_arg_matches_param(arg, param) {
diag.error_at(
format!(
"template argument '{}' does not match parameter '{}'",
arg, param.name
),
None::<String>,
0,
0,
);
return false;
}
}
}
true
}
fn template_arg_matches_param(&self, arg: &X86TemplateArg, param: &X86TemplateParam) -> bool {
match (arg, ¶m.kind) {
(X86TemplateArg::Type(_), X86TemplateParamKind::Type) => true,
(X86TemplateArg::NonType(_), X86TemplateParamKind::NonType) => true,
(X86TemplateArg::Template(_), X86TemplateParamKind::TemplateTemplate) => true,
_ => false,
}
}
pub fn deduce_template_args(
&mut self,
param_types: &[QualType],
arg_types: &[QualType],
) -> X86DeductionResult {
let mut deduced = Vec::new();
for (i, param_ty) in param_types.iter().enumerate() {
if let Some(arg_ty) = arg_types.get(i) {
if let Some(param_name) = self.extract_template_param(param_ty) {
deduced.push((param_name, X86TemplateArg::Type(arg_ty.clone())));
}
}
}
X86DeductionResult::success(deduced)
}
fn extract_template_param(&self, ty: &QualType) -> Option<String> {
let ty_str = ty.to_string();
for param in &self.template_params {
if ty_str.contains(¶m.name) {
return Some(param.name.clone());
}
}
None
}
pub fn classify_dependence(&self, name: &str) -> X86NameDependence {
self.name_dependence
.get(name)
.copied()
.unwrap_or(X86NameDependence::NonDependent)
}
pub fn needs_typename_keyword(&self, name: &str) -> bool {
matches!(
self.classify_dependence(name),
X86NameDependence::TypeDependent | X86NameDependence::FullyDependent
)
}
pub fn needs_template_keyword(&self, name: &str) -> bool {
matches!(
self.classify_dependence(name),
X86NameDependence::FullyDependent
)
}
pub fn enter_phase2(&mut self) {
self.lookup_phase = X86LookupPhase::Phase2;
}
pub fn defer_name(&mut self, name: &str, scope: &str) {
self.deferred_names
.push((name.to_string(), scope.to_string()));
}
pub fn deferred_names(&self) -> &[(String, String)] {
&self.deferred_names
}
pub fn enter_instantiation(&mut self, name: &str, diag: &mut X86DiagnosticEmitter) -> bool {
match self.depth.enter(name.to_string()) {
Ok(()) => {
diag.push_template_backtrace(name, Vec::new(), None, 0, 0);
true
}
Err(msg) => {
diag.fatal(msg);
false
}
}
}
pub fn leave_instantiation(&mut self) {
self.depth.leave();
}
pub fn current_depth(&self) -> u32 {
self.depth.current
}
pub fn enter_sfinae(&mut self) {
self.sfinae_active = true;
self.sfinae_errors.clear();
}
pub fn record_sfinae_error(&mut self, error: impl Into<String>) {
if self.sfinae_active {
self.sfinae_errors.push(error.into());
}
}
pub fn leave_sfinae(&mut self) -> Vec<String> {
self.sfinae_active = false;
std::mem::take(&mut self.sfinae_errors)
}
pub fn sfinae_has_errors(&self) -> bool {
!self.sfinae_errors.is_empty()
}
pub fn is_sfinae_active(&self) -> bool {
self.sfinae_active
}
pub fn is_instantiated(&self, key: &str) -> bool {
self.instantiation_cache.get(key).copied().unwrap_or(false)
}
pub fn mark_instantiated(&mut self, key: &str) {
self.instantiation_cache.insert(key.to_string(), true);
}
pub fn instantiation_key(&self, name: &str, args: &[X86TemplateArg]) -> String {
let args_str = args
.iter()
.map(|a| a.to_string())
.collect::<Vec<_>>()
.join(",");
format!("{}<{}>", name, args_str)
}
fn enter_scope(&mut self) {
}
}
impl Default for X86TemplateSema {
fn default() -> Self {
Self::new(1024)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum X86ConstValue {
Int(i128),
UInt(u128),
Float(f64),
Bool(bool),
NullPtr,
StringLiteral(String),
Address(usize),
Composite(Vec<X86ConstValue>),
NonConstant,
}
impl X86ConstValue {
pub fn as_int(&self) -> Option<i128> {
match self {
X86ConstValue::Int(v) => Some(*v),
X86ConstValue::Bool(b) => Some(if *b { 1 } else { 0 }),
_ => None,
}
}
pub fn as_float(&self) -> Option<f64> {
match self {
X86ConstValue::Float(v) => Some(*v),
_ => None,
}
}
pub fn is_constant(&self) -> bool {
!matches!(self, X86ConstValue::NonConstant)
}
pub fn is_zero(&self) -> bool {
match self {
X86ConstValue::Int(v) => *v == 0,
X86ConstValue::UInt(v) => *v == 0,
X86ConstValue::Float(v) => *v == 0.0,
X86ConstValue::Bool(b) => !*b,
X86ConstValue::NullPtr => true,
_ => false,
}
}
}
impl fmt::Display for X86ConstValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86ConstValue::Int(v) => write!(f, "{}", v),
X86ConstValue::UInt(v) => write!(f, "{}", v),
X86ConstValue::Float(v) => write!(f, "{}", v),
X86ConstValue::Bool(b) => write!(f, "{}", b),
X86ConstValue::NullPtr => write!(f, "nullptr"),
X86ConstValue::StringLiteral(s) => write!(f, "\"{}\"", s),
X86ConstValue::Address(a) => write!(f, "&{:x}", a),
X86ConstValue::Composite(vals) => {
write!(f, "{{")?;
for (i, v) in vals.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", v)?;
}
write!(f, "}}")
}
X86ConstValue::NonConstant => write!(f, "<non-constant>"),
}
}
}
#[derive(Debug, Clone)]
pub enum X86ConstExprError {
DivisionByZero,
Overflow,
NonConstantOperand,
ReadNonConstVariable,
CallNonConstexprFunction,
InfiniteRecursion,
MaxDepthExceeded,
UndefinedBehavior,
Other(String),
}
impl fmt::Display for X86ConstExprError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86ConstExprError::DivisionByZero => {
write!(f, "division by zero in constant expression")
}
X86ConstExprError::Overflow => write!(f, "overflow in constant expression"),
X86ConstExprError::NonConstantOperand => {
write!(f, "non-constant operand in constant expression")
}
X86ConstExprError::ReadNonConstVariable => {
write!(f, "read of non-const variable in constant expression")
}
X86ConstExprError::CallNonConstexprFunction => {
write!(f, "call to non-constexpr function")
}
X86ConstExprError::InfiniteRecursion => {
write!(f, "infinite recursion in constexpr function")
}
X86ConstExprError::MaxDepthExceeded => write!(f, "constexpr evaluation depth exceeded"),
X86ConstExprError::UndefinedBehavior => {
write!(f, "undefined behavior in constant expression")
}
X86ConstExprError::Other(s) => write!(f, "{}", s),
}
}
}
#[derive(Debug, Clone)]
pub struct X86ConstexprEval {
pub enum_values: HashMap<String, i64>,
pub var_addresses: HashMap<String, usize>,
pub errors: Vec<X86ConstExprError>,
pub max_depth: u32,
pub current_depth: u32,
pub is_consteval: bool,
pub is_if_constexpr: bool,
pub constexpr_vars: HashMap<String, X86ConstValue>,
}
impl X86ConstexprEval {
pub fn new(max_depth: u32) -> Self {
Self {
enum_values: HashMap::new(),
var_addresses: HashMap::new(),
errors: Vec::new(),
max_depth,
current_depth: 0,
is_consteval: false,
is_if_constexpr: false,
constexpr_vars: HashMap::new(),
}
}
pub fn register_enum_value(&mut self, name: &str, value: i64) {
self.enum_values.insert(name.to_string(), value);
}
pub fn register_var_address(&mut self, name: &str, addr: usize) {
self.var_addresses.insert(name.to_string(), addr);
}
pub fn register_constexpr_var(&mut self, name: &str, value: X86ConstValue) {
self.constexpr_vars.insert(name.to_string(), value);
}
pub fn evaluate(&mut self, expr: &Expr) -> X86ConstValue {
if self.current_depth >= self.max_depth {
self.errors.push(X86ConstExprError::MaxDepthExceeded);
return X86ConstValue::NonConstant;
}
self.current_depth += 1;
let result = self.eval_inner(expr);
self.current_depth -= 1;
result
}
fn eval_inner(&mut self, expr: &Expr) -> X86ConstValue {
match expr {
Expr::IntLiteral(val) => X86ConstValue::Int(*val as i128),
Expr::UIntLiteral(val, _) => X86ConstValue::UInt(*val as u128),
Expr::FloatLiteral(val) => X86ConstValue::Float(*val),
Expr::DoubleLiteral(val) => X86ConstValue::Float(*val),
Expr::CharLiteral(c) => X86ConstValue::Int(*c as i128),
Expr::StringLiteral(s) => X86ConstValue::StringLiteral(s.clone()),
Expr::Ident(name) => {
if let Some(val) = self.enum_values.get(name) {
return X86ConstValue::Int(*val as i128);
}
if let Some(val) = self.constexpr_vars.get(name) {
return val.clone();
}
if let Some(addr) = self.var_addresses.get(name) {
return X86ConstValue::Address(*addr);
}
X86ConstValue::NonConstant
}
Expr::Unary(op, operand) => {
let val = self.evaluate(operand);
match (op, val) {
(UnaryOp::Plus, v) => v,
(UnaryOp::Minus, X86ConstValue::Int(v)) => X86ConstValue::Int(-v),
(UnaryOp::Minus, X86ConstValue::Float(v)) => X86ConstValue::Float(-v),
(UnaryOp::Not, X86ConstValue::Int(v)) => X86ConstValue::Bool(v == 0),
(UnaryOp::Not, X86ConstValue::Bool(b)) => X86ConstValue::Bool(!b),
(UnaryOp::BitNot, X86ConstValue::Int(v)) => X86ConstValue::Int(!v),
(UnaryOp::BitNot, X86ConstValue::UInt(v)) => X86ConstValue::UInt(!v),
_ => X86ConstValue::NonConstant,
}
}
Expr::Binary(op, lhs, rhs) => {
let l = self.evaluate(lhs);
let r = self.evaluate(rhs);
self.eval_binary(*op, l, r)
}
Expr::Conditional(cond, then_expr, else_expr) => {
let c = self.evaluate(cond);
let is_true = match &c {
X86ConstValue::Bool(b) => *b,
X86ConstValue::Int(v) => *v != 0,
_ => return X86ConstValue::NonConstant,
};
if is_true {
self.evaluate(then_expr)
} else {
self.evaluate(else_expr)
}
}
Expr::Cast(ty, expr) => {
let val = self.evaluate(expr);
self.eval_cast(ty, val)
}
Expr::SizeOf(_inner) => {
X86ConstValue::UInt(8) }
Expr::SizeOfType(_) => X86ConstValue::UInt(8),
Expr::AlignOf(_) => X86ConstValue::UInt(8),
Expr::AlignOfType(_) => X86ConstValue::UInt(8),
_ => X86ConstValue::NonConstant,
}
}
fn eval_binary(&mut self, op: BinaryOp, l: X86ConstValue, r: X86ConstValue) -> X86ConstValue {
match (l, r) {
(X86ConstValue::Int(a), X86ConstValue::Int(b)) => match op {
BinaryOp::Add => X86ConstValue::Int(a.wrapping_add(b)),
BinaryOp::Sub => X86ConstValue::Int(a.wrapping_sub(b)),
BinaryOp::Mul => X86ConstValue::Int(a.wrapping_mul(b)),
BinaryOp::Div => {
if b == 0 {
self.errors.push(X86ConstExprError::DivisionByZero);
X86ConstValue::NonConstant
} else {
X86ConstValue::Int(a / b)
}
}
BinaryOp::Mod => {
if b == 0 {
self.errors.push(X86ConstExprError::DivisionByZero);
X86ConstValue::NonConstant
} else {
X86ConstValue::Int(a % b)
}
}
BinaryOp::And => X86ConstValue::Int(a & b),
BinaryOp::Or => X86ConstValue::Int(a | b),
BinaryOp::Xor => X86ConstValue::Int(a ^ b),
BinaryOp::Shl => {
if b >= 0 && b < 128 {
X86ConstValue::Int(a << (b as u32))
} else {
X86ConstValue::NonConstant
}
}
BinaryOp::Shr => {
if b >= 0 && b < 128 {
X86ConstValue::Int(a >> (b as u32))
} else {
X86ConstValue::NonConstant
}
}
BinaryOp::Eq => X86ConstValue::Bool(a == b),
BinaryOp::Ne => X86ConstValue::Bool(a != b),
BinaryOp::Lt => X86ConstValue::Bool(a < b),
BinaryOp::Gt => X86ConstValue::Bool(a > b),
BinaryOp::Le => X86ConstValue::Bool(a <= b),
BinaryOp::Ge => X86ConstValue::Bool(a >= b),
BinaryOp::LogicAnd => X86ConstValue::Bool(a != 0 && b != 0),
BinaryOp::LogicOr => X86ConstValue::Bool(a != 0 || b != 0),
BinaryOp::Comma => X86ConstValue::Int(b),
_ => X86ConstValue::NonConstant,
},
(X86ConstValue::UInt(a), X86ConstValue::UInt(b)) => match op {
BinaryOp::Add => X86ConstValue::UInt(a.wrapping_add(b)),
BinaryOp::Sub => X86ConstValue::UInt(a.wrapping_sub(b)),
BinaryOp::Mul => X86ConstValue::UInt(a.wrapping_mul(b)),
BinaryOp::Div => {
if b == 0 {
self.errors.push(X86ConstExprError::DivisionByZero);
X86ConstValue::NonConstant
} else {
X86ConstValue::UInt(a / b)
}
}
BinaryOp::Mod => {
if b == 0 {
self.errors.push(X86ConstExprError::DivisionByZero);
X86ConstValue::NonConstant
} else {
X86ConstValue::UInt(a % b)
}
}
BinaryOp::And => X86ConstValue::UInt(a & b),
BinaryOp::Or => X86ConstValue::UInt(a | b),
BinaryOp::Xor => X86ConstValue::UInt(a ^ b),
BinaryOp::Eq => X86ConstValue::Bool(a == b),
BinaryOp::Ne => X86ConstValue::Bool(a != b),
BinaryOp::Lt => X86ConstValue::Bool(a < b),
BinaryOp::Gt => X86ConstValue::Bool(a > b),
BinaryOp::Le => X86ConstValue::Bool(a <= b),
BinaryOp::Ge => X86ConstValue::Bool(a >= b),
_ => X86ConstValue::NonConstant,
},
(X86ConstValue::Float(a), X86ConstValue::Float(b)) => match op {
BinaryOp::Add => X86ConstValue::Float(a + b),
BinaryOp::Sub => X86ConstValue::Float(a - b),
BinaryOp::Mul => X86ConstValue::Float(a * b),
BinaryOp::Div => X86ConstValue::Float(a / b),
BinaryOp::Eq => X86ConstValue::Bool(a == b),
BinaryOp::Ne => X86ConstValue::Bool(a != b),
BinaryOp::Lt => X86ConstValue::Bool(a < b),
BinaryOp::Gt => X86ConstValue::Bool(a > b),
BinaryOp::Le => X86ConstValue::Bool(a <= b),
BinaryOp::Ge => X86ConstValue::Bool(a >= b),
_ => X86ConstValue::NonConstant,
},
(X86ConstValue::Bool(a), X86ConstValue::Bool(b)) => match op {
BinaryOp::LogicAnd => X86ConstValue::Bool(a && b),
BinaryOp::LogicOr => X86ConstValue::Bool(a || b),
BinaryOp::Eq => X86ConstValue::Bool(a == b),
BinaryOp::Ne => X86ConstValue::Bool(a != b),
_ => X86ConstValue::NonConstant,
},
_ => X86ConstValue::NonConstant,
}
}
fn eval_cast(&self, ty: &QualType, val: X86ConstValue) -> X86ConstValue {
match val {
X86ConstValue::Int(v) => {
if ty.is_float() {
return X86ConstValue::Float(v as f64);
}
if ty.is_double() {
return X86ConstValue::Float(v as f64);
}
if ty.is_bool() {
return X86ConstValue::Bool(v != 0);
}
X86ConstValue::Int(v)
}
X86ConstValue::Float(v) => {
if ty.is_integer() {
X86ConstValue::Int(v as i128)
} else {
X86ConstValue::Float(v)
}
}
X86ConstValue::Bool(b) => {
if ty.is_integer() {
X86ConstValue::Int(if b { 1 } else { 0 })
} else {
X86ConstValue::Bool(b)
}
}
other => other,
}
}
pub fn eval_if_constexpr(&mut self, cond: &Expr) -> Option<bool> {
self.is_if_constexpr = true;
let result = self.evaluate(cond);
self.is_if_constexpr = false;
match result {
X86ConstValue::Bool(b) => Some(b),
X86ConstValue::Int(v) => Some(v != 0),
_ => None,
}
}
pub fn is_core_constant_expression(&mut self, expr: &Expr) -> bool {
self.errors.clear();
let result = self.evaluate(expr);
result.is_constant() && self.errors.is_empty()
}
pub fn is_integer_constant_expression(&mut self, expr: &Expr) -> bool {
let result = self.evaluate(expr);
matches!(result, X86ConstValue::Int(_) | X86ConstValue::UInt(_))
}
pub fn is_valid_array_size(&mut self, size_expr: &Expr) -> bool {
match self.evaluate(size_expr) {
X86ConstValue::Int(v) => v > 0 && v <= i32::MAX as i128,
X86ConstValue::UInt(v) => v > 0 && v <= i32::MAX as u128,
_ => false,
}
}
pub fn is_valid_bitfield_width(&mut self, width_expr: &Expr, max_width: u32) -> bool {
match self.evaluate(width_expr) {
X86ConstValue::Int(v) => v > 0 && v <= max_width as i128,
X86ConstValue::UInt(v) => v > 0 && v <= max_width as u128,
_ => false,
}
}
pub fn enum_count(&self) -> usize {
self.enum_values.len()
}
pub fn clear_errors(&mut self) {
self.errors.clear();
}
}
impl Default for X86ConstexprEval {
fn default() -> Self {
Self::new(512)
}
}
#[derive(Debug, Clone)]
pub struct X86DeepSema {
pub standard: String,
pub target: String,
pub is_64bit: bool,
pub scope_manager: X86ScopeManager,
pub decl_validator: X86DeclValidator,
pub expr_validator: X86ExprValidator,
pub type_promotion: X86TypePromotion,
pub implicit_conversion: X86ImplicitConversion,
pub overload_resolution: X86OverloadResolution,
pub template_sema: X86TemplateSema,
pub constexpr_eval: X86ConstexprEval,
pub diagnostic: X86DiagnosticEmitter,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
impl X86DeepSema {
pub fn new(standard: &str, target: &str) -> Self {
let is_64bit =
target.contains("64") || target.contains("x86_64") || target.contains("amd64");
Self {
standard: standard.to_string(),
target: target.to_string(),
is_64bit,
scope_manager: X86ScopeManager::new(),
decl_validator: X86DeclValidator::new(standard, target),
expr_validator: X86ExprValidator::new(standard, is_64bit),
type_promotion: if is_64bit {
X86TypePromotion::new_lp64()
} else {
X86TypePromotion::new_ilp32()
},
implicit_conversion: X86ImplicitConversion::new(is_64bit),
overload_resolution: X86OverloadResolution::new(is_64bit),
template_sema: X86TemplateSema::new(1024),
constexpr_eval: X86ConstexprEval::new(512),
diagnostic: X86DiagnosticEmitter::new(),
errors: Vec::new(),
warnings: Vec::new(),
}
}
pub fn analyze_tu(&mut self, tu: &TranslationUnit) -> bool {
for decl in &tu.decls {
self.register_declaration(decl);
}
for decl in &tu.decls {
self.validate_declaration(decl);
}
self.check_odr_compliance();
self.decl_validator.finalize_tentative_defs();
self.collect_errors();
self.errors.is_empty()
}
pub fn analyze_function(&mut self, func: &FunctionDecl) -> bool {
self.scope_manager
.enter_function(&func.name, func.ret_ty.clone());
self.scope_manager.enter_scope(X86ScopeKind::Function);
for param in &func.params {
self.scope_manager.declare_variable(
&format!("param"),
param.ty.clone(),
false,
false,
&mut self.diagnostic,
);
}
self.decl_validator
.check_function_decl(func, &mut self.diagnostic);
if let Some(ref body) = func.body {
self.analyze_compound_stmt(body);
}
self.scope_manager.leave_scope();
self.scope_manager.leave_function();
self.collect_errors();
true
}
fn register_declaration(&mut self, decl: &Decl) {
match decl {
Decl::Function(func) => {
self.scope_manager.declare_function(
&func.name,
func.ret_ty.clone(),
func.params.iter().map(|p| p.ty.clone()).collect(),
func.is_vararg,
func.is_inline,
func.linkage.as_str(),
&mut self.diagnostic,
);
}
Decl::Variable(var) => {
self.scope_manager.declare_variable(
&var.name,
var.ty.clone(),
var.is_extern,
var.is_static,
&mut self.diagnostic,
);
}
Decl::Typedef(typedef) => {
self.scope_manager.declare_typedef(
&typedef.name,
typedef.underlying.clone(),
&mut self.diagnostic,
);
}
Decl::Enum(enum_decl) => {
self.scope_manager.declare_enum_tag(
enum_decl.name.as_deref().unwrap_or(""),
&mut self.diagnostic,
);
for variant in &enum_decl.variants {
self.scope_manager.declare_enum_constant(
&variant.name,
variant.value.unwrap_or(0),
&mut self.diagnostic,
);
self.constexpr_eval
.register_enum_value(&variant.name, variant.value.unwrap_or(0));
}
}
Decl::Struct(struct_decl) => {
self.scope_manager.declare_struct_tag(
struct_decl.name.as_deref().unwrap_or(""),
struct_decl.is_union,
&mut self.diagnostic,
);
}
_ => {}
}
}
fn validate_declaration(&mut self, decl: &Decl) {
match decl {
Decl::Function(func) => {
self.decl_validator
.check_function_decl(func, &mut self.diagnostic);
}
Decl::Variable(var) => {
self.decl_validator
.check_var_decl(var, &mut self.diagnostic);
}
Decl::Struct(struct_decl) => {
self.decl_validator
.check_struct_decl(struct_decl, &mut self.diagnostic);
}
Decl::Enum(enum_decl) => {
self.decl_validator
.check_enum_decl(enum_decl, &mut self.diagnostic);
}
Decl::Typedef(typedef) => {
self.decl_validator
.check_typedef(typedef, &mut self.diagnostic);
}
_ => {}
}
}
fn analyze_compound_stmt(&mut self, stmt: &CompoundStmt) {
self.scope_manager.enter_scope(X86ScopeKind::Block);
for s in &stmt.stmts {
self.analyze_stmt(s);
}
self.scope_manager.leave_scope();
}
fn analyze_stmt(&mut self, stmt: &Stmt) {
match stmt {
Stmt::Return(expr) => {
self.scope_manager.current_scope_mut().mark_returned();
if let Some(ref ret_ty) = self.scope_manager.current_return_type.clone() {
if let Some(expr) = expr {
let expr_ty = self.expr_validator.expr_type(expr);
if ret_ty.is_void() && !expr_ty.is_void() {
self.diagnostic
.warning("return with a value in function returning void");
}
} else if !ret_ty.is_void() {
self.diagnostic.warn_missing_return(
self.scope_manager
.current_function
.as_deref()
.unwrap_or("unknown"),
None::<String>,
0,
0,
);
}
}
}
Stmt::If { cond, then, els } => {
self.expr_validator
.check_condition(cond, &mut self.diagnostic);
self.analyze_stmt(then);
if let Some(else_stmt) = els {
self.analyze_stmt(else_stmt);
}
}
Stmt::While { cond, body } => {
self.expr_validator
.check_condition(cond, &mut self.diagnostic);
self.scope_manager.enter_loop();
self.analyze_stmt(body);
self.scope_manager.leave_loop();
}
Stmt::For {
init,
cond,
incr,
body,
} => {
if let Some(init) = init {
self.analyze_stmt(init);
}
if let Some(cond) = cond {
self.expr_validator
.check_condition(cond, &mut self.diagnostic);
}
self.scope_manager.enter_loop();
self.analyze_stmt(body);
self.scope_manager.leave_loop();
if let Some(incr) = incr {
let incr_stmt = Stmt::Expr(incr.clone());
self.analyze_stmt(&incr_stmt);
}
}
Stmt::DoWhile { cond, body } => {
self.scope_manager.enter_loop();
self.analyze_stmt(body);
self.scope_manager.leave_loop();
self.expr_validator
.check_condition(cond, &mut self.diagnostic);
}
Stmt::Switch { expr, body } => {
self.expr_validator
.check_condition(expr, &mut self.diagnostic);
self.scope_manager.enter_switch();
self.analyze_stmt(body);
self.scope_manager.leave_switch();
}
Stmt::Break => {
if !self.scope_manager.in_loop() && !self.scope_manager.in_switch() {
self.diagnostic
.error("break statement not within loop or switch");
}
}
Stmt::Continue => {
if !self.scope_manager.in_loop() {
self.diagnostic
.error("continue statement not within a loop");
}
}
Stmt::Compound(stmts) => {
self.analyze_compound_stmt(stmts);
}
Stmt::Expr(expr) => {
let _ty = self.expr_validator.expr_type(expr);
}
Stmt::Goto { label } => {
if !self.scope_manager.find_label(label) {
self.diagnostic.warning_at(
format!("goto label '{}' used but not yet defined", label),
None::<String>,
0,
0,
);
}
}
Stmt::Label { name, stmt } => {
self.scope_manager.define_label(name);
self.analyze_stmt(stmt);
}
Stmt::Case { value: _, stmt } => {
if !self.scope_manager.in_switch() {
self.diagnostic.error("case statement not within switch");
}
self.analyze_stmt(stmt);
}
Stmt::Default { stmt } => {
if !self.scope_manager.in_switch() {
self.diagnostic.error("default statement not within switch");
}
self.analyze_stmt(stmt);
}
_ => {}
}
}
fn check_odr_compliance(&mut self) {
for (name, locations) in &self.scope_manager.odr_functions {
if locations.len() > 1 {
for (_i, loc) in locations.iter().enumerate().skip(1) {
self.diagnostic.error_at(
format!(
"duplicate definition of function '{}' (ODR violation)",
name
),
loc.0.as_ref().map(|s| s.as_str()),
loc.1,
loc.2,
);
}
}
}
}
fn collect_errors(&mut self) {
self.errors.clear();
self.warnings.clear();
for diag in &self.diagnostic.diagnostics {
match diag.severity {
X86DiagSeverity::Error | X86DiagSeverity::Fatal => {
self.errors.push(diag.message.clone());
}
X86DiagSeverity::Warning => {
self.warnings.push(diag.message.clone());
}
_ => {}
}
}
}
pub fn type_of_expr(&self, expr: &Expr) -> QualType {
self.expr_validator.expr_type(expr)
}
pub fn check_type_compatibility(&self, a: &QualType, b: &QualType) -> bool {
self.decl_validator.types_compatible(a, b)
}
pub fn dump_symbol_table(&self) -> String {
self.scope_manager.dump_scopes()
}
pub fn resolve_overload(&mut self, _name: &str, arg_types: &[QualType]) -> Option<usize> {
if self.overload_resolution.candidates.is_empty() {
return None;
}
self.overload_resolution
.resolve(arg_types, &mut self.diagnostic)
}
pub fn evaluate_constexpr(&mut self, expr: &Expr) -> X86ConstValue {
self.constexpr_eval.evaluate(expr)
}
pub fn enter_template(&mut self, name: &str, args: &[X86TemplateArg]) -> bool {
let key = self.template_sema.instantiation_key(name, args);
if self.template_sema.is_instantiated(&key) {
return true; }
let ok = self
.template_sema
.enter_instantiation(name, &mut self.diagnostic);
if ok {
self.template_sema.mark_instantiated(&key);
}
ok
}
pub fn leave_template(&mut self) {
self.template_sema.leave_instantiation();
self.diagnostic.pop_template_backtrace();
}
}
impl Default for X86DeepSema {
fn default() -> Self {
Self::new("c17", "x86_64-unknown-linux-gnu")
}
}
pub fn make_x86_64_deep_sema() -> X86DeepSema {
X86DeepSema::new("c17", "x86_64-unknown-linux-gnu")
}
pub fn make_x86_32_deep_sema() -> X86DeepSema {
X86DeepSema::new("c17", "i386-unknown-linux-gnu")
}
pub fn analyze_x86_tu(tu: &TranslationUnit) -> (bool, Vec<String>, Vec<String>) {
let mut sema = make_x86_64_deep_sema();
let ok = sema.analyze_tu(tu);
(ok, sema.errors.clone(), sema.warnings.clone())
}
pub fn analyze_x86_function(func: &FunctionDecl) -> (bool, Vec<String>, Vec<String>) {
let mut sema = make_x86_64_deep_sema();
let ok = sema.analyze_function(func);
(ok, sema.errors.clone(), sema.warnings.clone())
}
pub fn contains_x86_message(errors: &[String], msg: &str) -> bool {
errors.iter().any(|e| e.contains(msg))
}
pub fn x86_int_lit(val: i64) -> Expr {
Expr::IntLiteral(val)
}
pub fn x86_ident(name: &str) -> Expr {
Expr::Ident(name.to_string())
}
pub fn x86_binary(op: BinaryOp, lhs: Expr, rhs: Expr) -> Expr {
Expr::Binary(op, Box::new(lhs), Box::new(rhs))
}
pub fn x86_assign(lhs: Expr, rhs: Expr) -> Expr {
Expr::Assign(BinaryOp::Assign, Box::new(lhs), Box::new(rhs))
}
pub fn x86_return(expr: Option<Expr>) -> Stmt {
Stmt::Return(expr.map(Box::new))
}
pub fn x86_compound(stmts: Vec<Stmt>) -> Stmt {
Stmt::Compound(CompoundStmt { stmts })
}
pub fn x86_call(callee: Expr, args: Vec<Expr>) -> Expr {
Expr::Call {
callee: Box::new(callee),
args,
}
}
pub fn x86_make_function(
name: &str,
ret_ty: QualType,
params: Vec<QualType>,
body: Option<CompoundStmt>,
) -> FunctionDecl {
FunctionDecl {
name: name.to_string(),
ret_ty,
params: params
.into_iter()
.map(|ty| VarDecl {
name: String::new(),
ty,
init: None,
linkage: Linkage::None,
is_global: false,
is_extern: false,
is_static: false,
})
.collect(),
body,
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
}
}
pub fn x86_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 x86_make_tu(decls: Vec<Decl>) -> TranslationUnit {
TranslationUnit {
decls,
filename: "test.c".to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_sema() -> X86DeepSema {
make_x86_64_deep_sema()
}
fn make_sema_32() -> X86DeepSema {
make_x86_32_deep_sema()
}
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_diag_emitter_error_count() {
let mut d = X86DiagnosticEmitter::new();
assert_eq!(d.error_count(), 0);
d.error("test error");
assert_eq!(d.error_count(), 1);
d.warning("test warning");
assert_eq!(d.error_count(), 1);
assert_eq!(d.warning_count(), 1);
}
#[test]
fn test_diag_emitter_clear() {
let mut d = X86DiagnosticEmitter::new();
d.error("err1");
d.error("err2");
d.warning("warn1");
assert_eq!(d.error_count(), 2);
d.clear();
assert_eq!(d.error_count(), 0);
assert_eq!(d.warning_count(), 0);
}
#[test]
fn test_diag_emitter_warnings_as_errors() {
let mut d = X86DiagnosticEmitter::new();
d.warnings_as_errors = true;
d.warning("test");
assert_eq!(d.error_count(), 1);
assert_eq!(d.warning_count(), 0);
}
#[test]
fn test_diag_emitter_format() {
let mut d = X86DiagnosticEmitter::new();
d.error_at("type mismatch", Some("test.c"), 10, 5);
let formatted = d.format_all();
assert!(formatted.contains("error"));
assert!(formatted.contains("type mismatch"));
assert!(formatted.contains("test.c"));
}
#[test]
fn test_diag_emitter_should_fail() {
let mut d = X86DiagnosticEmitter::new();
assert!(!d.should_fail());
d.error("error");
assert!(d.should_fail());
}
#[test]
fn test_diag_emitter_summary() {
let mut d = X86DiagnosticEmitter::new();
assert_eq!(d.summary(), "no errors or warnings.");
d.error("e");
d.warning("w");
let summary = d.summary();
assert!(summary.contains("error"));
}
#[test]
fn test_diag_emitter_template_backtrace() {
let mut d = X86DiagnosticEmitter::new();
d.push_template_backtrace(
"MyTemplate",
vec!["int".into()],
Some("test.h".into()),
20,
3,
);
d.error_at("instantiation error", Some("test.c"), 5, 1);
d.pop_template_backtrace();
let formatted = d.format_all();
assert!(formatted.contains("MyTemplate"));
}
#[test]
fn test_diag_emitter_overload_notes() {
let mut d = X86DiagnosticEmitter::new();
d.add_overload_note("candidate: foo(int)");
d.add_overload_note("candidate: foo(double)");
d.error("ambiguous call");
let formatted = d.format_all();
assert!(formatted.contains("candidate"));
}
#[test]
fn test_diag_emitter_type_mismatch() {
let mut d = X86DiagnosticEmitter::new();
d.type_mismatch("int", "double", "assignment", Some("test.c"), 1, 1);
assert_eq!(d.error_count(), 1);
}
#[test]
fn test_diag_emitter_suppress_category() {
let mut d = X86DiagnosticEmitter::new();
d.suppress_category(X86DiagCategory::Shadow);
d.warning_at("declaration shadows", Some("test.c"), 3, 1);
assert_eq!(d.warning_count(), 0);
}
#[test]
fn test_diag_emitter_note_previous_declaration() {
let mut d = X86DiagnosticEmitter::new();
d.error_at("redefinition of 'x'", Some("test.c"), 5, 1);
d.note_previous_declaration("x", Some("test.c"), 2, 1);
let formatted = d.format_all();
assert!(formatted.contains("previous declaration"));
}
#[test]
fn test_diag_emitter_fixit() {
let mut d = X86DiagnosticEmitter::new();
d.error("use of deprecated function");
d.add_fixit(X86FixIt {
file: "test.c".into(),
start_line: 1,
start_col: 1,
end_line: 1,
end_col: 10,
replacement: "new_func".into(),
description: "use new_func instead".into(),
});
let formatted = d.format_all();
assert!(formatted.contains("fixit"));
}
#[test]
fn test_scope_enter_leave() {
let mut sm = X86ScopeManager::new();
assert_eq!(sm.scope_depth(), 1);
sm.enter_scope(X86ScopeKind::Block);
assert_eq!(sm.scope_depth(), 2);
sm.leave_scope();
assert_eq!(sm.scope_depth(), 1);
}
#[test]
fn test_scope_variable_lookup() {
let mut sm = X86ScopeManager::new();
let mut d = X86DiagnosticEmitter::new();
sm.declare_variable("x", int_ty(), false, false, &mut d);
assert!(sm.lookup("x").is_some());
assert!(sm.lookup("y").is_none());
}
#[test]
fn test_scope_nested_shadowing() {
let mut sm = X86ScopeManager::new();
sm.warn_shadow = true;
let mut d = X86DiagnosticEmitter::new();
sm.declare_variable("x", int_ty(), false, false, &mut d);
sm.enter_scope(X86ScopeKind::Block);
sm.declare_variable("x", float_ty(), false, false, &mut d);
assert!(d.warning_count() > 0);
}
#[test]
fn test_scope_function_lookup() {
let mut sm = X86ScopeManager::new();
let mut d = X86DiagnosticEmitter::new();
sm.declare_function("main", int_ty(), vec![], false, false, "external", &mut d);
let entry = sm.lookup_function("main");
assert!(entry.is_some());
}
#[test]
fn test_scope_at_file_scope() {
let sm = X86ScopeManager::new();
assert!(sm.at_file_scope());
}
#[test]
fn test_scope_in_function() {
let mut sm = X86ScopeManager::new();
sm.enter_function("test", int_ty());
assert!(sm.in_function());
sm.leave_function();
assert!(!sm.in_function());
}
#[test]
fn test_scope_loop_switch_tracking() {
let mut sm = X86ScopeManager::new();
assert!(!sm.in_loop());
assert!(!sm.in_switch());
sm.enter_loop();
assert!(sm.in_loop());
sm.leave_loop();
assert!(!sm.in_loop());
sm.enter_switch();
assert!(sm.in_switch());
sm.leave_switch();
assert!(!sm.in_switch());
}
#[test]
fn test_scope_label_management() {
let mut sm = X86ScopeManager::new();
sm.enter_scope(X86ScopeKind::Function);
sm.define_label("loop_start");
assert!(sm.find_label("loop_start"));
assert!(!sm.find_label("nonexistent"));
sm.leave_scope();
}
#[test]
fn test_scope_redefinition_detection() {
let mut sm = X86ScopeManager::new();
let mut d = X86DiagnosticEmitter::new();
let ok1 = sm.declare_variable("x", int_ty(), false, false, &mut d);
assert!(ok1);
let ok2 = sm.declare_variable("x", int_ty(), false, false, &mut d);
assert!(!ok2); assert!(d.error_count() > 0);
}
#[test]
fn test_scope_namespace() {
let mut sm = X86ScopeManager::new();
sm.register_namespace("std");
sm.enter_namespace("std");
assert_eq!(sm.current_namespace_string(), "std");
sm.leave_namespace();
assert_eq!(sm.current_namespace_string(), "");
}
#[test]
fn test_scope_dump() {
let mut sm = X86ScopeManager::new();
let mut d = X86DiagnosticEmitter::new();
sm.declare_variable("x", int_ty(), false, false, &mut d);
let dump = sm.dump_scopes();
assert!(dump.contains("x"));
assert!(dump.contains("Scope"));
}
#[test]
fn test_scope_total_declarations() {
let mut sm = X86ScopeManager::new();
let mut d = X86DiagnosticEmitter::new();
sm.declare_variable("a", int_ty(), false, false, &mut d);
sm.declare_variable("b", float_ty(), false, false, &mut d);
assert_eq!(sm.total_declarations(), 2);
}
#[test]
fn test_decl_validator_var_decl_void_error() {
let mut v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let var = VarDecl {
name: "v".into(),
ty: void_ty(),
init: None,
linkage: Linkage::External,
is_global: true,
is_extern: false,
is_static: false,
};
let ok = v.check_var_decl(&var, &mut d);
assert!(!ok);
assert!(d.error_count() > 0);
}
#[test]
fn test_decl_validator_extern_with_init() {
let mut v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let var = VarDecl {
name: "v".into(),
ty: int_ty(),
init: Some(Box::new(x86_int_lit(42))),
linkage: Linkage::External,
is_global: true,
is_extern: true,
is_static: false,
};
let ok = v.check_var_decl(&var, &mut d);
assert!(ok);
assert!(d.warning_count() > 0);
}
#[test]
fn test_decl_validator_function_return_array_error() {
let mut v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let func = FunctionDecl {
name: "f".into(),
ret_ty: QualType::array(QualType::int(), 10),
params: vec![],
body: None,
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let ok = v.check_function_decl(&func, &mut d);
assert!(!ok);
}
#[test]
fn test_decl_validator_duplicate_definition() {
let mut v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let func1 = FunctionDecl {
name: "f".into(),
ret_ty: int_ty(),
params: vec![],
body: Some(CompoundStmt { stmts: vec![] }),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
v.check_function_decl(&func1, &mut d);
let func2 = FunctionDecl {
name: "f".into(),
ret_ty: int_ty(),
params: vec![],
body: Some(CompoundStmt { stmts: vec![] }),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
d.clear();
let ok = v.check_function_decl(&func2, &mut d);
assert!(!ok);
}
#[test]
fn test_decl_validator_struct_duplicate_field() {
let mut v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let s = StructDecl {
name: "S".into(),
fields: vec![
FieldDecl::new("x", int_ty()),
FieldDecl::new("x", float_ty()),
],
is_union: false,
};
let ok = v.check_struct_decl(&s, &mut d);
assert!(!ok);
}
#[test]
fn test_decl_validator_enum_duplicate_enumerator() {
let mut v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let e = EnumDecl {
name: "E".into(),
variants: vec![
EnumVariant::new("A", Some(0)),
EnumVariant::new("A", Some(1)),
],
underlying: int_ty(),
};
let ok = v.check_enum_decl(&e, &mut d);
assert!(!ok);
}
#[test]
fn test_decl_validator_bitfield_width() {
let v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let ok = v.check_bitfield_width(8, &int_ty(), &mut d);
assert!(ok);
let ok2 = v.check_bitfield_width(65, &char_ty(), &mut d);
assert!(!ok2);
}
#[test]
fn test_decl_validator_type_width_bits_lp64() {
let v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
assert_eq!(v.type_width_bits(&char_ty()), 8);
assert_eq!(v.type_width_bits(&short_ty()), 16);
assert_eq!(v.type_width_bits(&int_ty()), 32);
assert_eq!(v.type_width_bits(&long_ty()), 64); assert_eq!(v.type_width_bits(&ptr_ty()), 64);
}
#[test]
fn test_decl_validator_type_width_bits_ilp32() {
let v = X86DeclValidator::new("c17", "i386-unknown-linux-gnu");
assert_eq!(v.type_width_bits(&long_ty()), 32); assert_eq!(v.type_width_bits(&ptr_ty()), 32);
}
#[test]
fn test_decl_validator_tentative_defs() {
let mut v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let var = VarDecl {
name: "x".into(),
ty: int_ty(),
init: None,
linkage: Linkage::External,
is_global: true,
is_extern: false,
is_static: false,
};
v.check_var_decl(&var, &mut d);
assert_eq!(v.tentative_defs.len(), 1);
let finalized = v.finalize_tentative_defs();
assert_eq!(finalized.len(), 1);
}
#[test]
fn test_expr_validator_int_literal_type() {
let v = X86ExprValidator::new("c17", true);
let ty = v.expr_type(&x86_int_lit(42));
assert!(ty.is_int());
}
#[test]
fn test_expr_validator_is_lvalue() {
let v = X86ExprValidator::new("c17", true);
assert!(v.is_lvalue(&x86_ident("x")));
assert!(!v.is_lvalue(&x86_int_lit(42)));
}
#[test]
fn test_expr_validator_null_pointer_constant() {
let v = X86ExprValidator::new("c17", true);
assert!(v.is_null_pointer_constant(&x86_int_lit(0)));
assert!(!v.is_null_pointer_constant(&x86_int_lit(42)));
}
#[test]
fn test_expr_validator_integer_constant_expr() {
let v = X86ExprValidator::new("c17", true);
assert!(v.is_integer_constant_expr(&x86_int_lit(42)));
let add_expr = x86_binary(BinaryOp::Add, x86_int_lit(1), x86_int_lit(2));
assert!(v.is_integer_constant_expr(&add_expr));
}
#[test]
fn test_expr_validator_evaluate_integer_constant() {
let v = X86ExprValidator::new("c17", true);
let expr = x86_binary(BinaryOp::Add, x86_int_lit(10), x86_int_lit(20));
assert_eq!(v.evaluate_integer_constant(&expr), Some(30));
let mul_expr = x86_binary(BinaryOp::Mul, x86_int_lit(5), x86_int_lit(6));
assert_eq!(v.evaluate_integer_constant(&mul_expr), Some(30));
let div_expr = x86_binary(BinaryOp::Div, x86_int_lit(30), x86_int_lit(5));
assert_eq!(v.evaluate_integer_constant(&div_expr), Some(6));
}
#[test]
fn test_expr_validator_division_by_zero() {
let v = X86ExprValidator::new("c17", true);
let expr = x86_binary(BinaryOp::Div, x86_int_lit(10), x86_int_lit(0));
assert_eq!(v.evaluate_integer_constant(&expr), None);
}
#[test]
fn test_expr_validator_check_condition() {
let v = X86ExprValidator::new("c17", true);
let mut d = X86DiagnosticEmitter::new();
assert!(v.check_condition(&x86_int_lit(1), &mut d));
assert!(v.check_condition(&x86_ident("p"), &mut d)); }
#[test]
fn test_expr_validator_binary_op_mod_float() {
let v = X86ExprValidator::new("c17", true);
let mut d = X86DiagnosticEmitter::new();
let lhs = Expr::FloatLiteral(3.14);
let rhs = Expr::FloatLiteral(2.0);
let ok = v.check_binary_op(BinaryOp::Mod, &lhs, &rhs, &mut d);
assert!(!ok);
}
#[test]
fn test_expr_validator_const_fold() {
let v = X86ExprValidator::new("c17", true);
let expr = x86_binary(BinaryOp::Sub, x86_int_lit(100), x86_int_lit(42));
assert_eq!(v.const_fold(&expr), Some(58));
}
#[test]
fn test_type_promotion_integer_promotion_lp64() {
let tp = X86TypePromotion::new_lp64();
let promoted = tp.integer_promotion(&char_ty());
assert!(promoted.is_int());
let promoted_short = tp.integer_promotion(&short_ty());
assert!(promoted_short.is_int());
}
#[test]
fn test_type_promotion_usual_arithmetic_float_dominates() {
let tp = X86TypePromotion::new_lp64();
let result = tp.usual_arithmetic_conversions(&int_ty(), &float_ty());
assert!(result.is_float());
}
#[test]
fn test_type_promotion_usual_arithmetic_double_dominates() {
let tp = X86TypePromotion::new_lp64();
let result = tp.usual_arithmetic_conversions(&float_ty(), &double_ty());
assert!(result.is_double());
}
#[test]
fn test_type_promotion_default_argument_promotion_float() {
let tp = X86TypePromotion::new_lp64();
let promoted = tp.default_argument_promotion(&float_ty());
assert!(promoted.is_double()); }
#[test]
fn test_type_promotion_default_argument_promotion_int() {
let tp = X86TypePromotion::new_lp64();
let promoted = tp.default_argument_promotion(&char_ty());
assert!(promoted.is_int());
}
#[test]
fn test_type_promotion_rank_lp64() {
let tp = X86TypePromotion::new_lp64();
assert!(tp.rank(&int_ty()) < tp.rank(&long_ty()));
assert!(tp.rank(&long_ty()) <= tp.rank(&long_ty()));
}
#[test]
fn test_type_promotion_width_lp64_vs_ilp32() {
let tp64 = X86TypePromotion::new_lp64();
let tp32 = X86TypePromotion::new_ilp32();
assert_eq!(tp64.width(&long_ty()), 64);
assert_eq!(tp32.width(&long_ty()), 32);
assert_eq!(tp64.width(&ptr_ty()), 64);
assert_eq!(tp32.width(&ptr_ty()), 32);
}
#[test]
fn test_type_promotion_size_constants() {
assert_eq!(X86_LP64_INT_SIZE, 4);
assert_eq!(X86_LP64_LONG_SIZE, 8);
assert_eq!(X86_LP64_POINTER_SIZE, 8);
assert_eq!(X86_ILP32_LONG_SIZE, 4);
assert_eq!(X86_ILP32_POINTER_SIZE, 4);
}
#[test]
fn test_implicit_conversion_identity() {
let ic = X86ImplicitConversion::new(true);
let result = ic.check_implicit_conversion(&int_ty(), &int_ty());
assert!(result.valid);
assert_eq!(result.rank, X86ConversionRank::ExactMatch);
}
#[test]
fn test_implicit_conversion_integral_promotion() {
let ic = X86ImplicitConversion::new(true);
let result = ic.check_implicit_conversion(&char_ty(), &int_ty());
assert!(result.valid);
assert_eq!(result.rank, X86ConversionRank::Promotion);
}
#[test]
fn test_implicit_conversion_float_promotion() {
let ic = X86ImplicitConversion::new(true);
let result = ic.check_implicit_conversion(&float_ty(), &double_ty());
assert!(result.valid);
assert_eq!(result.rank, X86ConversionRank::Promotion);
}
#[test]
fn test_implicit_conversion_narrowing_warning() {
let ic = X86ImplicitConversion::new(true);
let result = ic.check_implicit_conversion(&int_ty(), &char_ty());
assert!(result.valid);
assert!(result.warning.is_some());
}
#[test]
fn test_implicit_conversion_float_to_int() {
let ic = X86ImplicitConversion::new(true);
let result = ic.check_implicit_conversion(&float_ty(), &int_ty());
assert!(result.valid);
assert!(result.warning.is_some());
}
#[test]
fn test_implicit_conversion_int_to_pointer_match_width() {
let ic = X86ImplicitConversion::new(true);
let result = ic.check_implicit_conversion(&long_ty(), &ptr_ty());
assert!(result.valid);
}
#[test]
fn test_implicit_conversion_bool() {
let ic = X86ImplicitConversion::new(true);
let bool_ty = QualType::bool_();
let result = ic.check_implicit_conversion(&int_ty(), &bool_ty);
assert!(result.valid);
assert_eq!(result.rank, X86ConversionRank::Conversion);
}
#[test]
fn test_implicit_conversion_no_viable() {
let ic = X86ImplicitConversion::new(true);
let result = ic.check_implicit_conversion(&QualType::void(), &int_ty());
assert!(!result.valid);
}
#[test]
fn test_overload_resolution_basic() {
let mut o = X86OverloadResolution::new(true);
o.add_candidate(X86OverloadCandidate::new(
"f",
vec![int_ty()],
int_ty(),
false,
));
o.add_candidate(X86OverloadCandidate::new(
"f",
vec![double_ty()],
double_ty(),
false,
));
let mut d = X86DiagnosticEmitter::new();
let result = o.resolve(&[int_ty()], &mut d);
assert_eq!(result, Some(0)); }
#[test]
fn test_overload_resolution_promotion() {
let mut o = X86OverloadResolution::new(true);
o.add_candidate(X86OverloadCandidate::new(
"f",
vec![int_ty()],
int_ty(),
false,
));
o.add_candidate(X86OverloadCandidate::new(
"f",
vec![long_ty()],
long_ty(),
false,
));
let mut d = X86DiagnosticEmitter::new();
let result = o.resolve(&[char_ty()], &mut d);
assert_eq!(result, Some(0)); }
#[test]
fn test_overload_resolution_no_variable() {
let mut o = X86OverloadResolution::new(true);
o.add_candidate(X86OverloadCandidate::new(
"f",
vec![double_ty()],
double_ty(),
false,
));
let mut d = X86DiagnosticEmitter::new();
let result = o.resolve(&[int_ty()], &mut d);
assert_eq!(result, Some(0));
}
#[test]
fn test_overload_resolution_vararg_fallback() {
let mut o = X86OverloadResolution::new(true);
o.add_candidate({
let mut c = X86OverloadCandidate::new("f", vec![int_ty()], int_ty(), true);
c
});
let mut d = X86DiagnosticEmitter::new();
let result = o.resolve(&[int_ty(), float_ty()], &mut d);
assert_eq!(result, Some(0)); }
#[test]
fn test_overload_resolution_no_candidate() {
let mut o = X86OverloadResolution::new(true);
let mut d = X86DiagnosticEmitter::new();
let result = o.resolve(&[int_ty()], &mut d);
assert_eq!(result, None);
}
#[test]
fn test_overload_resolution_clear_candidates() {
let mut o = X86OverloadResolution::new(true);
o.add_candidate(X86OverloadCandidate::new(
"f",
vec![int_ty()],
int_ty(),
false,
));
assert_eq!(o.candidate_count(), 1);
o.clear();
assert_eq!(o.candidate_count(), 0);
}
#[test]
fn test_overload_resolution_ambiguity_detection() {
let mut o = X86OverloadResolution::new(true);
o.add_candidate(X86OverloadCandidate::new(
"f",
vec![int_ty()],
int_ty(),
false,
));
o.add_candidate(X86OverloadCandidate::new(
"f",
vec![int_ty()],
int_ty(),
false,
));
let ambiguous = o.detect_ambiguity(&[int_ty()]);
assert_eq!(ambiguous.len(), 1); }
#[test]
fn test_template_sema_validate_params_no_duplicates() {
let ts = X86TemplateSema::new(1024);
let mut d = X86DiagnosticEmitter::new();
let ts_ref = ts; let ts_with_params = X86TemplateSema {
template_params: vec![
X86TemplateParam::type_param("T"),
X86TemplateParam::type_param("U"),
],
..ts_ref
};
assert!(ts_with_params.validate_template_params(&mut d));
}
#[test]
fn test_template_sema_validate_params_duplicate() {
let ts = X86TemplateSema {
template_params: vec![
X86TemplateParam::type_param("T"),
X86TemplateParam::type_param("T"),
],
..X86TemplateSema::new(1024)
};
let mut d = X86DiagnosticEmitter::new();
assert!(!ts.validate_template_params(&mut d));
}
#[test]
fn test_template_sema_check_args() {
let ts = X86TemplateSema {
template_params: vec![X86TemplateParam::type_param("T")],
..X86TemplateSema::new(1024)
};
let mut d = X86DiagnosticEmitter::new();
let ok = ts.check_template_args(&[X86TemplateArg::Type(int_ty())], &mut d);
assert!(ok);
}
#[test]
fn test_template_sema_check_args_mismatch() {
let ts = X86TemplateSema {
template_params: vec![X86TemplateParam::type_param("T")],
..X86TemplateSema::new(1024)
};
let mut d = X86DiagnosticEmitter::new();
let ok = ts.check_template_args(&[X86TemplateArg::NonType(42)], &mut d);
assert!(!ok);
}
#[test]
fn test_template_sema_sfinae() {
let mut ts = X86TemplateSema::new(1024);
ts.enter_sfinae();
ts.record_sfinae_error("substitution failure");
assert!(ts.sfinae_has_errors());
let errors = ts.leave_sfinae();
assert_eq!(errors.len(), 1);
assert!(!ts.is_sfinae_active());
}
#[test]
fn test_template_sema_instantiation_depth() {
let mut ts = X86TemplateSema::new(5);
let mut d = X86DiagnosticEmitter::new();
assert!(ts.enter_instantiation("A", &mut d));
assert!(ts.enter_instantiation("B", &mut d));
assert!(ts.enter_instantiation("C", &mut d));
assert!(ts.enter_instantiation("D", &mut d));
assert!(ts.enter_instantiation("E", &mut d));
assert!(!ts.enter_instantiation("F", &mut d));
assert!(ts.depth.is_at_max());
ts.leave_instantiation();
assert_eq!(ts.current_depth(), 4);
}
#[test]
fn test_template_sema_instantiation_cache() {
let mut ts = X86TemplateSema::new(1024);
let key = ts.instantiation_key("MyTemplate", &[X86TemplateArg::Type(int_ty())]);
assert!(!ts.is_instantiated(&key));
ts.mark_instantiated(&key);
assert!(ts.is_instantiated(&key));
}
#[test]
fn test_template_sema_deduction_result() {
let result =
X86DeductionResult::success(vec![("T".into(), X86TemplateArg::Type(int_ty()))]);
assert!(result.success);
assert_eq!(result.deduced_args.len(), 1);
let failure = X86DeductionResult::failure("could not deduce T");
assert!(!failure.success);
assert_eq!(failure.failure_reason, Some("could not deduce T".into()));
}
#[test]
fn test_template_sema_classify_dependence() {
let mut ts = X86TemplateSema::new(1024);
ts.name_dependence
.insert("T".into(), X86NameDependence::TypeDependent);
assert_eq!(
ts.classify_dependence("T"),
X86NameDependence::TypeDependent
);
assert_eq!(ts.classify_dependence("x"), X86NameDependence::NonDependent);
}
#[test]
fn test_constexpr_eval_int_literal() {
let mut ce = X86ConstexprEval::new(512);
let result = ce.evaluate(&x86_int_lit(42));
assert_eq!(result, X86ConstValue::Int(42));
}
#[test]
fn test_constexpr_eval_binary_add() {
let mut ce = X86ConstexprEval::new(512);
let expr = x86_binary(BinaryOp::Add, x86_int_lit(10), x86_int_lit(20));
let result = ce.evaluate(&expr);
assert_eq!(result, X86ConstValue::Int(30));
}
#[test]
fn test_constexpr_eval_division_by_zero() {
let mut ce = X86ConstexprEval::new(512);
let expr = x86_binary(BinaryOp::Div, x86_int_lit(10), x86_int_lit(0));
let result = ce.evaluate(&expr);
assert_eq!(result, X86ConstValue::NonConstant);
assert!(ce
.errors
.iter()
.any(|e| matches!(e, X86ConstExprError::DivisionByZero)));
}
#[test]
fn test_constexpr_eval_unary_minus() {
let mut ce = X86ConstexprEval::new(512);
let expr = Expr::Unary(UnaryOp::Minus, Box::new(x86_int_lit(42)));
let result = ce.evaluate(&expr);
assert_eq!(result, X86ConstValue::Int(-42));
}
#[test]
fn test_constexpr_eval_conditional_true() {
let mut ce = X86ConstexprEval::new(512);
let expr = Expr::Conditional(
Box::new(x86_int_lit(1)),
Box::new(x86_int_lit(100)),
Box::new(x86_int_lit(200)),
);
let result = ce.evaluate(&expr);
assert_eq!(result, X86ConstValue::Int(100));
}
#[test]
fn test_constexpr_eval_conditional_false() {
let mut ce = X86ConstexprEval::new(512);
let expr = Expr::Conditional(
Box::new(x86_int_lit(0)),
Box::new(x86_int_lit(100)),
Box::new(x86_int_lit(200)),
);
let result = ce.evaluate(&expr);
assert_eq!(result, X86ConstValue::Int(200));
}
#[test]
fn test_constexpr_eval_enum_value() {
let mut ce = X86ConstexprEval::new(512);
ce.register_enum_value("RED", 0);
ce.register_enum_value("GREEN", 1);
let result = ce.evaluate(&x86_ident("RED"));
assert_eq!(result, X86ConstValue::Int(0));
let result2 = ce.evaluate(&x86_ident("GREEN"));
assert_eq!(result2, X86ConstValue::Int(1));
}
#[test]
fn test_constexpr_eval_is_constant() {
let mut ce = X86ConstexprEval::new(512);
assert!(ce.is_core_constant_expression(&x86_int_lit(42)));
assert!(!ce.is_core_constant_expression(&x86_ident("unknown")));
}
#[test]
fn test_constexpr_eval_if_constexpr() {
let mut ce = X86ConstexprEval::new(512);
let result = ce.eval_if_constexpr(&x86_int_lit(1));
assert_eq!(result, Some(true));
let result2 = ce.eval_if_constexpr(&x86_int_lit(0));
assert_eq!(result2, Some(false));
}
#[test]
fn test_constexpr_eval_valid_array_size() {
let mut ce = X86ConstexprEval::new(512);
assert!(ce.is_valid_array_size(&x86_int_lit(10)));
assert!(!ce.is_valid_array_size(&x86_int_lit(0)));
assert!(!ce.is_valid_array_size(&x86_int_lit(-1)));
}
#[test]
fn test_constexpr_eval_valid_bitfield_width() {
let mut ce = X86ConstexprEval::new(512);
assert!(ce.is_valid_bitfield_width(&x86_int_lit(8), 32));
assert!(!ce.is_valid_bitfield_width(&x86_int_lit(0), 32));
assert!(!ce.is_valid_bitfield_width(&x86_int_lit(64), 32));
}
#[test]
fn test_constexpr_eval_float_operations() {
let mut ce = X86ConstexprEval::new(512);
let f1 = Expr::FloatLiteral(3.5);
let f2 = Expr::FloatLiteral(2.0);
let add = x86_binary(BinaryOp::Add, f1, f2);
let result = ce.evaluate(&add);
assert_eq!(result, X86ConstValue::Float(5.5));
}
#[test]
fn test_constexpr_eval_cast_int_to_float() {
let mut ce = X86ConstexprEval::new(512);
let cast = Expr::Cast(float_ty(), Box::new(x86_int_lit(42)));
let result = ce.evaluate(&cast);
assert_eq!(result, X86ConstValue::Float(42.0));
}
#[test]
fn test_constexpr_eval_constexpr_var() {
let mut ce = X86ConstexprEval::new(512);
ce.register_constexpr_var("N", X86ConstValue::Int(100));
let result = ce.evaluate(&x86_ident("N"));
assert_eq!(result, X86ConstValue::Int(100));
}
#[test]
fn test_constexpr_eval_comparisons() {
let mut ce = X86ConstexprEval::new(512);
let lt = x86_binary(BinaryOp::Lt, x86_int_lit(1), x86_int_lit(2));
assert_eq!(ce.evaluate(<), X86ConstValue::Bool(true));
let gt = x86_binary(BinaryOp::Gt, x86_int_lit(1), x86_int_lit(2));
assert_eq!(ce.evaluate(>), X86ConstValue::Bool(false));
let eq = x86_binary(BinaryOp::Eq, x86_int_lit(42), x86_int_lit(42));
assert_eq!(ce.evaluate(&eq), X86ConstValue::Bool(true));
}
#[test]
fn test_constexpr_eval_const_value_display() {
assert_eq!(format!("{}", X86ConstValue::Int(42)), "42");
assert_eq!(format!("{}", X86ConstValue::Bool(true)), "true");
assert_eq!(format!("{}", X86ConstValue::NullPtr), "nullptr");
}
#[test]
fn test_constexpr_eval_shift_operations() {
let mut ce = X86ConstexprEval::new(512);
let shl = x86_binary(BinaryOp::Shl, x86_int_lit(1), x86_int_lit(3));
assert_eq!(ce.evaluate(&shl), X86ConstValue::Int(8));
let shr = x86_binary(BinaryOp::Shr, x86_int_lit(16), x86_int_lit(2));
assert_eq!(ce.evaluate(&shr), X86ConstValue::Int(4));
}
#[test]
fn test_constexpr_eval_bitwise() {
let mut ce = X86ConstexprEval::new(512);
let and = x86_binary(BinaryOp::And, x86_int_lit(0xFF), x86_int_lit(0x0F));
assert_eq!(ce.evaluate(&and), X86ConstValue::Int(0x0F));
let or = x86_binary(BinaryOp::Or, x86_int_lit(0xF0), x86_int_lit(0x0F));
assert_eq!(ce.evaluate(&or), X86ConstValue::Int(0xFF));
let xor = x86_binary(BinaryOp::Xor, x86_int_lit(0xFF), x86_int_lit(0x0F));
assert_eq!(ce.evaluate(&xor), X86ConstValue::Int(0xF0));
}
#[test]
fn test_deep_sema_creation() {
let sema = make_sema();
assert!(sema.is_64bit);
assert!(sema.errors.is_empty());
}
#[test]
fn test_deep_sema_32bit_creation() {
let sema = make_sema_32();
assert!(!sema.is_64bit);
}
#[test]
fn test_deep_sema_empty_tu() {
let mut sema = make_sema();
let tu = x86_make_tu(vec![]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
assert!(sema.errors.is_empty());
}
#[test]
fn test_deep_sema_simple_variable() {
let mut sema = make_sema();
let var = Decl::Variable(VarDecl {
name: "x".into(),
ty: int_ty(),
init: None,
linkage: Linkage::External,
is_global: true,
is_extern: false,
is_static: false,
});
let tu = x86_make_tu(vec![var]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
}
#[test]
fn test_deep_sema_void_variable_error() {
let mut sema = make_sema();
let var = Decl::Variable(VarDecl {
name: "v".into(),
ty: void_ty(),
init: None,
linkage: Linkage::External,
is_global: true,
is_extern: false,
is_static: false,
});
let tu = x86_make_tu(vec![var]);
let ok = sema.analyze_tu(&tu);
assert!(!ok);
}
#[test]
fn test_deep_sema_simple_function() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "f".into(),
ret_ty: int_ty(),
params: vec![],
body: Some(CompoundStmt {
stmts: vec![x86_return(Some(x86_int_lit(42)))],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let ok = sema.analyze_function(&func);
assert!(ok);
}
#[test]
fn test_deep_sema_nonvoid_no_return_warning() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "f".into(),
ret_ty: int_ty(),
params: vec![],
body: Some(CompoundStmt { stmts: vec![] }),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
sema.analyze_function(&func);
assert!(sema.warnings.iter().any(|w| w.contains("missing return")));
}
#[test]
fn test_deep_sema_register_enum() {
let mut sema = make_sema();
let ed = Decl::Enum(EnumDecl {
name: "Color".into(),
variants: vec![
EnumVariant::new("RED", Some(0)),
EnumVariant::new("GREEN", Some(1)),
EnumVariant::new("BLUE", Some(2)),
],
underlying: int_ty(),
});
let tu = x86_make_tu(vec![ed]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
assert_eq!(sema.constexpr_eval.enum_count(), 3);
}
#[test]
fn test_deep_sema_dump_symbol_table() {
let mut sema = make_sema();
let var = Decl::Variable(VarDecl {
name: "counter".into(),
ty: int_ty(),
init: Some(Box::new(x86_int_lit(0))),
linkage: Linkage::External,
is_global: true,
is_extern: false,
is_static: false,
});
let tu = x86_make_tu(vec![var]);
sema.analyze_tu(&tu);
let dump = sema.dump_symbol_table();
assert!(dump.contains("counter"));
}
#[test]
fn test_deep_sema_evaluate_constexpr() {
let mut sema = make_sema();
let expr = x86_binary(BinaryOp::Add, x86_int_lit(1), x86_int_lit(2));
let result = sema.evaluate_constexpr(&expr);
assert_eq!(result, X86ConstValue::Int(3));
}
#[test]
fn test_deep_sema_if_condition_non_scalar() {
let mut sema = make_sema();
let s = StructDecl {
name: "S".into(),
fields: vec![FieldDecl::new("x", int_ty())],
is_union: false,
};
let tu = x86_make_tu(vec![Decl::Struct(s)]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
}
#[test]
fn test_deep_sema_complex_expression() {
let mut sema = make_sema();
let inner = x86_binary(BinaryOp::Add, x86_int_lit(10), x86_int_lit(5));
let outer = x86_binary(BinaryOp::Mul, inner, x86_int_lit(2));
let result = sema.evaluate_constexpr(&outer);
assert_eq!(result, X86ConstValue::Int(30));
}
#[test]
fn test_make_x86_64_deep_sema() {
let sema = make_x86_64_deep_sema();
assert!(sema.is_64bit);
}
#[test]
fn test_make_x86_32_deep_sema() {
let sema = make_x86_32_deep_sema();
assert!(!sema.is_64bit);
}
#[test]
fn test_analyze_x86_tu() {
let var = Decl::Variable(VarDecl {
name: "x".into(),
ty: int_ty(),
init: None,
linkage: Linkage::External,
is_global: true,
is_extern: false,
is_static: false,
});
let tu = x86_make_tu(vec![var]);
let (ok, errors, _warnings) = analyze_x86_tu(&tu);
assert!(ok);
assert!(errors.is_empty());
}
#[test]
fn test_analyze_x86_function() {
let func = FunctionDecl {
name: "f".into(),
ret_ty: int_ty(),
params: vec![],
body: Some(CompoundStmt {
stmts: vec![x86_return(Some(x86_int_lit(0)))],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let (ok, errors, _warnings) = analyze_x86_function(&func);
assert!(ok);
assert!(errors.is_empty());
}
#[test]
fn test_contains_x86_message() {
let errors = vec!["type mismatch".to_string(), "redefinition".to_string()];
assert!(contains_x86_message(&errors, "type mismatch"));
assert!(!contains_x86_message(&errors, "overflow"));
}
#[test]
fn test_x86_make_tu() {
let tu = x86_make_tu(vec![]);
assert!(tu.decls.is_empty());
assert_eq!(tu.filename, "test.c");
}
#[test]
fn test_conversion_rank_ordering() {
assert!(X86ConversionRank::ExactMatch < X86ConversionRank::Promotion);
assert!(X86ConversionRank::Promotion < X86ConversionRank::Conversion);
assert!(X86ConversionRank::Conversion < X86ConversionRank::UserDefined);
assert!(X86ConversionRank::UserDefined < X86ConversionRank::Ellipsis);
assert!(X86ConversionRank::Ellipsis < X86ConversionRank::NotViable);
}
#[test]
fn test_conversion_rank_display() {
assert_eq!(format!("{}", X86ConversionRank::ExactMatch), "ExactMatch");
assert_eq!(format!("{}", X86ConversionRank::Promotion), "Promotion");
assert_eq!(format!("{}", X86ConversionRank::NotViable), "NotViable");
}
#[test]
fn test_conversion_result_invalid() {
let result = X86ConversionResult::invalid(X86ConvKind::PointerConversion);
assert!(!result.valid);
assert_eq!(result.rank, X86ConversionRank::NotViable);
}
#[test]
fn test_conversion_result_with_warning() {
let result = X86ConversionResult::valid(
X86ConvKind::IntegralConversion,
X86ConversionRank::Conversion,
)
.with_warning("data loss".into());
assert!(result.valid);
assert!(result.warning.is_some());
}
#[test]
fn test_scope_kind_display() {
assert_eq!(format!("{}", X86ScopeKind::File), "file");
assert_eq!(format!("{}", X86ScopeKind::Function), "function");
assert_eq!(format!("{}", X86ScopeKind::Class), "class");
assert_eq!(format!("{}", X86ScopeKind::TemplateParam), "template-param");
}
#[test]
fn test_value_category_display() {
assert_eq!(format!("{}", X86ValueCategory::LValue), "lvalue");
assert_eq!(format!("{}", X86ValueCategory::RValue), "rvalue");
assert_eq!(format!("{}", X86ValueCategory::XValue), "xvalue");
}
#[test]
fn test_diag_category_display() {
assert_eq!(format!("{}", X86DiagCategory::Template), "template");
assert_eq!(format!("{}", X86DiagCategory::ODR), "odr");
}
#[test]
fn test_diag_severity_display() {
assert_eq!(format!("{}", X86DiagSeverity::Warning), "warning");
assert_eq!(format!("{}", X86DiagSeverity::Error), "error");
assert_eq!(format!("{}", X86DiagSeverity::Fatal), "fatal error");
}
#[test]
fn test_constexpr_error_display() {
assert!(format!("{}", X86ConstExprError::DivisionByZero).contains("division by zero"));
assert!(format!("{}", X86ConstExprError::Overflow).contains("overflow"));
}
#[test]
fn test_conv_kind_display() {
assert!(format!("{}", X86ConvKind::LvalueToRvalue).contains("lvalue"));
assert!(format!("{}", X86ConvKind::ArrayToPointerDecay).contains("array"));
}
#[test]
fn test_x86_vector_constants() {
assert_eq!(X86_MMX_WIDTH, 64);
assert_eq!(X86_SSE_WIDTH, 128);
assert_eq!(X86_AVX_WIDTH, 256);
assert_eq!(X86_AVX512_WIDTH, 512);
}
#[test]
fn test_x86_size_constants() {
assert_eq!(X86_LP64_CHAR_SIZE, 1);
assert_eq!(X86_LP64_SHORT_SIZE, 2);
assert_eq!(X86_LP64_INT_SIZE, 4);
assert_eq!(X86_LP64_LONG_SIZE, 8);
assert_eq!(X86_LP64_DOUBLE_SIZE, 8);
}
#[test]
fn test_type_promotion_default() {
let tp = X86TypePromotion::default();
assert!(tp.is_64bit);
}
#[test]
fn test_scope_manager_default() {
let sm = X86ScopeManager::default();
assert_eq!(sm.scope_depth(), 1);
assert!(sm.at_file_scope());
}
#[test]
fn test_decl_validator_default() {
let dv = X86DeclValidator::default();
assert!(dv.is_64bit);
}
#[test]
fn test_diag_emitter_default() {
let de = X86DiagnosticEmitter::default();
assert!(de.diagnostics.is_empty());
}
#[test]
fn test_constexpr_eval_default() {
let ce = X86ConstexprEval::default();
assert_eq!(ce.enum_count(), 0);
}
#[test]
fn test_overload_resolution_default() {
let o = X86OverloadResolution::default();
assert_eq!(o.candidate_count(), 0);
}
#[test]
fn test_template_sema_default() {
let ts = X86TemplateSema::default();
assert_eq!(ts.current_depth(), 0);
assert!(!ts.is_sfinae_active());
}
#[test]
fn test_overload_candidate_with_template() {
let c = X86OverloadCandidate::new("f", vec![int_ty()], int_ty(), false)
.with_template(vec!["T".into()]);
assert!(c.is_template);
assert_eq!(c.template_params.len(), 1);
}
#[test]
fn test_instantiation_depth_default() {
let depth = X86InstantiationDepth::default();
assert_eq!(depth.current, 0);
assert_eq!(depth.maximum, 1024);
}
#[test]
fn test_stress_many_declarations() {
let mut sm = X86ScopeManager::new();
let mut d = X86DiagnosticEmitter::new();
for i in 0..1000 {
sm.declare_variable(&format!("var_{}", i), int_ty(), false, false, &mut d);
}
assert_eq!(sm.total_declarations(), 1000);
assert!(sm.lookup("var_500").is_some());
}
#[test]
fn test_stress_deep_scope_nesting() {
let mut sm = X86ScopeManager::new();
for i in 0..100 {
sm.enter_scope(X86ScopeKind::Block);
}
assert_eq!(sm.scope_depth(), 101);
for _ in 0..100 {
sm.leave_scope();
}
assert_eq!(sm.scope_depth(), 1);
}
#[test]
fn test_stress_many_diagnostics() {
let mut d = X86DiagnosticEmitter::new();
for i in 0..100 {
d.error_at(format!("error {}", i), Some("test.c"), i as u32, 1);
}
assert!(d.error_count() > 0);
assert!(d.should_fail());
}
#[test]
fn test_stress_many_constexpr_evals() {
let mut ce = X86ConstexprEval::new(512);
for i in 0..1000 {
let expr = x86_binary(BinaryOp::Mul, x86_int_lit(i), x86_int_lit(i));
let result = ce.evaluate(&expr);
assert_eq!(result, X86ConstValue::Int((i * i) as i128));
}
}
#[test]
fn test_stress_many_overload_candidates() {
let mut o = X86OverloadResolution::new(true);
for i in 0..50 {
o.add_candidate(X86OverloadCandidate::new(
"f",
vec![int_ty()],
int_ty(),
false,
));
}
assert_eq!(o.candidate_count(), 50);
let mut d = X86DiagnosticEmitter::new();
let ambiguous = o.detect_ambiguity(&[int_ty()]);
assert!(ambiguous.len() > 0);
}
#[test]
fn test_deep_sema_all_components_initialized() {
let sema = make_sema();
assert!(sema.scope_manager.at_file_scope());
assert_eq!(sema.constexpr_eval.enum_count(), 0);
assert!(sema.overload_resolution.candidates.is_empty());
assert_eq!(sema.template_sema.current_depth(), 0);
}
#[test]
fn test_no_panic_on_empty_diagnostic() {
let d = X86DiagnosticEmitter::new();
let formatted = d.format_all();
assert!(formatted.is_empty() || !formatted.contains("error"));
}
#[test]
fn test_no_panic_on_empty_scope_lookup() {
let sm = X86ScopeManager::new();
assert!(sm.lookup("nonexistent").is_none());
}
#[test]
fn test_no_panic_empty_overload_resolution() {
let mut o = X86OverloadResolution::new(true);
let mut d = X86DiagnosticEmitter::new();
let result = o.resolve(&[], &mut d);
assert_eq!(result, None);
}
#[test]
fn test_x86_make_function_helper() {
let f = x86_make_function("test", int_ty(), vec![int_ty(), float_ty()], None);
assert_eq!(f.name, "test");
assert_eq!(f.params.len(), 2);
assert!(f.body.is_none());
}
#[test]
fn test_x86_make_variable_helper() {
let v = x86_make_variable("x", int_ty(), Some(x86_int_lit(42)));
assert_eq!(v.name, "x");
assert!(v.init.is_some());
assert!(v.is_global);
}
#[test]
fn test_scope_odr_function_duplicate() {
let mut sm = X86ScopeManager::new();
sm.enforce_odr = true;
let mut d = X86DiagnosticEmitter::new();
sm.declare_function(
"dup_func",
int_ty(),
vec![],
false,
false,
"external",
&mut d,
);
sm.declare_function(
"dup_func",
int_ty(),
vec![],
false,
false,
"external",
&mut d,
);
assert!(
d.error_count() > 0
|| !sm
.odr_functions
.get("dup_func")
.map_or(true, |v| v.len() <= 1)
);
}
#[test]
fn test_scope_tentative_variable_odr() {
let mut sm = X86ScopeManager::new();
sm.enforce_odr = true;
let mut d = X86DiagnosticEmitter::new();
sm.declare_variable("tent", int_ty(), false, false, &mut d);
sm.declare_variable("tent", int_ty(), false, false, &mut d);
assert!(sm.odr_variables.contains_key("tent"));
}
#[test]
fn test_scope_template_param_scope() {
let mut sm = X86ScopeManager::new();
sm.in_template_params = true;
sm.enter_scope(X86ScopeKind::TemplateParam);
let mut d = X86DiagnosticEmitter::new();
sm.declare_variable("T", int_ty(), false, false, &mut d);
assert!(sm.lookup("T").is_some());
sm.leave_scope();
assert!(sm.lookup("T").is_none());
}
#[test]
fn test_scope_prototype_scope() {
let mut sm = X86ScopeManager::new();
sm.enter_scope(X86ScopeKind::FunctionPrototype);
let mut d = X86DiagnosticEmitter::new();
sm.declare_variable("param", int_ty(), false, false, &mut d);
assert!(sm.lookup("param").is_some());
sm.leave_scope();
assert!(sm.lookup("param").is_none());
}
#[test]
fn test_scope_class_scope() {
let mut sm = X86ScopeManager::new();
sm.enter_scope(X86ScopeKind::Class);
let mut d = X86DiagnosticEmitter::new();
sm.declare_variable("member", int_ty(), false, false, &mut d);
assert!(sm.lookup("member").is_some());
sm.leave_scope();
}
#[test]
fn test_scope_lambda_scope() {
let mut sm = X86ScopeManager::new();
sm.enter_scope(X86ScopeKind::Lambda);
let mut d = X86DiagnosticEmitter::new();
sm.declare_variable("captured", int_ty(), false, false, &mut d);
assert!(sm.in_scope_kind(X86ScopeKind::Lambda));
sm.leave_scope();
assert!(!sm.in_scope_kind(X86ScopeKind::Lambda));
}
#[test]
fn test_scope_try_catch_scopes() {
let mut sm = X86ScopeManager::new();
sm.enter_scope(X86ScopeKind::TryBlock);
assert!(sm.in_scope_kind(X86ScopeKind::TryBlock));
sm.enter_scope(X86ScopeKind::CatchBlock);
assert!(sm.in_scope_kind(X86ScopeKind::CatchBlock));
assert_eq!(sm.scope_depth(), 3);
sm.leave_scope();
sm.leave_scope();
assert_eq!(sm.scope_depth(), 1);
}
#[test]
fn test_scope_redeclaration_merge_extern() {
let mut sm = X86ScopeManager::new();
let mut d = X86DiagnosticEmitter::new();
sm.declare_variable("x", int_ty(), true, false, &mut d);
assert!(sm.declare_variable("x", int_ty(), false, true, &mut d));
assert_eq!(d.error_count(), 0);
}
#[test]
fn test_scope_typedef_lookup() {
let mut sm = X86ScopeManager::new();
let mut d = X86DiagnosticEmitter::new();
sm.declare_typedef("size_t", QualType::ulong(), &mut d);
assert!(sm.lookup_type("size_t").is_some());
}
#[test]
fn test_scope_struct_tag_lookup() {
let mut sm = X86ScopeManager::new();
let mut d = X86DiagnosticEmitter::new();
sm.declare_struct_tag("MyStruct", false, &mut d);
assert!(sm.lookup_type("MyStruct").is_some());
}
#[test]
fn test_scope_enum_constant_lookup() {
let mut sm = X86ScopeManager::new();
let mut d = X86DiagnosticEmitter::new();
sm.declare_enum_constant("RED", 0, &mut d);
let entry = sm.lookup("RED");
assert!(entry.is_some());
}
#[test]
fn test_scope_qualified_lookup() {
let mut sm = X86ScopeManager::new();
sm.register_namespace("std");
let mut d = X86DiagnosticEmitter::new();
sm.declare_in_namespace(
"std",
X86ScopeEntry::Variable {
name: "cout".into(),
ty: int_ty(),
is_extern: true,
is_static: false,
declared_line: 0,
declared_col: 0,
declared_file: None,
},
);
assert!(sm.qualified_lookup("std", "cout").is_some());
assert!(sm.qualified_lookup("std", "nonexistent").is_none());
}
#[test]
fn test_scope_multi_level_namespace() {
let mut sm = X86ScopeManager::new();
sm.enter_namespace("outer");
sm.enter_namespace("inner");
assert_eq!(sm.current_namespace_string(), "outer::inner");
sm.leave_namespace();
assert_eq!(sm.current_namespace_string(), "outer");
sm.leave_namespace();
assert_eq!(sm.current_namespace_string(), "");
}
#[test]
fn test_scope_file_scope_names() {
let mut sm = X86ScopeManager::new();
let mut d = X86DiagnosticEmitter::new();
sm.declare_variable("global_a", int_ty(), false, false, &mut d);
sm.declare_variable("global_b", float_ty(), false, false, &mut d);
sm.enter_scope(X86ScopeKind::Block);
sm.declare_variable("local_x", int_ty(), false, false, &mut d);
let file_names = sm.file_scope_names();
assert!(file_names.contains(&"global_a".to_string()));
assert!(file_names.contains(&"global_b".to_string()));
assert!(!file_names.contains(&"local_x".to_string()));
sm.leave_scope();
}
#[test]
fn test_scope_shadow_suppressed() {
let mut sm = X86ScopeManager::new();
sm.warn_shadow = false;
let mut d = X86DiagnosticEmitter::new();
sm.declare_variable("x", int_ty(), false, false, &mut d);
sm.enter_scope(X86ScopeKind::Block);
sm.declare_variable("x", float_ty(), false, false, &mut d);
assert_eq!(d.warning_count(), 0);
sm.leave_scope();
}
#[test]
fn test_decl_validator_function_param_void_sole() {
let mut v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let func = FunctionDecl {
name: "f".into(),
ret_ty: int_ty(),
params: vec![VarDecl::new("", void_ty())],
body: None,
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let ok = v.check_function_decl(&func, &mut d);
assert!(ok);
}
#[test]
fn test_decl_validator_function_param_void_not_sole() {
let mut v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let func = FunctionDecl {
name: "f".into(),
ret_ty: int_ty(),
params: vec![VarDecl::new("", void_ty()), VarDecl::new("", int_ty())],
body: None,
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let ok = v.check_function_decl(&func, &mut d);
assert!(!ok);
}
#[test]
fn test_decl_validator_array_size_negative() {
let v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let ok = v.check_array_size(&x86_int_lit(-1), &mut d);
assert!(!ok);
}
#[test]
fn test_decl_validator_array_size_zero() {
let v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let ok = v.check_array_size(&x86_int_lit(0), &mut d);
assert!(!ok);
}
#[test]
fn test_decl_validator_array_size_valid() {
let v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let ok = v.check_array_size(&x86_int_lit(100), &mut d);
assert!(ok);
}
#[test]
fn test_decl_validator_call_args_too_many() {
let v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let ok = v.check_call_args(
"f",
&[int_ty()],
&[x86_int_lit(1), x86_int_lit(2)],
false,
&mut d,
);
assert!(!ok);
}
#[test]
fn test_decl_validator_call_args_too_few() {
let v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let ok = v.check_call_args(
"f",
&[int_ty(), float_ty()],
&[x86_int_lit(1)],
false,
&mut d,
);
assert!(!ok);
}
#[test]
fn test_decl_validator_call_args_vararg_ok() {
let v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let ok = v.check_call_args(
"printf",
&[QualType::const_char_ptr()],
&[x86_int_lit(1), x86_int_lit(2), x86_int_lit(3)],
true, &mut d,
);
assert!(ok);
}
#[test]
fn test_decl_validator_function_not_defined_warning() {
let mut v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let func = FunctionDecl {
name: "never_defined".into(),
ret_ty: int_ty(),
params: vec![],
body: None,
is_vararg: false,
linkage: Linkage::Internal, is_inline: false,
is_noreturn: false,
};
v.check_function_decl(&func, &mut d);
assert!(d.warning_count() > 0);
}
#[test]
fn test_decl_validator_noreturn_nonvoid_ok() {
let mut v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let func = FunctionDecl {
name: "abort_like".into(),
ret_ty: int_ty(),
params: vec![],
body: None,
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: true,
};
let ok = v.check_function_decl(&func, &mut d);
assert!(ok);
}
#[test]
fn test_decl_validator_compute_linkage() {
let v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
assert_eq!(v.compute_linkage(true, false), "internal");
assert_eq!(v.compute_linkage(false, true), "external");
assert_eq!(v.compute_linkage(false, false), "external");
}
#[test]
fn test_expr_validator_char_literal_type() {
let v = X86ExprValidator::new("c17", true);
let ty = v.expr_type(&Expr::CharLiteral('A'));
assert!(ty.is_char());
}
#[test]
fn test_expr_validator_string_literal_type() {
let v = X86ExprValidator::new("c17", true);
let ty = v.expr_type(&Expr::StringLiteral("hello".into()));
assert!(ty.is_pointer());
}
#[test]
fn test_expr_validator_sizeof_expr_type() {
let v = X86ExprValidator::new("c17", true);
let ty = v.expr_type(&Expr::SizeOf(Box::new(x86_int_lit(42))));
assert!(ty.is_integer());
}
#[test]
fn test_expr_validator_value_category() {
let v = X86ExprValidator::new("c17", true);
assert_eq!(v.value_category(&x86_ident("x")), X86ValueCategory::LValue);
assert_eq!(v.value_category(&x86_int_lit(42)), X86ValueCategory::RValue);
}
#[test]
fn test_expr_validator_evaluate_unary_minus() {
let v = X86ExprValidator::new("c17", true);
let expr = Expr::Unary(UnaryOp::Minus, Box::new(x86_int_lit(5)));
assert_eq!(v.evaluate_integer_constant(&expr), Some(-5));
}
#[test]
fn test_expr_validator_evaluate_bitwise() {
let v = X86ExprValidator::new("c17", true);
let and_expr = x86_binary(BinaryOp::And, x86_int_lit(0xFF), x86_int_lit(0xF0));
assert_eq!(v.evaluate_integer_constant(&and_expr), Some(0xF0));
let or_expr = x86_binary(BinaryOp::Or, x86_int_lit(0x0F), x86_int_lit(0xF0));
assert_eq!(v.evaluate_integer_constant(&or_expr), Some(0xFF));
}
#[test]
fn test_expr_validator_evaluate_shifts() {
let v = X86ExprValidator::new("c17", true);
let shl = x86_binary(BinaryOp::Shl, x86_int_lit(1), x86_int_lit(10));
assert_eq!(v.evaluate_integer_constant(&shl), Some(1024));
let shr = x86_binary(BinaryOp::Shr, x86_int_lit(1024), x86_int_lit(10));
assert_eq!(v.evaluate_integer_constant(&shr), Some(1));
}
#[test]
fn test_expr_validator_evaluate_comparisons() {
let v = X86ExprValidator::new("c17", true);
let eq = x86_binary(BinaryOp::Eq, x86_int_lit(5), x86_int_lit(5));
assert_eq!(v.evaluate_integer_constant(&eq), Some(1));
let ne = x86_binary(BinaryOp::Ne, x86_int_lit(5), x86_int_lit(3));
assert_eq!(v.evaluate_integer_constant(&ne), Some(1));
let lt = x86_binary(BinaryOp::Lt, x86_int_lit(3), x86_int_lit(5));
assert_eq!(v.evaluate_integer_constant(<), Some(1));
let gt = x86_binary(BinaryOp::Gt, x86_int_lit(3), x86_int_lit(5));
assert_eq!(v.evaluate_integer_constant(>), Some(0));
}
#[test]
fn test_expr_validator_evaluate_logical() {
let v = X86ExprValidator::new("c17", true);
let land = x86_binary(BinaryOp::LogicAnd, x86_int_lit(1), x86_int_lit(1));
assert_eq!(v.evaluate_integer_constant(&land), Some(1));
let land2 = x86_binary(BinaryOp::LogicAnd, x86_int_lit(0), x86_int_lit(1));
assert_eq!(v.evaluate_integer_constant(&land2), Some(0));
let lor = x86_binary(BinaryOp::LogicOr, x86_int_lit(0), x86_int_lit(1));
assert_eq!(v.evaluate_integer_constant(&lor), Some(1));
}
#[test]
fn test_expr_validator_evaluate_conditional() {
let v = X86ExprValidator::new("c17", true);
let expr = Expr::Conditional(
Box::new(x86_int_lit(1)),
Box::new(x86_int_lit(100)),
Box::new(x86_int_lit(200)),
);
assert_eq!(v.evaluate_integer_constant(&expr), Some(100));
let expr2 = Expr::Conditional(
Box::new(x86_int_lit(0)),
Box::new(x86_int_lit(100)),
Box::new(x86_int_lit(200)),
);
assert_eq!(v.evaluate_integer_constant(&expr2), Some(200));
}
#[test]
fn test_expr_validator_check_binary_op_pointer_add() {
let v = X86ExprValidator::new("c17", true);
let mut d = X86DiagnosticEmitter::new();
let ptr = Expr::Ident("p".into());
let int = x86_int_lit(4);
let ok = v.check_binary_op(BinaryOp::Add, &ptr, &int, &mut d);
assert!(ok);
}
#[test]
fn test_expr_validator_check_binary_op_double_pointer_add() {
let v = X86ExprValidator::new("c17", true);
let mut d = X86DiagnosticEmitter::new();
let p1 = Expr::Ident("p1".into());
let p2 = Expr::Ident("p2".into());
let ok = v.check_binary_op(BinaryOp::Add, &p1, &p2, &mut d);
assert!(!ok); }
#[test]
fn test_expr_validator_check_binary_op_pointer_sub() {
let v = X86ExprValidator::new("c17", true);
let mut d = X86DiagnosticEmitter::new();
let p1 = Expr::Ident("p1".into());
let p2 = Expr::Ident("p2".into());
let ok = v.check_binary_op(BinaryOp::Sub, &p1, &p2, &mut d);
assert!(ok); }
#[test]
fn test_expr_validator_x86_integer_promotion_char() {
let v = X86ExprValidator::new("c17", true);
let promoted = v.x86_integer_promotion(&QualType::char());
assert!(promoted.is_int());
}
#[test]
fn test_expr_validator_x86_usual_arithmetic_lp64() {
let v = X86ExprValidator::new("c17", true);
let result = v.x86_usual_arithmetic_conversion(&QualType::long(), &QualType::int());
assert!(result.is_long());
}
#[test]
fn test_type_promotion_rank_ordering_lp64() {
let tp = X86TypePromotion::new_lp64();
assert!(tp.rank(&QualType::bool_()) < tp.rank(&QualType::char()));
assert!(tp.rank(&QualType::char()) < tp.rank(&QualType::short()));
assert!(tp.rank(&QualType::short()) < tp.rank(&QualType::int()));
assert!(tp.rank(&QualType::int()) < tp.rank(&QualType::long()));
}
#[test]
fn test_type_promotion_int_long_same_on_lp64() {
let tp = X86TypePromotion::new_lp64();
assert_eq!(tp.width(&QualType::long()), 64);
assert_eq!(tp.width(&QualType::long_long()), 64);
}
#[test]
fn test_type_promotion_int_long_diff_on_ilp32() {
let tp = X86TypePromotion::new_ilp32();
assert_eq!(tp.width(&QualType::int()), 32);
assert_eq!(tp.width(&QualType::long()), 32);
assert_eq!(tp.width(&QualType::long_long()), 64);
}
#[test]
fn test_type_promotion_float_double_usual() {
let tp = X86TypePromotion::new_lp64();
let result = tp.usual_arithmetic_conversions(&QualType::float_(), &QualType::int());
assert!(result.is_float()); }
#[test]
fn test_type_promotion_double_long_usual() {
let tp = X86TypePromotion::new_lp64();
let result = tp.usual_arithmetic_conversions(&QualType::double_(), &QualType::long());
assert!(result.is_double()); }
#[test]
fn test_type_promotion_long_uint_usual() {
let tp = X86TypePromotion::new_lp64();
let result = tp.usual_arithmetic_conversions(&QualType::long(), &QualType::uint());
assert_eq!(tp.rank(&QualType::long()), 5);
assert_eq!(tp.rank(&QualType::uint()), 3);
assert!(result.is_long());
}
#[test]
fn test_type_promotion_to_unsigned() {
let tp = X86TypePromotion::new_lp64();
let u = tp.to_unsigned(&QualType::int());
assert!(u.is_unsigned());
let u2 = tp.to_unsigned(&QualType::char());
assert!(u2.is_unsigned());
}
#[test]
fn test_type_promotion_default_arg_float_to_double() {
let tp = X86TypePromotion::new_lp64();
let result = tp.default_argument_promotion(&QualType::float_());
assert!(result.is_double());
}
#[test]
fn test_type_promotion_default_arg_char_to_int() {
let tp = X86TypePromotion::new_lp64();
let result = tp.default_argument_promotion(&QualType::char());
assert!(result.is_int());
}
#[test]
fn test_type_promotion_default_arg_double_unchanged() {
let tp = X86TypePromotion::new_lp64();
let result = tp.default_argument_promotion(&QualType::double_());
assert!(result.is_double());
}
#[test]
fn test_implicit_conversion_lvalue_to_rvalue() {
let ic = X86ImplicitConversion::new(true);
let result = ic.check_implicit_conversion(&int_ty(), &int_ty());
assert!(result.valid);
assert!(result.is_exact_match());
}
#[test]
fn test_implicit_conversion_array_to_pointer() {
let ic = X86ImplicitConversion::new(true);
let arr_ty = QualType::array(int_ty(), 10);
let ptr_ty = ptr_ty();
let result = ic.check_implicit_conversion(&arr_ty, &ptr_ty);
assert!(result.valid);
}
#[test]
fn test_implicit_conversion_function_to_pointer() {
let ic = X86ImplicitConversion::new(true);
let func_ty = QualType::function(int_ty(), vec![]);
let ptr_ty = ptr_ty();
let result = ic.check_implicit_conversion(&func_ty, &ptr_ty);
assert!(result.valid);
}
#[test]
fn test_implicit_conversion_integral_to_float() {
let ic = X86ImplicitConversion::new(true);
let result = ic.check_implicit_conversion(&int_ty(), &float_ty());
assert!(result.valid);
assert_eq!(result.kind, X86ConvKind::FloatingIntegralConversion);
}
#[test]
fn test_implicit_conversion_pointer_to_pointer_incompatible() {
let ic = X86ImplicitConversion::new(true);
let result =
ic.check_implicit_conversion(&ptr_ty(), &QualType::pointer_to(QualType::char()));
assert!(!result.valid);
}
#[test]
fn test_implicit_conversion_void_ptr_to_any_ptr() {
let ic = X86ImplicitConversion::new(true);
let result =
ic.check_implicit_conversion(&QualType::pointer_to(QualType::void()), &ptr_ty());
assert!(result.valid);
}
#[test]
fn test_implicit_conversion_any_ptr_to_void_ptr() {
let ic = X86ImplicitConversion::new(true);
let result =
ic.check_implicit_conversion(&ptr_ty(), &QualType::pointer_to(QualType::void()));
assert!(result.valid);
}
#[test]
fn test_implicit_conversion_int_to_bool() {
let ic = X86ImplicitConversion::new(true);
let result = ic.check_implicit_conversion(&int_ty(), &QualType::bool_());
assert!(result.valid);
assert_eq!(result.kind, X86ConvKind::BooleanConversion);
}
#[test]
fn test_implicit_conversion_long_long_to_int_narrowing() {
let ic = X86ImplicitConversion::new(true);
let result = ic.check_implicit_conversion(&QualType::long_long(), &int_ty());
assert!(result.valid);
assert!(result.warning.is_some());
}
#[test]
fn test_implicit_conversion_float_to_double_promotion() {
let ic = X86ImplicitConversion::new(true);
let result = ic.check_implicit_conversion(&float_ty(), &double_ty());
assert!(result.valid);
assert!(result.is_promotion());
}
#[test]
fn test_implicit_conversion_is_better_sequence() {
let ic = X86ImplicitConversion::new(true);
assert!(ic.is_better_sequence(X86ConversionRank::ExactMatch, X86ConversionRank::Promotion));
assert!(!ic.is_better_sequence(X86ConversionRank::Conversion, X86ConversionRank::Promotion));
}
#[test]
fn test_implicit_conversion_conversion_sequence_rank() {
let ic = X86ImplicitConversion::new(true);
let rank = ic.conversion_sequence_rank(&char_ty(), &int_ty());
assert_eq!(rank, X86ConversionRank::Promotion);
}
#[test]
fn test_overload_resolution_template_prefers_non_template() {
let mut o = X86OverloadResolution::new(true);
let non_template = X86OverloadCandidate::new("f", vec![int_ty()], int_ty(), false);
let template = X86OverloadCandidate::new("f", vec![int_ty()], int_ty(), false)
.with_template(vec!["T".into()]);
o.add_candidate(non_template);
o.add_candidate(template);
let mut d = X86DiagnosticEmitter::new();
let result = o.resolve(&[int_ty()], &mut d);
assert_eq!(result, Some(0)); }
#[test]
fn test_overload_resolution_multiple_param_types() {
let mut o = X86OverloadResolution::new(true);
o.add_candidate(X86OverloadCandidate::new(
"f",
vec![int_ty(), int_ty()],
int_ty(),
false,
));
o.add_candidate(X86OverloadCandidate::new(
"f",
vec![int_ty(), double_ty()],
int_ty(),
false,
));
let mut d = X86DiagnosticEmitter::new();
let result = o.resolve(&[int_ty(), int_ty()], &mut d);
assert_eq!(result, Some(0)); }
#[test]
fn test_overload_resolution_builtin_candidate() {
let mut o = X86OverloadResolution::new(true);
o.add_builtin_candidate("operator+", vec![int_ty(), int_ty()], int_ty());
assert_eq!(o.candidate_count(), 1);
}
#[test]
fn test_overload_resolution_too_many_args_not_viable() {
let mut o = X86OverloadResolution::new(true);
o.add_candidate(X86OverloadCandidate::new(
"f",
vec![int_ty()],
int_ty(),
false,
));
let mut d = X86DiagnosticEmitter::new();
let result = o.resolve(&[int_ty(), int_ty()], &mut d);
assert_eq!(result, None);
}
#[test]
fn test_overload_resolution_is_viable_at() {
let mut o = X86OverloadResolution::new(true);
o.add_candidate(X86OverloadCandidate::new(
"f",
vec![int_ty()],
int_ty(),
false,
));
o.add_candidate(X86OverloadCandidate::new(
"g",
vec![double_ty()],
double_ty(),
false,
));
assert!(o.is_viable_at(0, &[int_ty()]));
assert!(!o.is_viable_at(1, &[QualType::void()]));
}
#[test]
fn test_overload_resolution_max_candidates() {
let mut o = X86OverloadResolution::new(true);
o.max_candidates = 3;
for i in 0..10 {
o.add_candidate(X86OverloadCandidate::new(
"f",
vec![int_ty()],
int_ty(),
false,
));
}
assert_eq!(o.candidate_count(), 3);
}
#[test]
fn test_overload_resolution_sfinae_handling() {
let mut o = X86OverloadResolution::new(true);
o.enable_sfinae = true;
let template = X86OverloadCandidate::new("f", vec![QualType::void()], int_ty(), false)
.with_template(vec!["T".into()]);
o.add_candidate(template);
o.add_candidate(X86OverloadCandidate::new(
"f",
vec![int_ty()],
int_ty(),
false,
));
let mut d = X86DiagnosticEmitter::new();
let result = o.resolve(&[int_ty()], &mut d);
assert_eq!(result, Some(1));
}
#[test]
fn test_template_sema_deduce_simple() {
let mut ts = X86TemplateSema {
template_params: vec![X86TemplateParam::type_param("T")],
..X86TemplateSema::new(1024)
};
let param_tys = vec![QualType::int()];
let arg_tys = vec![QualType::int()];
let result = ts.deduce_template_args(¶m_tys, &arg_tys);
assert!(result.success);
}
#[test]
fn test_template_sema_extract_template_param() {
let ts = X86TemplateSema {
template_params: vec![X86TemplateParam::type_param("T")],
..X86TemplateSema::new(1024)
};
let param = ts.extract_template_param(&QualType::int());
assert!(param.is_none());
}
#[test]
fn test_template_sema_typename_keyword_required() {
let mut ts = X86TemplateSema::new(1024);
ts.name_dependence
.insert("T::value_type".into(), X86NameDependence::TypeDependent);
assert!(ts.needs_typename_keyword("T::value_type"));
assert!(!ts.needs_typename_keyword("int"));
}
#[test]
fn test_template_sema_template_keyword_required() {
let mut ts = X86TemplateSema::new(1024);
ts.name_dependence.insert(
"T::template_method".into(),
X86NameDependence::FullyDependent,
);
assert!(ts.needs_template_keyword("T::template_method"));
assert!(!ts.needs_template_keyword("int"));
}
#[test]
fn test_template_sema_two_phase_lookup() {
let mut ts = X86TemplateSema::new(1024);
assert_eq!(ts.lookup_phase, X86LookupPhase::Phase1);
ts.enter_phase2();
assert_eq!(ts.lookup_phase, X86LookupPhase::Phase2);
}
#[test]
fn test_template_sema_defer_name() {
let mut ts = X86TemplateSema::new(1024);
ts.defer_name("T::value", "dependent");
assert_eq!(ts.deferred_names().len(), 1);
assert_eq!(ts.deferred_names()[0].0, "T::value");
}
#[test]
fn test_template_sema_begin_end_params() {
let mut ts = X86TemplateSema::new(1024);
ts.begin_template_params(vec![X86TemplateParam::type_param("T")]);
assert_eq!(ts.template_params.len(), 1);
ts.end_template_params();
assert!(ts.template_params.is_empty());
}
#[test]
fn test_template_sema_template_arg_display() {
assert_eq!(format!("{}", X86TemplateArg::Type(int_ty())), "int");
assert_eq!(format!("{}", X86TemplateArg::NonType(42)), "42");
}
#[test]
fn test_constexpr_eval_max_depth_exceeded() {
let mut ce = X86ConstexprEval::new(5);
let expr = x86_binary(
BinaryOp::Add,
x86_binary(
BinaryOp::Add,
x86_binary(
BinaryOp::Add,
x86_binary(BinaryOp::Add, x86_int_lit(1), x86_int_lit(1)),
x86_int_lit(1),
),
x86_int_lit(1),
),
x86_int_lit(1),
);
let result = ce.evaluate(&expr);
assert!(result.is_constant());
}
#[test]
fn test_constexpr_eval_uint_operations() {
let mut ce = X86ConstexprEval::new(512);
let a = Expr::UIntLiteral(100);
let b = Expr::UIntLiteral(50);
let add = x86_binary(BinaryOp::Add, a, b);
let result = ce.evaluate(&add);
assert_eq!(result, X86ConstValue::UInt(150));
}
#[test]
fn test_constexpr_eval_string_literal() {
let mut ce = X86ConstexprEval::new(512);
let result = ce.evaluate(&Expr::StringLiteral("hello".into()));
assert_eq!(result, X86ConstValue::StringLiteral("hello".into()));
}
#[test]
fn test_constexpr_eval_bool_value() {
let mut ce = X86ConstexprEval::new(512);
let not_true = Expr::Unary(UnaryOp::Not, Box::new(x86_int_lit(1)));
let result = ce.evaluate(¬_true);
assert_eq!(result, X86ConstValue::Bool(false));
}
#[test]
fn test_constexpr_eval_logical_and() {
let mut ce = X86ConstexprEval::new(512);
let expr = x86_binary(BinaryOp::LogicAnd, x86_int_lit(42), x86_int_lit(0));
let result = ce.evaluate(&expr);
assert_eq!(result, X86ConstValue::Bool(false));
}
#[test]
fn test_constexpr_eval_logical_or() {
let mut ce = X86ConstexprEval::new(512);
let expr = x86_binary(BinaryOp::LogicOr, x86_int_lit(0), x86_int_lit(42));
let result = ce.evaluate(&expr);
assert_eq!(result, X86ConstValue::Bool(true));
}
#[test]
fn test_constexpr_eval_composite() {
let mut ce = X86ConstexprEval::new(512);
let vals = vec![
X86ConstValue::Int(1),
X86ConstValue::Int(2),
X86ConstValue::Int(3),
];
let composite = X86ConstValue::Composite(vals);
assert!(composite.is_constant());
assert_eq!(format!("{}", composite), "{1, 2, 3}");
}
#[test]
fn test_constexpr_eval_float_add() {
let mut ce = X86ConstexprEval::new(512);
let a = Expr::FloatLiteral(1.5);
let b = Expr::FloatLiteral(2.5);
let expr = x86_binary(BinaryOp::Add, a, b);
let result = ce.evaluate(&expr);
assert_eq!(result, X86ConstValue::Float(4.0));
}
#[test]
fn test_constexpr_eval_float_comparisons() {
let mut ce = X86ConstexprEval::new(512);
let a = Expr::FloatLiteral(1.0);
let b = Expr::FloatLiteral(2.0);
let lt = x86_binary(BinaryOp::Lt, a.clone(), b.clone());
assert_eq!(ce.evaluate(<), X86ConstValue::Bool(true));
let eq = x86_binary(BinaryOp::Eq, a, b);
assert_eq!(ce.evaluate(&eq), X86ConstValue::Bool(false));
}
#[test]
fn test_constexpr_eval_cast_float_to_int() {
let mut ce = X86ConstexprEval::new(512);
let cast = Expr::Cast(int_ty(), Box::new(Expr::FloatLiteral(3.9)));
let result = ce.evaluate(&cast);
assert_eq!(result, X86ConstValue::Int(3)); }
#[test]
fn test_constexpr_eval_cast_int_to_bool() {
let mut ce = X86ConstexprEval::new(512);
let cast = Expr::Cast(QualType::bool_(), Box::new(x86_int_lit(42)));
let result = ce.evaluate(&cast);
assert_eq!(result, X86ConstValue::Bool(true));
}
#[test]
fn test_constexpr_eval_ast_depth() {
let mut ce = X86ConstexprEval::new(512);
let mut expr = x86_int_lit(0);
for i in 0..50 {
expr = x86_binary(BinaryOp::Add, expr, x86_int_lit(1));
}
let result = ce.evaluate(&expr);
assert_eq!(result, X86ConstValue::Int(50));
}
#[test]
fn test_constexpr_eval_negate_max() {
let mut ce = X86ConstexprEval::new(512);
let expr = Expr::Unary(UnaryOp::Minus, Box::new(x86_int_lit(i64::MIN)));
let result = ce.evaluate(&expr);
assert_eq!(result, X86ConstValue::Int(i64::MIN.wrapping_neg() as i128));
}
#[test]
fn test_constexpr_eval_clear_errors() {
let mut ce = X86ConstexprEval::new(512);
let expr = x86_binary(BinaryOp::Div, x86_int_lit(1), x86_int_lit(0));
ce.evaluate(&expr);
assert!(!ce.errors.is_empty());
ce.clear_errors();
assert!(ce.errors.is_empty());
}
#[test]
fn test_constexpr_eval_register_var_address() {
let mut ce = X86ConstexprEval::new(512);
ce.register_var_address("global_var", 0x1000);
let result = ce.evaluate(&x86_ident("global_var"));
assert_eq!(result, X86ConstValue::Address(0x1000));
}
#[test]
fn test_constexpr_eval_const_value_as_int() {
assert_eq!(X86ConstValue::Int(42).as_int(), Some(42));
assert_eq!(X86ConstValue::Bool(true).as_int(), Some(1));
assert_eq!(X86ConstValue::Float(3.14).as_int(), None);
}
#[test]
fn test_constexpr_eval_const_value_as_float() {
assert_eq!(X86ConstValue::Float(2.5).as_float(), Some(2.5));
assert_eq!(X86ConstValue::Int(42).as_float(), None);
}
#[test]
fn test_constexpr_eval_const_value_is_zero() {
assert!(X86ConstValue::Int(0).is_zero());
assert!(X86ConstValue::Float(0.0).is_zero());
assert!(X86ConstValue::Bool(false).is_zero());
assert!(X86ConstValue::NullPtr.is_zero());
assert!(!X86ConstValue::Int(1).is_zero());
}
#[test]
fn test_constexpr_eval_is_integer_constant_expression() {
let mut ce = X86ConstexprEval::new(512);
assert!(ce.is_integer_constant_expression(&x86_int_lit(42)));
assert!(!ce.is_integer_constant_expression(&Expr::FloatLiteral(3.14)));
}
#[test]
fn test_deep_sema_typedef_validation() {
let mut sema = make_sema();
let typedef = Decl::Typedef(TypedefDecl {
name: "myint".into(),
underlying: int_ty(),
});
let var = Decl::Variable(VarDecl {
name: "x".into(),
ty: int_ty(),
init: None,
linkage: Linkage::External,
is_global: true,
is_extern: false,
is_static: false,
});
let tu = x86_make_tu(vec![typedef, var]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
}
#[test]
fn test_deep_sema_struct_field_validation() {
let mut sema = make_sema();
let s = Decl::Struct(StructDecl {
name: "Point".into(),
fields: vec![FieldDecl::new("x", int_ty()), FieldDecl::new("y", int_ty())],
is_union: false,
});
let tu = x86_make_tu(vec![s]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
}
#[test]
fn test_deep_sema_enum_with_values() {
let mut sema = make_sema();
let e = Decl::Enum(EnumDecl {
name: "Flags".into(),
variants: vec![
EnumVariant::new("A", Some(1)),
EnumVariant::new("B", Some(2)),
EnumVariant::new("C", Some(4)),
],
underlying: int_ty(),
});
let tu = x86_make_tu(vec![e]);
let ok = sema.analyze_tu(&tu);
assert!(ok);
assert_eq!(sema.constexpr_eval.enum_count(), 3);
}
#[test]
fn test_deep_sema_multiple_functions() {
let mut sema = make_sema();
let func1 = FunctionDecl {
name: "f1".into(),
ret_ty: int_ty(),
params: vec![],
body: Some(CompoundStmt {
stmts: vec![x86_return(Some(x86_int_lit(1)))],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let func2 = FunctionDecl {
name: "f2".into(),
ret_ty: void_ty(),
params: vec![],
body: Some(CompoundStmt { stmts: vec![] }),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
assert!(sema.analyze_function(&func1));
assert!(sema.analyze_function(&func2));
}
#[test]
fn test_deep_sema_function_with_params() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "add".into(),
ret_ty: int_ty(),
params: vec![VarDecl::new("", int_ty()), VarDecl::new("", int_ty())],
body: Some(CompoundStmt {
stmts: vec![x86_return(Some(x86_int_lit(0)))],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let ok = sema.analyze_function(&func);
assert!(ok);
}
#[test]
fn test_deep_sema_while_loop_analysis() {
let mut sema = make_sema();
let body = CompoundStmt {
stmts: vec![Stmt::Expr(Box::new(x86_assign(
x86_ident("x"),
x86_binary(BinaryOp::Add, x86_ident("x"), x86_int_lit(1)),
)))],
};
let func = FunctionDecl {
name: "loop".into(),
ret_ty: void_ty(),
params: vec![],
body: Some(CompoundStmt {
stmts: vec![Stmt::While {
cond: Box::new(x86_int_lit(1)),
body: Box::new(Stmt::Compound(body)),
}],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let ok = sema.analyze_function(&func);
assert!(ok);
}
#[test]
fn test_deep_sema_for_loop_analysis() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "for_loop".into(),
ret_ty: void_ty(),
params: vec![],
body: Some(CompoundStmt {
stmts: vec![Stmt::For {
init: Some(Box::new(Stmt::Expr(Box::new(x86_assign(
x86_ident("i"),
x86_int_lit(0),
))))),
cond: Some(Box::new(x86_binary(
BinaryOp::Lt,
x86_ident("i"),
x86_int_lit(10),
))),
incr: Some(Box::new(x86_assign(
x86_ident("i"),
x86_binary(BinaryOp::Add, x86_ident("i"), x86_int_lit(1)),
))),
body: Box::new(Stmt::Null),
}],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let ok = sema.analyze_function(&func);
assert!(ok);
}
#[test]
fn test_deep_sema_switch_analysis() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "sw".into(),
ret_ty: void_ty(),
params: vec![],
body: Some(CompoundStmt {
stmts: vec![Stmt::Switch {
expr: Box::new(x86_ident("x")),
body: Box::new(Stmt::Compound(CompoundStmt {
stmts: vec![
Stmt::Case {
value: Box::new(x86_int_lit(1)),
stmt: Box::new(Stmt::Null),
},
Stmt::Default {
stmt: Box::new(Stmt::Null),
},
],
})),
}],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let ok = sema.analyze_function(&func);
assert!(ok);
}
#[test]
fn test_deep_sema_do_while_loop() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "dw".into(),
ret_ty: void_ty(),
params: vec![],
body: Some(CompoundStmt {
stmts: vec![Stmt::DoWhile {
cond: Box::new(x86_int_lit(1)),
body: Box::new(Stmt::Null),
}],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let ok = sema.analyze_function(&func);
assert!(ok);
}
#[test]
fn test_deep_sema_if_else_chain() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "branch".into(),
ret_ty: int_ty(),
params: vec![],
body: Some(CompoundStmt {
stmts: vec![Stmt::If {
cond: Box::new(x86_int_lit(1)),
then: Box::new(x86_return(Some(x86_int_lit(1)))),
els: Some(Box::new(x86_return(Some(x86_int_lit(0))))),
}],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let ok = sema.analyze_function(&func);
assert!(ok);
}
#[test]
fn test_deep_sema_break_outside_loop_error() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "bad_break".into(),
ret_ty: void_ty(),
params: vec![],
body: Some(CompoundStmt {
stmts: vec![Stmt::Break],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
sema.analyze_function(&func);
assert!(sema.errors.iter().any(|e| e.contains("break")));
}
#[test]
fn test_deep_sema_continue_outside_loop_error() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "bad_continue".into(),
ret_ty: void_ty(),
params: vec![],
body: Some(CompoundStmt {
stmts: vec![Stmt::Continue],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
sema.analyze_function(&func);
assert!(sema.errors.iter().any(|e| e.contains("continue")));
}
#[test]
fn test_deep_sema_case_outside_switch_error() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "bad_case".into(),
ret_ty: void_ty(),
params: vec![],
body: Some(CompoundStmt {
stmts: vec![Stmt::Case {
value: Box::new(x86_int_lit(1)),
stmt: Box::new(Stmt::Null),
}],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
sema.analyze_function(&func);
assert!(sema.errors.iter().any(|e| e.contains("case")));
}
#[test]
fn test_deep_sema_goto_label() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "jump".into(),
ret_ty: void_ty(),
params: vec![],
body: Some(CompoundStmt {
stmts: vec![
Stmt::Goto {
label: "end".into(),
},
Stmt::Label {
name: "end".into(),
stmt: Box::new(Stmt::Null),
},
],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let ok = sema.analyze_function(&func);
assert!(ok);
}
#[test]
fn test_deep_sema_noreturn_warning() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "noret".into(),
ret_ty: int_ty(),
params: vec![],
body: Some(CompoundStmt {
stmts: vec![], }),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: true,
};
sema.analyze_function(&func);
assert!(sema.warnings.iter().any(|w| w.contains("missing return")));
}
#[test]
fn test_deep_sema_resolve_overload() {
let mut sema = make_sema();
sema.overload_resolution
.add_candidate(X86OverloadCandidate::new(
"f",
vec![int_ty()],
int_ty(),
false,
));
sema.overload_resolution
.add_candidate(X86OverloadCandidate::new(
"f",
vec![double_ty()],
double_ty(),
false,
));
let result = sema.resolve_overload("f", &[int_ty()]);
assert_eq!(result, Some(0));
}
#[test]
fn test_deep_sema_enter_template() {
let mut sema = make_sema();
let ok = sema.enter_template("MyTemplate", &[X86TemplateArg::Type(int_ty())]);
assert!(ok);
assert_eq!(sema.template_sema.current_depth(), 1);
sema.leave_template();
assert_eq!(sema.template_sema.current_depth(), 0);
}
#[test]
fn test_deep_sema_type_of_expr() {
let sema = make_sema();
let ty = sema.type_of_expr(&x86_int_lit(42));
assert!(ty.is_int());
}
#[test]
fn test_deep_sema_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_dump_symbol_table_empty() {
let sema = make_sema();
let dump = sema.dump_symbol_table();
assert!(dump.contains("Scope"));
}
#[test]
fn prop_scope_enter_leave_balanced() {
let mut sm = X86ScopeManager::new();
let start = sm.scope_depth();
sm.enter_scope(X86ScopeKind::Block);
sm.enter_scope(X86ScopeKind::Block);
sm.leave_scope();
sm.leave_scope();
assert_eq!(sm.scope_depth(), start);
}
#[test]
fn prop_conversion_identity_is_exact() {
let ic = X86ImplicitConversion::new(true);
let result = ic.check_implicit_conversion(&int_ty(), &int_ty());
assert!(result.is_exact_match());
}
#[test]
fn prop_usual_conversion_commutative() {
let tp = X86TypePromotion::new_lp64();
let result1 = tp.usual_arithmetic_conversions(&int_ty(), &float_ty());
let result2 = tp.usual_arithmetic_conversions(&float_ty(), &int_ty());
assert_eq!(result1.to_string(), result2.to_string());
}
#[test]
fn prop_diag_error_count_never_negative() {
let mut d = X86DiagnosticEmitter::new();
assert!(d.error_count() <= 0); d.error("test");
assert!(d.error_count() > 0);
d.clear();
assert_eq!(d.error_count(), 0);
}
#[test]
fn prop_constexpr_eval_deterministic() {
let mut ce1 = X86ConstexprEval::new(512);
let mut ce2 = X86ConstexprEval::new(512);
let expr = x86_binary(BinaryOp::Add, x86_int_lit(10), x86_int_lit(20));
assert_eq!(ce1.evaluate(&expr), ce2.evaluate(&expr));
}
#[test]
fn prop_template_depth_monotonic() {
let mut ts = X86TemplateSema::new(100);
let mut d = X86DiagnosticEmitter::new();
for i in 0..50 {
assert!(ts.enter_instantiation(&format!("T{}", i), &mut d));
}
assert_eq!(ts.current_depth(), 50);
for _ in 0..50 {
ts.leave_instantiation();
}
assert_eq!(ts.current_depth(), 0);
}
#[test]
fn prop_promotion_lp64_char_becomes_int() {
let tp = X86TypePromotion::new_lp64();
let promoted = tp.integer_promotion(&QualType::char());
assert!(promoted.is_int());
}
#[test]
fn prop_promotion_ilp32_long_is_32bit() {
let tp = X86TypePromotion::new_ilp32();
assert_eq!(tp.width(&QualType::long()), 32);
}
#[test]
fn regression_empty_tu_no_panic() {
let mut sema = make_sema();
let tu = x86_make_tu(vec![]);
assert!(sema.analyze_tu(&tu));
}
#[test]
fn regression_no_panic_on_many_errors() {
let mut sema = make_sema();
let mut funcs = Vec::new();
for i in 0..100 {
funcs.push(Decl::Function(FunctionDecl {
name: format!("f{}", i),
ret_ty: void_ty(),
params: vec![],
body: None,
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
}));
}
let tu = x86_make_tu(funcs);
let _ = sema.analyze_tu(&tu);
}
#[test]
fn regression_no_panic_empty_scope_leave() {
let mut sm = X86ScopeManager::new();
sm.leave_scope();
assert_eq!(sm.scope_depth(), 0);
}
#[test]
fn regression_no_panic_zero_arg_overload() {
let mut o = X86OverloadResolution::new(true);
o.add_candidate(X86OverloadCandidate::new("f", vec![], int_ty(), false));
let mut d = X86DiagnosticEmitter::new();
let result = o.resolve(&[], &mut d);
assert_eq!(result, Some(0));
}
#[test]
fn regression_diag_format_empty_diag() {
let d = X86DiagnosticEmitter::new();
let fmt = d.format_all();
assert!(fmt.is_empty());
}
#[test]
fn regression_null_check_after_clear() {
let mut d = X86DiagnosticEmitter::new();
d.error("e");
d.clear();
assert_eq!(d.error_count(), 0);
assert!(!d.should_fail());
}
#[test]
fn doc_example_x86_deep_sema_basic() {
let mut sema = X86DeepSema::new("c17", "x86_64-unknown-linux-gnu");
assert!(sema.is_64bit);
let var = Decl::Variable(VarDecl {
name: "answer".into(),
ty: QualType::int(),
init: Some(Box::new(Expr::IntLiteral(42))),
linkage: Linkage::External,
is_global: true,
is_extern: false,
is_static: false,
});
let func = Decl::Function(FunctionDecl {
name: "main".into(),
ret_ty: QualType::int(),
params: vec![],
body: Some(CompoundStmt {
stmts: vec![Stmt::Return(Some(Box::new(Expr::IntLiteral(0))))],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
});
let tu = TranslationUnit {
decls: vec![var, func],
filename: "example.c".into(),
};
let ok = sema.analyze_tu(&tu);
assert!(ok);
}
#[test]
fn doc_example_scope_management() {
let mut sm = X86ScopeManager::new();
let mut d = X86DiagnosticEmitter::new();
sm.declare_variable("global_counter", QualType::int(), false, false, &mut d);
sm.enter_function("compute", QualType::int());
sm.enter_scope(X86ScopeKind::Function);
sm.declare_variable("local_result", QualType::int(), false, false, &mut d);
sm.enter_scope(X86ScopeKind::Block);
sm.declare_variable("temp", QualType::float_(), false, false, &mut d);
assert!(sm.lookup("temp").is_some());
sm.leave_scope();
assert!(sm.lookup("temp").is_none());
sm.leave_scope();
sm.leave_function();
assert!(sm.lookup("global_counter").is_some());
}
#[test]
fn doc_example_type_promotion_lp64() {
let tp = X86TypePromotion::new_lp64();
let promoted_char = tp.integer_promotion(&QualType::char());
assert!(promoted_char.is_int());
let promoted_float = tp.default_argument_promotion(&QualType::float_());
assert!(promoted_float.is_double());
let result = tp.usual_arithmetic_conversions(&QualType::int(), &QualType::float_());
assert!(result.is_float());
}
#[test]
fn doc_example_implicit_conversion_checking() {
let ic = X86ImplicitConversion::new(true);
let result = ic.check_implicit_conversion(&QualType::char(), &QualType::int());
assert!(result.valid);
assert_eq!(result.kind, X86ConvKind::IntegralPromotion);
let result2 = ic.check_implicit_conversion(&QualType::int(), &QualType::char());
assert!(result2.valid);
assert!(result2.warning.is_some());
}
#[test]
fn doc_example_overload_resolution() {
let mut resolver = X86OverloadResolution::new(true);
resolver.add_candidate(X86OverloadCandidate::new(
"abs",
vec![QualType::int()],
QualType::int(),
false,
));
resolver.add_candidate(X86OverloadCandidate::new(
"abs",
vec![QualType::double_()],
QualType::double_(),
false,
));
let mut diag = X86DiagnosticEmitter::new();
let best = resolver.resolve(&[QualType::int()], &mut diag);
assert_eq!(best, Some(0));
}
#[test]
fn doc_example_template_sema() {
let mut ts = X86TemplateSema::new(256);
ts.begin_template_params(vec![X86TemplateParam::type_param("T")]);
let mut d = X86DiagnosticEmitter::new();
assert!(ts.validate_template_params(&mut d));
let ok = ts.check_template_args(&[X86TemplateArg::Type(QualType::int())], &mut d);
assert!(ok);
ts.end_template_params();
}
#[test]
fn doc_example_constexpr_eval() {
let mut ce = X86ConstexprEval::new(512);
let inner = Expr::Binary(
BinaryOp::Add,
Box::new(Expr::IntLiteral(2)),
Box::new(Expr::IntLiteral(3)),
);
let outer = Expr::Binary(
BinaryOp::Mul,
Box::new(inner),
Box::new(Expr::IntLiteral(4)),
);
assert_eq!(ce.evaluate(&outer), X86ConstValue::Int(20));
}
#[test]
fn test_x86_abi_long_size_is_8_on_lp64() {
let dv = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
assert_eq!(dv.type_width_bits(&QualType::long()), 64);
}
#[test]
fn test_x86_abi_long_size_is_4_on_ilp32() {
let dv = X86DeclValidator::new("c17", "i386-unknown-linux-gnu");
assert_eq!(dv.type_width_bits(&QualType::long()), 32);
}
#[test]
fn test_x86_abi_pointer_size_is_8_on_lp64() {
let dv = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
assert_eq!(
dv.type_width_bits(&QualType::pointer_to(QualType::int())),
64
);
}
#[test]
fn test_x86_abi_pointer_size_is_4_on_ilp32() {
let dv = X86DeclValidator::new("c17", "i386-unknown-linux-gnu");
assert_eq!(
dv.type_width_bits(&QualType::pointer_to(QualType::int())),
32
);
}
#[test]
fn test_x86_abi_long_long_is_8_on_both() {
let dv64 = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let dv32 = X86DeclValidator::new("c17", "i386-unknown-linux-gnu");
assert_eq!(dv64.type_width_bits(&QualType::long_long()), 64);
assert_eq!(dv32.type_width_bits(&QualType::long_long()), 64);
}
#[test]
fn test_x86_abi_int_is_4_on_both() {
let dv64 = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let dv32 = X86DeclValidator::new("c17", "i386-unknown-linux-gnu");
assert_eq!(dv64.type_width_bits(&QualType::int()), 32);
assert_eq!(dv32.type_width_bits(&QualType::int()), 32);
}
#[test]
fn test_x86_size_t_is_8_on_lp64() {
assert_eq!(X86_LP64_SIZE_T_SIZE, 8);
}
#[test]
fn test_x86_size_t_is_4_on_ilp32() {
assert_eq!(X86_ILP32_SIZE_T_SIZE, 4);
}
#[test]
fn test_x86_wchar_t_is_4_on_lp64() {
assert_eq!(X86_LP64_WCHAR_T_SIZE, 4);
}
#[test]
fn test_x86_long_double_is_16_on_lp64() {
assert_eq!(X86_LP64_LONG_DOUBLE_SIZE, 16);
}
#[test]
fn test_diag_all_severity_levels() {
let mut d = X86DiagnosticEmitter::new();
d.error("critical error");
d.warning("gentle warning");
d.note("additional context");
d.fatal("unrecoverable");
assert_eq!(d.error_count(), 2); assert_eq!(d.warning_count(), 1);
assert!(d.has_fatal());
assert!(d.should_fail());
}
#[test]
fn test_diag_category_filtering() {
let mut d = X86DiagnosticEmitter::new();
d.suppress_category(X86DiagCategory::Shadow);
d.warning_at(
"declaration of 'x' shadows a previous declaration",
Some("test.c"),
5,
1,
);
assert!(d.warning_count() <= 0);
}
#[test]
fn test_diag_error_limit() {
let mut d = X86DiagnosticEmitter::new();
d.set_error_limit(5);
for i in 0..20 {
d.error_at(format!("error {}", i), Some("test.c"), i, 1);
}
assert_eq!(d.error_count(), 5);
}
#[test]
fn test_diag_source_file_snippet() {
let mut d = X86DiagnosticEmitter::new();
d.add_source_file("test.c", "int main() {\n return 0;\n}");
d.error_at("something wrong", Some("test.c"), 2, 3);
let formatted = d.format_all();
assert!(!formatted.is_empty());
}
#[test]
fn test_diag_type_name_registration() {
let mut d = X86DiagnosticEmitter::new();
d.register_type_name("std::vector<int>", "vector<int>");
assert_eq!(d.pretty_type_name("std::vector<int>"), "vector<int>");
assert_eq!(d.pretty_type_name("int"), "int");
}
#[test]
fn test_diag_fixit_insertion() {
let mut d = X86DiagnosticEmitter::new();
d.error("use of undeclared identifier");
d.add_fixit(X86FixIt {
file: "test.c".into(),
start_line: 3,
start_col: 1,
end_line: 3,
end_col: 1,
replacement: "int x = ".into(),
description: "add declaration".into(),
});
let formatted = d.format_all();
assert!(formatted.contains("fixit"));
}
#[test]
fn test_diag_fixit_removal() {
let mut d = X86DiagnosticEmitter::new();
d.error("extra semicolon");
d.add_fixit(X86FixIt {
file: "test.c".into(),
start_line: 5,
start_col: 20,
end_line: 5,
end_col: 21,
replacement: "".into(),
description: "remove extra semicolon".into(),
});
let formatted = d.format_all();
assert!(formatted.contains("fixit"));
}
#[test]
fn test_diag_enable_only() {
let mut d = X86DiagnosticEmitter::new();
let mut enabled = HashSet::new();
enabled.insert(X86DiagCategory::Template);
d.enable_only(enabled);
d.error_at("type mismatch", Some("test.c"), 1, 1);
d.error_at("template argument deduction failure", Some("test.c"), 1, 1);
assert_eq!(d.error_count(), 1);
}
#[test]
fn test_diag_warn_deprecated_with_replacement() {
let mut d = X86DiagnosticEmitter::new();
d.warn_deprecated("gets", Some("fgets"), Some("test.c"), 10, 5);
assert_eq!(d.warning_count(), 1);
let formatted = d.format_all();
assert!(formatted.contains("deprecated"));
assert!(formatted.contains("fgets"));
}
#[test]
fn test_diag_warn_unreachable_code() {
let mut d = X86DiagnosticEmitter::new();
d.warn_unreachable(Some("test.c"), 15, 1);
assert_eq!(d.warning_count(), 1);
}
#[test]
fn test_diag_warn_unused_variable() {
let mut d = X86DiagnosticEmitter::new();
d.warn_unused_variable("temp", Some("test.c"), 20, 5);
assert_eq!(d.warning_count(), 1);
}
#[test]
fn test_diag_warn_unused_parameter() {
let mut d = X86DiagnosticEmitter::new();
d.warn_unused_parameter("unused_param", Some("test.c"), 8, 15);
assert_eq!(d.warning_count(), 1);
}
#[test]
fn test_diag_warn_division_by_zero() {
let mut d = X86DiagnosticEmitter::new();
d.warn_division_by_zero(Some("test.c"), 12, 10);
assert_eq!(d.warning_count(), 1);
}
#[test]
fn test_diag_suggest_const() {
let mut d = X86DiagnosticEmitter::new();
d.error("variable is never modified");
d.suggest_const("x", "test.c", 5, 1);
let formatted = d.format_all();
assert!(formatted.contains("const"));
}
#[test]
fn test_diag_explain_type_difference() {
let mut d = X86DiagnosticEmitter::new();
d.explain_type_difference(
"x",
"int",
"double",
(Some("a.c".into()), 5, 1),
(Some("b.c".into()), 3, 1),
);
assert!(d.error_count() > 0);
let formatted = d.format_all();
assert!(formatted.contains("redefinition"));
}
#[test]
fn test_vector_constants_coherent() {
assert!(X86_MMX_WIDTH < X86_SSE_WIDTH);
assert!(X86_SSE_WIDTH < X86_AVX_WIDTH);
assert!(X86_AVX_WIDTH < X86_AVX512_WIDTH);
assert_eq!(X86_SSE_WIDTH, 128);
assert_eq!(X86_AVX512_WIDTH, 512);
}
#[test]
fn test_max_int_width() {
assert_eq!(X86_MAX_INT_WIDTH, 128);
}
#[test]
fn test_conversion_chain_char_int_long() {
let ic = X86ImplicitConversion::new(true);
let r1 = ic.check_implicit_conversion(&QualType::char(), &QualType::int());
assert!(r1.valid);
assert_eq!(r1.rank, X86ConversionRank::Promotion);
let r2 = ic.check_implicit_conversion(&QualType::int(), &QualType::long());
assert!(r2.valid);
}
#[test]
fn test_all_scope_kinds_roundtrip_display() {
let kinds = vec![
X86ScopeKind::File,
X86ScopeKind::Function,
X86ScopeKind::FunctionPrototype,
X86ScopeKind::Block,
X86ScopeKind::Namespace,
X86ScopeKind::Class,
X86ScopeKind::TemplateParam,
X86ScopeKind::Enum,
X86ScopeKind::TryBlock,
X86ScopeKind::CatchBlock,
X86ScopeKind::Lambda,
];
for kind in kinds {
let s = format!("{}", kind);
assert!(!s.is_empty());
}
}
#[test]
fn test_all_diag_categories_roundtrip_display() {
let cats = vec![
X86DiagCategory::Semantic,
X86DiagCategory::Parse,
X86DiagCategory::Lex,
X86DiagCategory::TypeCheck,
X86DiagCategory::Template,
X86DiagCategory::Overload,
X86DiagCategory::Constexpr,
X86DiagCategory::ODR,
X86DiagCategory::Shadow,
X86DiagCategory::Other,
];
for cat in cats {
let s = format!("{}", cat);
assert!(!s.is_empty());
}
}
#[test]
fn test_all_conv_kinds_roundtrip_display() {
let kinds = vec![
X86ConvKind::Identity,
X86ConvKind::LvalueToRvalue,
X86ConvKind::ArrayToPointerDecay,
X86ConvKind::FunctionToPointerDecay,
X86ConvKind::IntegralPromotion,
X86ConvKind::FloatingPromotion,
X86ConvKind::IntegralConversion,
X86ConvKind::FloatingConversion,
X86ConvKind::FloatingIntegralConversion,
X86ConvKind::PointerConversion,
X86ConvKind::PointerToBoolConversion,
X86ConvKind::NullPointerConversion,
X86ConvKind::NullMemberPointerConversion,
X86ConvKind::BooleanConversion,
X86ConvKind::QualificationConversion,
X86ConvKind::DerivedToBaseConversion,
X86ConvKind::UserDefinedConversion,
];
for kind in kinds {
let s = format!("{}", kind);
assert!(!s.is_empty());
}
}
#[test]
fn test_all_constexpr_errors_roundtrip_display() {
let errors = vec![
X86ConstExprError::DivisionByZero,
X86ConstExprError::Overflow,
X86ConstExprError::NonConstantOperand,
X86ConstExprError::ReadNonConstVariable,
X86ConstExprError::CallNonConstexprFunction,
X86ConstExprError::InfiniteRecursion,
X86ConstExprError::MaxDepthExceeded,
X86ConstExprError::UndefinedBehavior,
X86ConstExprError::Other("custom".into()),
];
for err in errors {
let s = format!("{}", err);
assert!(!s.is_empty());
}
}
#[test]
fn test_independent_sema_instances_do_not_interfere() {
let mut sema1 = X86DeepSema::new("c17", "x86_64-unknown-linux-gnu");
let mut sema2 = X86DeepSema::new("c11", "i386-unknown-linux-gnu");
sema1.scope_manager.enter_scope(X86ScopeKind::Block);
sema1.scope_manager.declare_variable(
"x",
QualType::int(),
false,
false,
&mut sema1.diagnostic,
);
assert_eq!(sema2.scope_manager.scope_depth(), 1);
assert!(sema2.scope_manager.lookup("x").is_none());
sema1.scope_manager.leave_scope();
}
#[test]
fn test_diag_emitter_independent() {
let mut d1 = X86DiagnosticEmitter::new();
let mut d2 = X86DiagnosticEmitter::new();
d1.error("error in d1");
d2.warning("warning in d2");
assert_eq!(d1.error_count(), 1);
assert_eq!(d2.error_count(), 0);
assert_eq!(d2.warning_count(), 1);
}
#[test]
fn test_constexpr_eval_independent() {
let mut ce1 = X86ConstexprEval::new(512);
let mut ce2 = X86ConstexprEval::new(512);
ce1.register_enum_value("E1", 100);
ce2.register_enum_value("E2", 200);
assert_eq!(
ce1.evaluate(&Expr::Ident("E1".into())),
X86ConstValue::Int(100)
);
assert_eq!(
ce1.evaluate(&Expr::Ident("E2".into())),
X86ConstValue::NonConstant
);
assert_eq!(
ce2.evaluate(&Expr::Ident("E1".into())),
X86ConstValue::NonConstant
);
assert_eq!(
ce2.evaluate(&Expr::Ident("E2".into())),
X86ConstValue::Int(200)
);
}
#[test]
fn test_all_defaults_constructible() {
let _ = X86DeepSema::default();
let _ = X86ScopeManager::default();
let _ = X86DeclValidator::default();
let _ = X86ExprValidator::default();
let _ = X86TypePromotion::default();
let _ = X86ImplicitConversion::default();
let _ = X86OverloadResolution::default();
let _ = X86TemplateSema::default();
let _ = X86ConstexprEval::default();
let _ = X86DiagnosticEmitter::default();
let _ = X86InstantiationDepth::default();
}
#[test]
fn test_const_value_display_all_variants() {
assert_eq!(format!("{}", X86ConstValue::Int(-42)), "-42");
assert_eq!(format!("{}", X86ConstValue::UInt(42)), "42");
assert_eq!(format!("{}", X86ConstValue::Float(3.14)), "3.14");
assert_eq!(format!("{}", X86ConstValue::Bool(true)), "true");
assert_eq!(format!("{}", X86ConstValue::Bool(false)), "false");
assert_eq!(format!("{}", X86ConstValue::NullPtr), "nullptr");
assert_eq!(
format!("{}", X86ConstValue::StringLiteral("hi".into())),
"\"hi\""
);
assert_eq!(format!("{}", X86ConstValue::Address(0xDEAD)), "&dead");
assert_eq!(format!("{}", X86ConstValue::NonConstant), "<non-constant>");
}
#[test]
fn test_template_arg_display_all_variants() {
assert_eq!(format!("{}", X86TemplateArg::Type(QualType::int())), "int");
assert_eq!(format!("{}", X86TemplateArg::NonType(42)), "42");
assert_eq!(
format!(
"{}",
X86TemplateArg::Template(X86TemplateParam::type_param("U"))
),
"template U"
);
}
#[test]
fn test_scope_entry_name_all_variants() {
let entries = vec![
X86ScopeEntry::Variable {
name: "v".into(),
ty: QualType::int(),
is_extern: false,
is_static: false,
declared_line: 0,
declared_col: 0,
declared_file: None,
},
X86ScopeEntry::Function {
name: "f".into(),
ret_ty: QualType::void(),
params: vec![],
is_vararg: false,
is_inline: false,
linkage: "external".into(),
declared_line: 0,
declared_col: 0,
declared_file: None,
},
X86ScopeEntry::Typedef {
name: "t".into(),
underlying: QualType::int(),
},
X86ScopeEntry::StructTag {
name: "s".into(),
is_union: false,
},
X86ScopeEntry::EnumTag { name: "e".into() },
X86ScopeEntry::EnumConstant {
name: "c".into(),
value: 0,
},
X86ScopeEntry::Label {
name: "l".into(),
defined: true,
},
X86ScopeEntry::Namespace { name: "ns".into() },
X86ScopeEntry::TemplateParam {
name: "T".into(),
kind: "type".into(),
},
X86ScopeEntry::ClassMember {
name: "m".into(),
ty: QualType::int(),
access: "public".into(),
},
];
for entry in &entries {
assert!(!entry.name().is_empty());
}
}
#[test]
fn test_expr_validator_evaluate_int_max() {
let v = X86ExprValidator::new("c17", true);
let expr = x86_binary(BinaryOp::Add, x86_int_lit(i64::MAX - 1), x86_int_lit(1));
assert_eq!(v.evaluate_integer_constant(&expr), Some(i64::MAX));
}
#[test]
fn test_expr_validator_evaluate_int_min() {
let v = X86ExprValidator::new("c17", true);
let expr = x86_binary(BinaryOp::Sub, x86_int_lit(i64::MIN + 1), x86_int_lit(1));
assert_eq!(v.evaluate_integer_constant(&expr), Some(i64::MIN));
}
#[test]
fn test_expr_validator_evaluate_overflow_add() {
let v = X86ExprValidator::new("c17", true);
let expr = x86_binary(BinaryOp::Add, x86_int_lit(i64::MAX), x86_int_lit(1));
assert_eq!(v.evaluate_integer_constant(&expr), None);
}
#[test]
fn test_constexpr_eval_large_unsigned() {
let mut ce = X86ConstexprEval::new(512);
let result = ce.evaluate(&Expr::UIntLiteral(u64::MAX));
assert_eq!(result, X86ConstValue::UInt(u64::MAX as u128));
}
#[test]
fn test_scope_many_entries_then_lookup() {
let mut sm = X86ScopeManager::new();
let mut d = X86DiagnosticEmitter::new();
for i in 0..5000 {
sm.declare_variable(&format!("v{}", i), QualType::int(), false, false, &mut d);
}
assert!(sm.lookup("v0").is_some());
assert!(sm.lookup("v4999").is_some());
assert!(sm.lookup("v5000").is_none());
}
#[test]
fn test_decl_validator_call_args_exact_match() {
let v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let ok = v.check_call_args(
"f",
&[QualType::int(), QualType::float_()],
&[Expr::IntLiteral(1), Expr::FloatLiteral(2.0)],
false,
&mut d,
);
assert!(ok);
}
#[test]
fn test_decl_validator_call_args_vararg_extra_ok() {
let v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let ok = v.check_call_args(
"printf",
&[QualType::const_char_ptr()],
&[Expr::StringLiteral("fmt".into()), Expr::IntLiteral(42)],
true,
&mut d,
);
assert!(ok);
}
#[test]
fn test_decl_validator_call_args_no_params_no_args() {
let v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let ok = v.check_call_args("empty", &[], &[], false, &mut d);
assert!(ok);
}
#[test]
fn test_decl_validator_array_size_huge() {
let v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let ok = v.check_array_size(&Expr::IntLiteral(0x80000000), &mut d);
assert!(ok); assert!(d.warning_count() > 0);
}
#[test]
fn test_decl_validator_bitfield_zero_width_unnamed() {
let v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let ok = v.check_bitfield_width(0, &QualType::int(), &mut d);
assert!(ok); }
#[test]
fn test_decl_validator_bitfield_exceeds_type_width() {
let v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let ok = v.check_bitfield_width(33, &QualType::int(), &mut d);
assert!(!ok); }
#[test]
fn test_decl_validator_function_return_function_error() {
let mut v = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let mut d = X86DiagnosticEmitter::new();
let func = FunctionDecl {
name: "bad".into(),
ret_ty: QualType::function(QualType::void(), vec![]),
params: vec![],
body: None,
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let ok = v.check_function_decl(&func, &mut d);
assert!(!ok);
}
#[test]
fn test_implicit_conversion_null_to_pointer() {
let ic = X86ImplicitConversion::new(true);
let result =
ic.check_implicit_conversion(&QualType::int(), &QualType::pointer_to(QualType::int()));
assert!(!result.valid);
}
#[test]
fn test_implicit_conversion_long_to_pointer_lp64() {
let ic = X86ImplicitConversion::new(true);
let result =
ic.check_implicit_conversion(&QualType::long(), &QualType::pointer_to(QualType::int()));
assert!(result.valid);
}
#[test]
fn test_implicit_conversion_pointer_to_long_lp64() {
let ic = X86ImplicitConversion::new(true);
let result =
ic.check_implicit_conversion(&QualType::pointer_to(QualType::int()), &QualType::long());
assert!(result.valid);
}
#[test]
fn test_implicit_conversion_int_to_pointer_ilp32() {
let ic = X86ImplicitConversion::new(false);
let result =
ic.check_implicit_conversion(&QualType::int(), &QualType::pointer_to(QualType::int()));
assert!(result.valid);
}
#[test]
fn test_implicit_conversion_short_to_pointer() {
let ic = X86ImplicitConversion::new(true);
let result = ic
.check_implicit_conversion(&QualType::short(), &QualType::pointer_to(QualType::int()));
assert!(!result.valid);
}
#[test]
fn test_implicit_conversion_float_to_int_narrowing() {
let ic = X86ImplicitConversion::new(true);
let result = ic.check_implicit_conversion(&QualType::float_(), &QualType::int());
assert!(result.valid);
assert!(result.warning.is_some()); }
#[test]
fn test_implicit_conversion_double_to_float_narrowing() {
let ic = X86ImplicitConversion::new(true);
let result = ic.check_implicit_conversion(&QualType::double_(), &QualType::float_());
assert!(result.valid);
assert!(result.warning.is_some()); }
#[test]
fn test_implicit_conversion_bool_to_int() {
let ic = X86ImplicitConversion::new(true);
let result = ic.check_implicit_conversion(&QualType::bool_(), &QualType::int());
assert!(result.valid);
}
#[test]
fn test_overload_resolution_ptr_vs_int_parameter() {
let mut o = X86OverloadResolution::new(true);
o.add_candidate(X86OverloadCandidate::new(
"f",
vec![QualType::pointer_to(QualType::int())],
QualType::void(),
false,
));
o.add_candidate(X86OverloadCandidate::new(
"f",
vec![QualType::int()],
QualType::void(),
false,
));
let mut d = X86DiagnosticEmitter::new();
let result = o.resolve(&[QualType::pointer_to(QualType::int())], &mut d);
assert_eq!(result, Some(0)); }
#[test]
fn test_overload_resolution_const_qualifier_matters() {
let mut o = X86OverloadResolution::new(true);
let const_int = QualType::const_(TypeNode::Int);
o.add_candidate(X86OverloadCandidate::new(
"f",
vec![QualType::int()],
QualType::void(),
false,
));
o.add_candidate(X86OverloadCandidate::new(
"f",
vec![const_int.clone()],
QualType::void(),
false,
));
let mut d = X86DiagnosticEmitter::new();
let result = o.resolve(&[const_int], &mut d);
assert_eq!(result, Some(1));
}
#[test]
fn test_overload_resolution_ellipsis_only_match() {
let mut o = X86OverloadResolution::new(true);
let mut c = X86OverloadCandidate::new("f", vec![QualType::int()], QualType::void(), true);
c.is_variadic = true;
o.add_candidate(c);
let mut d = X86DiagnosticEmitter::new();
let result = o.resolve(&[QualType::int(), QualType::float_()], &mut d);
assert_eq!(result, Some(0)); }
#[test]
fn test_overload_resolution_empty_candidate_set() {
let o = X86OverloadResolution::new(true);
assert_eq!(o.candidate_count(), 0);
let ambiguous = o.detect_ambiguity(&[]);
assert!(ambiguous.is_empty());
}
#[test]
fn test_overload_resolution_single_candidate_always_best() {
let mut o = X86OverloadResolution::new(true);
o.add_candidate(X86OverloadCandidate::new(
"only",
vec![QualType::int()],
QualType::int(),
false,
));
let mut d = X86DiagnosticEmitter::new();
let result = o.resolve(&[QualType::int()], &mut d);
assert_eq!(result, Some(0));
}
#[test]
fn test_template_sema_too_many_args_error() {
let ts = X86TemplateSema {
template_params: vec![X86TemplateParam::type_param("T")],
..X86TemplateSema::new(1024)
};
let mut d = X86DiagnosticEmitter::new();
let ok = ts.check_template_args(
&[
X86TemplateArg::Type(QualType::int()),
X86TemplateArg::Type(QualType::float_()),
],
&mut d,
);
assert!(!ok);
}
#[test]
fn test_template_sema_non_type_param_check() {
let ts = X86TemplateSema {
template_params: vec![X86TemplateParam::nontype_param("N", QualType::int())],
..X86TemplateSema::new(1024)
};
let mut d = X86DiagnosticEmitter::new();
let ok = ts.check_template_args(&[X86TemplateArg::NonType(42)], &mut d);
assert!(ok);
}
#[test]
fn test_template_sema_template_template_param() {
let ts = X86TemplateSema {
template_params: vec![X86TemplateParam {
name: "Container".into(),
kind: X86TemplateParamKind::TemplateTemplate,
default_arg: None,
is_parameter_pack: false,
}],
..X86TemplateSema::new(1024)
};
let mut d = X86DiagnosticEmitter::new();
let ok = ts.check_template_args(
&[X86TemplateArg::Template(X86TemplateParam::type_param(
"vector",
))],
&mut d,
);
assert!(ok);
}
#[test]
fn test_template_sema_pack_parameter() {
let param = X86TemplateParam::type_param("Args").pack();
assert!(param.is_parameter_pack);
}
#[test]
fn test_template_sema_cache_key() {
let ts = X86TemplateSema::new(1024);
let key = ts.instantiation_key("vector", &[X86TemplateArg::Type(QualType::int())]);
assert_eq!(key, "vector<int>");
}
#[test]
fn test_template_sema_multiple_cached_instantiations() {
let mut ts = X86TemplateSema::new(1024);
let key1 = ts.instantiation_key("A", &[X86TemplateArg::Type(QualType::int())]);
let key2 = ts.instantiation_key("A", &[X86TemplateArg::Type(QualType::float_())]);
ts.mark_instantiated(&key1);
assert!(ts.is_instantiated(&key1));
assert!(!ts.is_instantiated(&key2));
}
#[test]
fn test_constexpr_eval_all_arithmetic_ops() {
let mut ce = X86ConstexprEval::new(512);
let ops = vec![
(BinaryOp::Add, 10, 5, X86ConstValue::Int(15)),
(BinaryOp::Sub, 10, 5, X86ConstValue::Int(5)),
(BinaryOp::Mul, 10, 5, X86ConstValue::Int(50)),
(BinaryOp::Div, 10, 5, X86ConstValue::Int(2)),
(BinaryOp::Mod, 10, 3, X86ConstValue::Int(1)),
(BinaryOp::And, 0xFF, 0x0F, X86ConstValue::Int(0x0F)),
(BinaryOp::Or, 0xF0, 0x0F, X86ConstValue::Int(0xFF)),
(BinaryOp::Xor, 0xFF, 0xF0, X86ConstValue::Int(0x0F)),
];
for (op, a, b, expected) in ops {
let expr = x86_binary(op, x86_int_lit(a), x86_int_lit(b));
assert_eq!(ce.evaluate(&expr), expected);
}
}
#[test]
fn test_constexpr_eval_all_comparison_ops() {
let mut ce = X86ConstexprEval::new(512);
let ops = vec![
(BinaryOp::Eq, 5, 5, true),
(BinaryOp::Eq, 5, 3, false),
(BinaryOp::Ne, 5, 3, true),
(BinaryOp::Ne, 5, 5, false),
(BinaryOp::Lt, 3, 5, true),
(BinaryOp::Lt, 5, 3, false),
(BinaryOp::Gt, 5, 3, true),
(BinaryOp::Gt, 3, 5, false),
(BinaryOp::Le, 3, 3, true),
(BinaryOp::Le, 5, 3, false),
(BinaryOp::Ge, 5, 5, true),
(BinaryOp::Ge, 3, 5, false),
];
for (op, a, b, expected) in ops {
let expr = x86_binary(op, x86_int_lit(a), x86_int_lit(b));
assert_eq!(
ce.evaluate(&expr),
X86ConstValue::Bool(expected),
"op: {:?}",
op
);
}
}
#[test]
fn test_constexpr_eval_uint_arithmetic_ops() {
let mut ce = X86ConstexprEval::new(512);
let a = Expr::UIntLiteral(100);
let b = Expr::UIntLiteral(30);
let add = x86_binary(BinaryOp::Add, a.clone(), b.clone());
assert_eq!(ce.evaluate(&add), X86ConstValue::UInt(130));
let sub = x86_binary(BinaryOp::Sub, a.clone(), b.clone());
assert_eq!(ce.evaluate(&sub), X86ConstValue::UInt(70));
let mul = x86_binary(BinaryOp::Mul, a.clone(), b.clone());
assert_eq!(ce.evaluate(&mul), X86ConstValue::UInt(3000));
let div = x86_binary(BinaryOp::Div, a.clone(), b.clone());
assert_eq!(ce.evaluate(&div), X86ConstValue::UInt(3));
}
#[test]
fn test_constexpr_eval_bool_arithmetic() {
let mut ce = X86ConstexprEval::new(512);
let t = Expr::Unary(UnaryOp::Not, Box::new(x86_int_lit(0)));
assert_eq!(ce.evaluate(&t), X86ConstValue::Bool(true));
let f = Expr::Unary(UnaryOp::Not, Box::new(x86_int_lit(42)));
assert_eq!(ce.evaluate(&f), X86ConstValue::Bool(false));
}
#[test]
fn test_e2e_simple_program() {
let mut sema = X86DeepSema::new("c17", "x86_64-unknown-linux-gnu");
let tu = TranslationUnit {
decls: vec![
Decl::Variable(VarDecl {
name: "g_counter".into(),
ty: QualType::int(),
init: Some(Box::new(Expr::IntLiteral(0))),
linkage: Linkage::External,
is_global: true,
is_extern: false,
is_static: false,
}),
Decl::Function(FunctionDecl {
name: "increment".into(),
ret_ty: QualType::void(),
params: vec![],
body: Some(CompoundStmt {
stmts: vec![Stmt::Expr(Box::new(Expr::Assign(
BinaryOp::Assign,
Box::new(Expr::Ident("g_counter".into())),
Box::new(Expr::Binary(
BinaryOp::Add,
Box::new(Expr::Ident("g_counter".into())),
Box::new(Expr::IntLiteral(1)),
)),
)))],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
}),
],
filename: "prog.c".into(),
};
let ok = sema.analyze_tu(&tu);
assert!(ok);
}
#[test]
fn test_e2e_constexpr_factorial() {
let mut ce = X86ConstexprEval::new(512);
let mut expr = x86_int_lit(1);
for i in 2..=5 {
expr = x86_binary(BinaryOp::Mul, expr, x86_int_lit(i));
}
assert_eq!(ce.evaluate(&expr), X86ConstValue::Int(120));
}
#[test]
fn test_e2e_overload_entire_pipeline() {
let mut sema = X86DeepSema::new("c17", "x86_64-unknown-linux-gnu");
sema.overload_resolution
.add_candidate(X86OverloadCandidate::new(
"max",
vec![QualType::int(), QualType::int()],
QualType::int(),
false,
));
sema.overload_resolution
.add_candidate(X86OverloadCandidate::new(
"max",
vec![QualType::double_(), QualType::double_()],
QualType::double_(),
false,
));
let best = sema.resolve_overload("max", &[QualType::int(), QualType::int()]);
assert_eq!(best, Some(0));
}
#[test]
fn test_e2e_template_with_constexpr() {
let mut sema = X86DeepSema::new("c17", "x86_64-unknown-linux-gnu");
let ok = sema.enter_template("array", &[X86TemplateArg::Type(QualType::int())]);
assert!(ok);
let result =
sema.evaluate_constexpr(&x86_binary(BinaryOp::Mul, x86_int_lit(6), x86_int_lit(7)));
assert_eq!(result, X86ConstValue::Int(42));
sema.leave_template();
}
#[test]
fn test_e2e_full_compile_pipeline() {
let mut sema = X86DeepSema::new("c17", "x86_64-unknown-linux-gnu");
sema.scope_manager.declare_variable(
"answer",
QualType::int(),
false,
true,
&mut sema.diagnostic,
);
sema.scope_manager.enter_function("main", QualType::int());
sema.scope_manager.enter_scope(X86ScopeKind::Function);
let ret_expr = x86_binary(BinaryOp::Add, x86_int_lit(40), x86_int_lit(2));
let ty = sema.expr_validator.expr_type(&ret_expr);
assert!(ty.is_int());
let const_val = sema.constexpr_eval.evaluate(&ret_expr);
assert_eq!(const_val, X86ConstValue::Int(42));
sema.scope_manager.leave_scope();
sema.scope_manager.leave_function();
assert!(sema.diagnostic.has_no_errors());
}
#[test]
fn test_all_lp64_ilp32_configurations() {
let configs = vec![
("x86_64-unknown-linux-gnu", true, 64, 64),
("x86_64-pc-windows-msvc", true, 64, 64),
("x86_64-apple-darwin", true, 64, 64),
("i386-unknown-linux-gnu", false, 32, 32),
("i686-pc-windows-msvc", false, 32, 32),
("i386-unknown-freebsd", false, 32, 32),
];
for (target, is_64, long_bits, ptr_bits) in configs {
let dv = X86DeclValidator::new("c17", target);
assert_eq!(dv.is_64bit, is_64, "Mismatch for {}", target);
assert_eq!(
dv.type_width_bits(&QualType::long()),
long_bits,
"long bits for {}",
target
);
assert_eq!(
dv.type_width_bits(&ptr_ty()),
ptr_bits,
"ptr bits for {}",
target
);
}
}
#[test]
fn test_standard_configurations() {
let standards = vec!["c89", "c99", "c11", "c17", "c++11", "c++14", "c++17"];
for std in standards {
let sema = X86DeepSema::new(std, "x86_64-unknown-linux-gnu");
assert_eq!(sema.standard, std);
assert!(sema.is_64bit);
}
}
#[test]
fn test_diag_state_transitions() {
let mut d = X86DiagnosticEmitter::new();
assert!(!d.should_fail());
assert!(d.has_no_errors());
d.error("transition to error");
assert!(d.should_fail());
assert!(!d.has_no_errors());
d.clear();
assert!(!d.should_fail());
assert!(d.has_no_errors());
}
#[test]
fn test_scope_state_transitions() {
let mut sm = X86ScopeManager::new();
assert!(sm.at_file_scope());
assert!(!sm.in_function());
sm.enter_function("test", QualType::int());
assert!(!sm.at_file_scope());
assert!(sm.in_function());
sm.leave_function();
assert!(sm.at_file_scope());
assert!(!sm.in_function());
}
#[test]
fn test_constexpr_state_transitions() {
let mut ce = X86ConstexprEval::new(512);
assert_eq!(ce.current_depth, 0);
assert!(!ce.is_consteval);
assert!(!ce.is_if_constexpr);
ce.evaluate(&x86_int_lit(42));
assert_eq!(ce.current_depth, 0); }
#[test]
fn test_template_state_transitions() {
let mut ts = X86TemplateSema::new(1024);
assert!(!ts.is_sfinae_active());
assert!(!ts.sfinae_has_errors());
ts.enter_sfinae();
assert!(ts.is_sfinae_active());
ts.record_sfinae_error("test failure");
assert!(ts.sfinae_has_errors());
let errors = ts.leave_sfinae();
assert_eq!(errors.len(), 1);
assert!(!ts.is_sfinae_active());
assert!(!ts.sfinae_has_errors());
}
#[test]
fn test_vector_type_promotion_sse128() {
let tp = X86TypePromotion::new_lp64();
assert_eq!(tp.width(&QualType::int()), 32);
assert_eq!(X86_SSE_WIDTH, 128);
}
#[test]
fn test_vector_type_promotion_avx256() {
let tp = X86TypePromotion::new_lp64();
assert_eq!(tp.width(&QualType::double_()), 64);
assert_eq!(X86_AVX_WIDTH, 256);
}
#[test]
fn test_vector_type_promotion_avx512() {
assert_eq!(X86_AVX512_WIDTH, 512);
}
#[test]
fn test_mmx_width_correct() {
assert_eq!(X86_MMX_WIDTH, 64);
}
#[test]
fn test_int128_type_on_x86() {
let tp = X86TypePromotion::new_lp64();
assert_eq!(X86_MAX_INT_WIDTH, 128);
}
#[test]
fn test_sysv_amd64_struct_return_semantics() {
let mut dv = X86DeclValidator::new("c17", "x86_64-unknown-linux-gnu");
let small_struct = StructDecl {
name: "Small".into(),
fields: vec![
FieldDecl::new("a", QualType::int()),
FieldDecl::new("b", QualType::int()),
],
is_union: false,
};
let mut d = X86DiagnosticEmitter::new();
let ok = dv.check_struct_decl(&small_struct, &mut d);
assert!(ok);
}
#[test]
fn test_win64_struct_return_semantics() {
let dv = X86DeclValidator::new("c17", "x86_64-pc-windows-msvc");
assert!(dv.is_64bit);
}
#[test]
fn test_cdecl_calling_convention_semantics() {
let dv32 = X86DeclValidator::new("c17", "i386-unknown-linux-gnu");
assert!(!dv32.is_64bit);
}
#[test]
fn test_fastcall_semantics() {
let dv32 = X86DeclValidator::new("c17", "i686-pc-windows-msvc");
assert!(!dv32.is_64bit);
}
#[test]
fn test_vectorcall_semantics() {
let dv64 = X86DeclValidator::new("c17", "x86_64-pc-windows-msvc");
assert!(dv64.is_64bit);
}
#[test]
fn test_stack_alignment_semantics() {
let tp = X86TypePromotion::new_lp64();
assert_eq!(tp.width(&QualType::int()), 32);
}
#[test]
fn test_red_zone_semantics() {
let tp = X86TypePromotion::new_lp64();
assert_eq!(tp.pointer_width, 64);
}
#[test]
fn test_x86_little_endian_semantics() {
let mut ce = X86ConstexprEval::new(512);
let expr = x86_binary(BinaryOp::Shl, x86_int_lit(0x01), x86_int_lit(8));
assert_eq!(ce.evaluate(&expr), X86ConstValue::Int(0x100));
}
#[test]
fn test_all_primitive_types_have_width() {
let tp = X86TypePromotion::new_lp64();
let types = vec![
QualType::bool_(),
QualType::char(),
QualType::short(),
QualType::int(),
QualType::long(),
QualType::long_long(),
QualType::float_(),
QualType::double_(),
QualType::long_double(),
];
for ty in &types {
let w = tp.width(ty);
assert!(w > 0, "Type {:?} has zero width", ty);
assert!(w <= 128, "Type {:?} has width > 128", ty);
}
}
#[test]
fn test_all_integer_types_have_rank() {
let tp = X86TypePromotion::new_lp64();
let types = vec![
QualType::bool_(),
QualType::char(),
QualType::short(),
QualType::int(),
QualType::long(),
QualType::long_long(),
];
let mut ranks: Vec<u32> = types.iter().map(|t| tp.rank(t)).collect();
for i in 1..ranks.len() {
assert!(
ranks[i - 1] <= ranks[i],
"rank not non-decreasing at index {}",
i
);
}
}
#[test]
fn test_all_types_to_string_non_empty() {
let types = vec![
QualType::bool_(),
QualType::char(),
QualType::short(),
QualType::int(),
QualType::long(),
QualType::long_long(),
QualType::float_(),
QualType::double_(),
QualType::void(),
];
for ty in &types {
let s = format!("{}", ty);
assert!(!s.is_empty());
}
}
#[test]
fn test_all_scope_kinds_enter_leave() {
let kinds = vec![
X86ScopeKind::Block,
X86ScopeKind::Function,
X86ScopeKind::FunctionPrototype,
X86ScopeKind::Namespace,
X86ScopeKind::Class,
X86ScopeKind::TemplateParam,
X86ScopeKind::Enum,
X86ScopeKind::TryBlock,
X86ScopeKind::CatchBlock,
X86ScopeKind::Lambda,
];
let mut sm = X86ScopeManager::new();
for kind in &kinds {
sm.enter_scope(*kind);
}
assert_eq!(sm.scope_depth(), 1 + kinds.len());
for _ in &kinds {
sm.leave_scope();
}
assert_eq!(sm.scope_depth(), 1);
}
#[test]
fn test_nearest_scope_of_each_kind() {
let mut sm = X86ScopeManager::new();
sm.enter_scope(X86ScopeKind::Function);
sm.enter_scope(X86ScopeKind::Block);
sm.enter_scope(X86ScopeKind::Lambda);
assert!(sm.nearest_scope_of_kind(X86ScopeKind::Lambda).is_some());
assert!(sm.nearest_scope_of_kind(X86ScopeKind::Block).is_some());
assert!(sm.nearest_scope_of_kind(X86ScopeKind::Function).is_some());
assert!(sm.nearest_scope_of_kind(X86ScopeKind::Class).is_none());
sm.leave_scope();
sm.leave_scope();
sm.leave_scope();
}
#[test]
fn test_identity_conversion_for_all_types() {
let ic = X86ImplicitConversion::new(true);
let types = vec![
QualType::char(),
QualType::short(),
QualType::int(),
QualType::long(),
QualType::float_(),
QualType::double_(),
QualType::bool_(),
];
for ty in &types {
let result = ic.check_implicit_conversion(ty, ty);
assert!(result.valid);
assert_eq!(result.rank, X86ConversionRank::ExactMatch);
assert_eq!(result.kind, X86ConvKind::Identity);
}
}
#[test]
fn test_void_cannot_convert() {
let ic = X86ImplicitConversion::new(true);
let result = ic.check_implicit_conversion(&QualType::void(), &QualType::int());
assert!(!result.valid);
let result2 = ic.check_implicit_conversion(&QualType::int(), &QualType::void());
assert!(!result2.valid);
}
#[test]
fn test_full_pipeline_struct_typedef_enum_func() {
let mut sema = X86DeepSema::new("c17", "x86_64-unknown-linux-gnu");
let tu = TranslationUnit {
decls: vec![
Decl::Typedef(TypedefDecl {
name: "my_int".into(),
underlying: QualType::int(),
}),
Decl::Struct(StructDecl {
name: "Point".into(),
fields: vec![
FieldDecl::new("x", QualType::int()),
FieldDecl::new("y", QualType::int()),
],
is_union: false,
}),
Decl::Enum(EnumDecl {
name: "Color".into(),
variants: vec![
EnumVariant::new("RED", Some(0)),
EnumVariant::new("GREEN", Some(1)),
],
underlying: QualType::int(),
}),
Decl::Function(FunctionDecl {
name: "main".into(),
ret_ty: QualType::int(),
params: vec![],
body: Some(CompoundStmt {
stmts: vec![Stmt::Return(Some(Box::new(Expr::IntLiteral(0))))],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
}),
],
filename: "full.c".into(),
};
let ok = sema.analyze_tu(&tu);
assert!(ok);
assert!(sema.constexpr_eval.enum_count() > 0);
assert!(sema.scope_manager.total_declarations() > 0);
}
#[test]
fn test_full_pipeline_with_template_and_overload() {
let mut sema = X86DeepSema::new("c++17", "x86_64-unknown-linux-gnu");
sema.template_sema
.begin_template_params(vec![X86TemplateParam::type_param("T")]);
sema.overload_resolution
.add_candidate(X86OverloadCandidate::new(
"func",
vec![QualType::int()],
QualType::void(),
false,
));
sema.overload_resolution
.add_candidate(X86OverloadCandidate::new(
"func",
vec![QualType::double_()],
QualType::void(),
false,
));
let best = sema.resolve_overload("func", &[QualType::int()]);
assert_eq!(best, Some(0));
let result =
sema.evaluate_constexpr(&x86_binary(BinaryOp::Mul, x86_int_lit(7), x86_int_lit(6)));
assert_eq!(result, X86ConstValue::Int(42));
}
#[test]
fn test_burnin_all_components_interact_correctly() {
let mut sema = X86DeepSema::new("c17", "x86_64-unknown-linux-gnu");
sema.scope_manager.enter_scope(X86ScopeKind::Function);
sema.scope_manager.declare_variable(
"a",
QualType::int(),
false,
false,
&mut sema.diagnostic,
);
sema.scope_manager.declare_variable(
"b",
QualType::double_(),
false,
false,
&mut sema.diagnostic,
);
assert!(sema.scope_manager.lookup("a").is_some());
let promoted = sema.type_promotion.integer_promotion(&QualType::char());
assert!(promoted.is_int());
let conv = sema
.implicit_conversion
.check_implicit_conversion(&QualType::char(), &QualType::int());
assert!(conv.valid);
let expr = x86_binary(BinaryOp::Add, x86_int_lit(3), x86_int_lit(4));
let ty = sema.expr_validator.expr_type(&expr);
assert!(ty.is_int());
let val = sema.constexpr_eval.evaluate(&expr);
assert_eq!(val, X86ConstValue::Int(7));
sema.scope_manager.leave_scope();
assert!(sema.diagnostic.has_no_errors());
}
#[test]
fn test_burnin_overload_resolution_stress() {
let mut sema = X86DeepSema::new("c17", "x86_64-unknown-linux-gnu");
for i in 0..100 {
sema.overload_resolution
.add_candidate(X86OverloadCandidate::new(
&format!("f{}", i),
vec![QualType::int()],
QualType::void(),
false,
));
}
let best = sema.resolve_overload("f0", &[QualType::int()]);
assert!(best.is_some());
}
#[test]
fn test_burnin_constexpr_nested_evaluation() {
let mut ce = X86ConstexprEval::new(512);
let mut expr = x86_int_lit(1);
for i in 0..200 {
expr = x86_binary(BinaryOp::Add, expr, x86_int_lit(1));
}
let result = ce.evaluate(&expr);
assert_eq!(result, X86ConstValue::Int(201));
}
#[test]
fn test_burnin_scope_stress_nested_lookups() {
let mut sm = X86ScopeManager::new();
let mut d = X86DiagnosticEmitter::new();
sm.declare_variable("x", QualType::int(), false, false, &mut d);
for depth in 0..50 {
sm.enter_scope(X86ScopeKind::Block);
sm.declare_variable(
&format!("var_{}", depth),
QualType::int(),
false,
false,
&mut d,
);
}
assert!(sm.lookup("var_49").is_some());
assert!(sm.lookup("x").is_some()); assert!(sm.lookup("var_0").is_some()); for _ in 0..50 {
sm.leave_scope();
}
assert_eq!(sm.scope_depth(), 1);
}
#[test]
fn test_burnin_diagnostic_deep_backtrace() {
let mut d = X86DiagnosticEmitter::new();
for i in 0..10 {
d.push_template_backtrace(
&format!("Level{}", i),
vec![format!("Arg{}", i)],
Some(format!("t{}.h", i)),
i as u32 + 1,
1,
);
}
d.error_at("deep template error", Some("main.c"), 100, 1);
let formatted = d.format_all();
assert!(formatted.contains("Level0"));
assert!(formatted.contains("Level9"));
for _ in 0..10 {
d.pop_template_backtrace();
}
}
#[test]
fn test_burnin_all_binary_ops_in_constexpr() {
let mut ce = X86ConstexprEval::new(512);
use BinaryOp::*;
let tests: Vec<(BinaryOp, i64, i64, X86ConstValue)> = vec![
(Add, 100, 50, X86ConstValue::Int(150)),
(Sub, 100, 50, X86ConstValue::Int(50)),
(Mul, 12, 12, X86ConstValue::Int(144)),
(Div, 100, 4, X86ConstValue::Int(25)),
(Mod, 100, 7, X86ConstValue::Int(2)),
(And, 0xFF00, 0x0FF0, X86ConstValue::Int(0x0F00)),
(Or, 0xF000, 0x0F00, X86ConstValue::Int(0xFF00)),
(Xor, 0xFFFF, 0x00FF, X86ConstValue::Int(0xFF00)),
(Shl, 1, 10, X86ConstValue::Int(1024)),
(Shr, 1024, 10, X86ConstValue::Int(1)),
(Eq, 5, 5, X86ConstValue::Bool(true)),
(Ne, 5, 3, X86ConstValue::Bool(true)),
(Lt, 3, 5, X86ConstValue::Bool(true)),
(Gt, 5, 3, X86ConstValue::Bool(true)),
(Le, 5, 5, X86ConstValue::Bool(true)),
(Ge, 5, 5, X86ConstValue::Bool(true)),
(LogicAnd, 1, 1, X86ConstValue::Bool(true)),
(LogicOr, 0, 1, X86ConstValue::Bool(true)),
];
for (op, a, b, expected) in tests {
let expr = x86_binary(op, x86_int_lit(a), x86_int_lit(b));
assert_eq!(ce.evaluate(&expr), expected, "Failed for {:?}", op);
}
}
#[test]
fn smoke_x86_deep_sema_creation() {
let sema = X86DeepSema::default();
assert_eq!(sema.standard, "c17");
assert!(sema.is_64bit);
}
#[test]
fn smoke_x86_scope_manager_creation() {
let sm = X86ScopeManager::default();
assert_eq!(sm.scope_depth(), 1);
assert!(sm.at_file_scope());
}
#[test]
fn smoke_x86_decl_validator_creation() {
let dv = X86DeclValidator::default();
assert!(dv.is_64bit);
assert_eq!(dv.standard, "c17");
}
#[test]
fn smoke_x86_expr_validator_creation() {
let ev = X86ExprValidator::default();
assert!(ev.is_64bit);
}
#[test]
fn smoke_x86_type_promotion_creation() {
let tp = X86TypePromotion::default();
assert!(tp.is_64bit);
assert_eq!(tp.int_width, 32);
}
#[test]
fn smoke_x86_implicit_conversion_creation() {
let ic = X86ImplicitConversion::default();
assert!(ic.promotion.is_64bit);
}
#[test]
fn smoke_x86_overload_resolution_creation() {
let o = X86OverloadResolution::default();
assert!(o.candidates.is_empty());
}
#[test]
fn smoke_x86_template_sema_creation() {
let ts = X86TemplateSema::default();
assert_eq!(ts.current_depth(), 0);
assert!(!ts.is_sfinae_active());
}
#[test]
fn smoke_x86_constexpr_eval_creation() {
let ce = X86ConstexprEval::default();
assert_eq!(ce.enum_count(), 0);
assert_eq!(ce.max_depth, 512);
}
#[test]
fn smoke_x86_diagnostic_emitter_creation() {
let de = X86DiagnosticEmitter::default();
assert!(de.diagnostics.is_empty());
assert!(!de.warnings_as_errors);
}
#[test]
fn smoke_all_convenience_functions() {
let sema64 = make_x86_64_deep_sema();
assert!(sema64.is_64bit);
let sema32 = make_x86_32_deep_sema();
assert!(!sema32.is_64bit);
let tu = x86_make_tu(vec![]);
let (ok, _, _) = analyze_x86_tu(&tu);
assert!(ok);
let func = x86_make_function("f", QualType::int(), vec![], None);
let (ok2, _, _) = analyze_x86_function(&func);
assert!(ok2);
}
#[test]
fn smoke_all_make_helpers() {
let lit = x86_int_lit(42);
assert!(matches!(lit, Expr::IntLiteral(42)));
let ident = x86_ident("x");
assert!(matches!(ident, Expr::Ident(_)));
let bin = x86_binary(BinaryOp::Add, x86_int_lit(1), x86_int_lit(2));
assert!(matches!(bin, Expr::Binary { .. }));
let assign = x86_assign(x86_ident("a"), x86_int_lit(10));
assert!(matches!(assign, Expr::Assign(..)));
let ret = x86_return(Some(x86_int_lit(0)));
assert!(matches!(ret, Stmt::Return(_)));
let compound = x86_compound(vec![]);
assert!(matches!(compound, Stmt::Compound(_)));
let call = x86_call(x86_ident("f"), vec![x86_int_lit(1)]);
assert!(matches!(call, Expr::Call { .. }));
let func = x86_make_function("main", QualType::int(), vec![], None);
assert_eq!(func.name, "main");
let var = x86_make_variable("g", QualType::int(), None);
assert_eq!(var.name, "g");
let tu = x86_make_tu(vec![]);
assert!(tu.decls.is_empty());
}
}