use crate::ssa::type_facts::TypeKind;
use super::domain::{BoolState, ConstValue, Nullability, PathEnv, RelOp, TypeSet, ValueFact};
use super::lower::{CompOp, ConditionExpr, Operand};
pub fn refine_env(env: &PathEnv, cond: &ConditionExpr, polarity: bool) -> PathEnv {
if env.is_unsat() {
return env.clone();
}
let effective = if polarity {
cond.clone()
} else {
cond.negate()
};
let mut result = env.clone();
apply_condition(&mut result, &effective);
result
}
pub fn is_satisfiable(env: &PathEnv) -> bool {
!env.is_unsat()
}
fn apply_condition(env: &mut PathEnv, cond: &ConditionExpr) {
match cond {
ConditionExpr::NullCheck { var, is_null } => {
let mut fact = ValueFact::top();
if *is_null {
fact.null = Nullability::Null;
fact.types = TypeSet::singleton(&TypeKind::Null);
} else {
fact.null = Nullability::NonNull;
}
env.refine(*var, &fact);
}
ConditionExpr::TypeCheck {
var,
type_name,
positive,
} => {
if let Some(kind) = parse_type_name(type_name) {
let ts = TypeSet::singleton(&kind);
let mut fact = ValueFact::top();
if *positive {
fact.types = ts;
if kind != TypeKind::Null {
fact.null = Nullability::NonNull;
}
} else {
fact.types = ts.complement();
}
env.refine(*var, &fact);
}
}
ConditionExpr::BoolTest { var } => {
let existing = env.get(*var);
if existing.types == TypeSet::singleton(&TypeKind::Bool) {
let mut fact = ValueFact::top();
fact.bool_state = BoolState::True;
fact.null = Nullability::NonNull;
env.refine(*var, &fact);
}
}
ConditionExpr::Comparison { lhs, op, rhs } => {
apply_comparison(env, lhs, *op, rhs);
}
ConditionExpr::Unknown => {
}
}
}
fn apply_comparison(env: &mut PathEnv, lhs: &Operand, op: CompOp, rhs: &Operand) {
match (lhs, rhs) {
(Operand::Value(v), Operand::Const(c)) => {
apply_value_const(env, *v, op, c);
}
(Operand::Const(c), Operand::Value(v)) => {
apply_value_const(env, *v, op.flip(), c);
}
(Operand::Value(a), Operand::Value(b)) => match op {
CompOp::Eq => env.assert_equal(*a, *b),
CompOp::Neq => env.assert_not_equal(*a, *b),
CompOp::Lt => env.assert_relational(*a, RelOp::Lt, *b),
CompOp::Gt => env.assert_relational(*b, RelOp::Lt, *a),
CompOp::Le => env.assert_relational(*a, RelOp::Le, *b),
CompOp::Ge => env.assert_relational(*b, RelOp::Le, *a),
},
_ => {}
}
}
fn apply_value_const(env: &mut PathEnv, v: crate::ssa::ir::SsaValue, op: CompOp, c: &ConstValue) {
let mut fact = ValueFact::top();
match op {
CompOp::Eq => {
fact.exact = Some(c.clone());
match c {
ConstValue::Int(i) => {
fact.lo = Some(*i);
fact.hi = Some(*i);
fact.types = TypeSet::singleton(&TypeKind::Int);
fact.null = Nullability::NonNull;
}
ConstValue::Null => {
fact.null = Nullability::Null;
fact.types = TypeSet::singleton(&TypeKind::Null);
}
ConstValue::Bool(b) => {
fact.bool_state = if *b {
BoolState::True
} else {
BoolState::False
};
fact.types = TypeSet::singleton(&TypeKind::Bool);
fact.null = Nullability::NonNull;
}
ConstValue::Str(_) => {
fact.types = TypeSet::singleton(&TypeKind::String);
fact.null = Nullability::NonNull;
}
}
}
CompOp::Neq => {
if c == &ConstValue::Null {
fact.null = Nullability::NonNull;
}
fact.excluded.push(c.clone());
}
CompOp::Lt => {
if let ConstValue::Int(i) = c {
fact.hi = Some(*i);
fact.hi_strict = true;
fact.null = Nullability::NonNull;
}
}
CompOp::Le => {
if let ConstValue::Int(i) = c {
fact.hi = Some(*i);
fact.null = Nullability::NonNull;
}
}
CompOp::Gt => {
if let ConstValue::Int(i) = c {
fact.lo = Some(*i);
fact.lo_strict = true;
fact.null = Nullability::NonNull;
}
}
CompOp::Ge => {
if let ConstValue::Int(i) = c {
fact.lo = Some(*i);
fact.null = Nullability::NonNull;
}
}
}
env.refine(v, &fact);
}
pub fn parse_type_name(name: &str) -> Option<TypeKind> {
use crate::ssa::type_facts::TypeHierarchy;
primitive_type_alias(name)
.or_else(|| class_name_to_type_kind(name))
.or_else(|| TypeHierarchy::resolve_kind(name))
}
fn primitive_type_alias(name: &str) -> Option<TypeKind> {
match name.to_ascii_lowercase().as_str() {
"string" | "str" => Some(TypeKind::String),
"number" | "int" | "integer" | "i32" | "i64" | "u32" | "u64" | "float" | "double"
| "numeric" => Some(TypeKind::Int),
"boolean" | "bool" => Some(TypeKind::Bool),
"object" => Some(TypeKind::Object),
"array" | "list" => Some(TypeKind::Array),
"null" | "nil" | "none" | "undefined" => Some(TypeKind::Null),
_ => None,
}
}
pub fn class_name_to_type_kind(name: &str) -> Option<TypeKind> {
match name {
"String" | "CharSequence" | "StringBuilder" | "StringBuffer" => Some(TypeKind::String),
"Integer" | "Long" | "Short" | "Byte" | "Number" | "BigInteger" | "BigDecimal"
| "Double" | "Float" => Some(TypeKind::Int),
"Boolean" => Some(TypeKind::Bool),
"List" | "ArrayList" | "Collection" | "Set" | "HashSet" => Some(TypeKind::Array),
"URL" | "URI" => Some(TypeKind::Url),
"HttpClient" | "CloseableHttpClient" | "OkHttpClient" | "WebClient"
| "RestTemplate" => Some(TypeKind::HttpClient),
"HttpServletResponse" | "HttpResponse" | "ServletResponse"
| "ResponseEntity" => {
Some(TypeKind::HttpResponse)
}
"Connection" | "DataSource" | "MongoClient"
| "Statement" | "PreparedStatement" => Some(TypeKind::DatabaseConnection),
"File" | "Path"
| "InputStream" | "OutputStream" | "Reader" | "Writer" | "PrintWriter"
| "BufferedInputStream" | "BufferedOutputStream" => Some(TypeKind::FileHandle),
"requests.Response" | "http.client.HTTPResponse" | "urllib3.response.HTTPResponse"
| "httpx.Response" | "aiohttp.ClientResponse" => Some(TypeKind::HttpResponse),
"requests.Session" | "urllib3.PoolManager" | "aiohttp.ClientSession"
| "httpx.Client" | "httpx.AsyncClient" => Some(TypeKind::HttpClient),
"sqlite3.Connection" | "psycopg2.connection" | "mysql.connector.connection"
| "pymongo.MongoClient" | "redis.Redis" => Some(TypeKind::DatabaseConnection),
"io.TextIOWrapper" | "io.BufferedReader" | "io.BufferedWriter" | "io.FileIO"
| "io.BytesIO" | "io.StringIO" => Some(TypeKind::FileHandle),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_numeric_to_int() {
assert_eq!(parse_type_name("numeric"), Some(TypeKind::Int));
}
#[test]
fn parse_string_case_insensitive() {
assert_eq!(parse_type_name("String"), Some(TypeKind::String));
}
#[test]
fn parse_integer_class_name() {
assert_eq!(parse_type_name("Integer"), Some(TypeKind::Int));
}
#[test]
fn parse_http_servlet_response() {
assert_eq!(
parse_type_name("HttpServletResponse"),
Some(TypeKind::HttpResponse)
);
}
#[test]
fn parse_closeable_http_client_via_hierarchy() {
assert_eq!(
parse_type_name("CloseableHttpClient"),
Some(TypeKind::HttpClient)
);
}
#[test]
fn parse_file_input_stream_via_hierarchy() {
assert_eq!(
parse_type_name("FileInputStream"),
Some(TypeKind::FileHandle)
);
}
#[test]
fn parse_java_statement_to_db() {
assert_eq!(
parse_type_name("Statement"),
Some(TypeKind::DatabaseConnection)
);
assert_eq!(
parse_type_name("PreparedStatement"),
Some(TypeKind::DatabaseConnection)
);
}
#[test]
fn parse_java_io_stream_to_file_handle() {
assert_eq!(parse_type_name("InputStream"), Some(TypeKind::FileHandle));
assert_eq!(parse_type_name("OutputStream"), Some(TypeKind::FileHandle));
assert_eq!(parse_type_name("Reader"), Some(TypeKind::FileHandle));
assert_eq!(parse_type_name("Writer"), Some(TypeKind::FileHandle));
assert_eq!(parse_type_name("PrintWriter"), Some(TypeKind::FileHandle));
}
#[test]
fn parse_java_response_entity() {
assert_eq!(
parse_type_name("ResponseEntity"),
Some(TypeKind::HttpResponse)
);
}
#[test]
fn parse_python_qualified_http_client() {
assert_eq!(
parse_type_name("requests.Session"),
Some(TypeKind::HttpClient)
);
assert_eq!(
parse_type_name("aiohttp.ClientSession"),
Some(TypeKind::HttpClient)
);
assert_eq!(parse_type_name("httpx.Client"), Some(TypeKind::HttpClient));
assert_eq!(
parse_type_name("httpx.AsyncClient"),
Some(TypeKind::HttpClient)
);
assert_eq!(
parse_type_name("urllib3.PoolManager"),
Some(TypeKind::HttpClient)
);
}
#[test]
fn parse_python_qualified_db_connection() {
assert_eq!(
parse_type_name("sqlite3.Connection"),
Some(TypeKind::DatabaseConnection)
);
assert_eq!(
parse_type_name("psycopg2.connection"),
Some(TypeKind::DatabaseConnection)
);
assert_eq!(
parse_type_name("mysql.connector.connection"),
Some(TypeKind::DatabaseConnection)
);
}
#[test]
fn parse_python_qualified_http_response() {
assert_eq!(
parse_type_name("requests.Response"),
Some(TypeKind::HttpResponse)
);
assert_eq!(
parse_type_name("httpx.Response"),
Some(TypeKind::HttpResponse)
);
assert_eq!(
parse_type_name("aiohttp.ClientResponse"),
Some(TypeKind::HttpResponse)
);
}
#[test]
fn parse_python_qualified_file_handle() {
assert_eq!(
parse_type_name("io.TextIOWrapper"),
Some(TypeKind::FileHandle)
);
assert_eq!(parse_type_name("io.BytesIO"), Some(TypeKind::FileHandle));
assert_eq!(parse_type_name("io.StringIO"), Some(TypeKind::FileHandle));
}
}