use std::collections::BTreeSet;
use super::bytecode::{Chunk, Const, Op};
include!(concat!(env!("OUT_DIR"), "/bridge_tables.rs"));
#[derive(Clone, Copy, PartialEq)]
pub enum Engine {
Fast,
Parallel,
Both,
}
pub struct BridgeTable {
pub engine: Engine,
pub recv: &'static str,
pub names: &'static [&'static str],
}
pub struct Finding {
pub method: String,
pub recv: Option<&'static str>,
pub func: String,
}
impl Finding {
pub fn message(&self) -> String {
match self.recv {
Some(recv) => format!(
"`{}` on {} is not implemented by the interpreter, in `{}`",
self.method, recv, self.func
),
None => format!(
"`{}` is not implemented by the interpreter, in `{}`",
self.method, self.func
),
}
}
}
#[derive(Clone, Copy, PartialEq)]
enum Ty {
Str,
Int,
Float,
Bool,
Char,
Vec,
Unknown,
}
impl Ty {
fn name(self) -> Option<&'static str> {
match self {
Ty::Str => Some("Str"),
Ty::Vec => Some("Vec"),
Ty::Int | Ty::Float | Ty::Bool | Ty::Char | Ty::Unknown => None,
}
}
}
fn applies(table: &BridgeTable, engine: Engine) -> bool {
table.engine == engine || table.engine == Engine::Both
}
fn any_name(engine: Engine, method: &str) -> bool {
BUILTIN_IDS.contains(&method)
|| BRIDGE_TABLES
.iter()
.any(|t| applies(t, engine) && t.names.contains(&method))
}
fn on_recv(engine: Engine, recv: &str, method: &str) -> bool {
let mut saw_table = false;
for table in BRIDGE_TABLES.iter().filter(|t| applies(t, engine)) {
if table.recv == recv {
saw_table = true;
if table.names.contains(&method) {
return true;
}
}
if table.recv == "*" && table.names.contains(&method) {
return true;
}
}
if !saw_table {
return any_name(engine, method);
}
BUILTIN_IDS.contains(&method)
}
const UNIVERSAL: &[&str] = &["clone", "to_string"];
fn walk(chunk: &Chunk, engine: Engine, user: &BTreeSet<String>, out: &mut Vec<Finding>) {
for (index, op) in chunk.code.iter().enumerate() {
if let Op::Method { recv, name, .. } = op {
let method = &chunk.names[*name as usize].text;
if UNIVERSAL.contains(&method.as_str()) || user.contains(method) {
continue;
}
let ty = infer(chunk, index, *recv);
let known = match ty.name() {
Some(recv_name) => on_recv(engine, recv_name, method),
None => any_name(engine, method),
};
if !known {
out.push(Finding {
method: method.clone(),
recv: ty.name(),
func: chunk.name.clone(),
});
}
}
}
for child in &chunk.children {
walk(child, engine, user, out);
}
}
fn infer(chunk: &Chunk, before: usize, reg: u16) -> Ty {
for op in chunk.code[..before].iter().rev() {
match op {
Op::LoadConst { dst, k } if *dst == reg => {
return match chunk.consts[*k as usize] {
Const::Str(_) => Ty::Str,
Const::Char(_) => Ty::Char,
Const::Float(_) => Ty::Float,
Const::Bytes(_) => Ty::Vec,
};
}
Op::LoadInt { dst, .. } if *dst == reg => return Ty::Int,
Op::LoadBool { dst, .. } if *dst == reg => return Ty::Bool,
Op::MakeVec { dst, .. } if *dst == reg => return Ty::Vec,
Op::Fmt { dst, .. } if *dst == reg => return Ty::Str,
_ => {
if writes(op) == Some(reg) {
return Ty::Unknown;
}
}
}
}
Ty::Unknown
}
fn writes(op: &Op) -> Option<u16> {
match op {
Op::Move { dst, .. }
| Op::Bin { dst, .. }
| Op::Un { dst, .. }
| Op::Method { dst, .. }
| Op::CallFn { dst, .. }
| Op::CallPath { dst, .. }
| Op::CallValue { dst, .. }
| Op::MakeStruct { dst, .. }
| Op::MakeEnum { dst, .. }
| Op::LoadGlobal { dst, .. }
| Op::LoadUpvalue { dst, .. }
| Op::Index { dst, .. }
| Op::GetField { dst, .. } => Some(*dst),
_ => None,
}
}
pub fn report(
functions: &[std::rc::Rc<Chunk>],
methods: impl Iterator<Item = String>,
engine: Engine,
) -> Vec<Finding> {
let user: BTreeSet<String> = methods.collect();
let mut out = Vec::new();
for chunk in functions {
walk(chunk, engine, &user, &mut out);
}
let mut seen = BTreeSet::new();
out.retain(|f| seen.insert((f.method.clone(), f.recv)));
out
}