use std::cell::RefCell;
use std::collections::HashMap;
use walrus::ir::Value;
use crate::ir::{Expr, Literal};
use crate::wasm_analysis::{AnalyzedModule, StackValue, TrackedHostCall};
thread_local! {
static MEMORY_STRINGS: RefCell<HashMap<usize, String>> = RefCell::new(HashMap::new());
}
pub fn set_memory_strings(strings: &HashMap<usize, String>) {
MEMORY_STRINGS.with(|ms| {
*ms.borrow_mut() = strings.clone();
});
}
pub fn clear_memory_strings() {
MEMORY_STRINGS.with(|ms| {
ms.borrow_mut().clear();
});
}
const TAG_SYMBOL_SMALL: u64 = 14;
const CODE_BITS: u32 = 6;
const MAX_SMALL_CHARS: u32 = 9;
const TAG_SYMBOL_SMALL_U8: u64 = TAG_SYMBOL_SMALL;
pub fn extract_u32_val(sv: &StackValue) -> Option<u32> {
if let StackValue::Const(Value::I64(v)) = sv {
let uv = *v as u64;
if uv & 0xFF == 4 {
return Some((uv >> 32) as u32);
}
}
let stripped = strip_val_boilerplate(sv);
match &stripped {
StackValue::Const(Value::I32(v)) => Some(*v as u32),
StackValue::Const(Value::I64(v)) => Some(*v as u32),
_ => None,
}
}
pub fn strip_val_boilerplate(val: &StackValue) -> StackValue {
use crate::ir::BinOp as B;
match val {
StackValue::BinOp { op: B::Shr, left, right } => {
let is_decode_shift = matches!(
right.as_ref(),
StackValue::Const(
Value::I64(32) | Value::I32(32)
| Value::I64(8) | Value::I32(8)
)
);
if is_decode_shift {
return strip_val_boilerplate(left);
}
StackValue::BinOp {
op: B::Shr,
left: Box::new(strip_val_boilerplate(left)),
right: Box::new(strip_val_boilerplate(right)),
}
}
StackValue::BinOp { op: B::BitOr, left, right } => {
let is_small_tag = matches!(
right.as_ref(),
StackValue::Const(Value::I64(t))
if (*t as u64) <= 14
);
if is_small_tag {
if let StackValue::BinOp {
op: B::Shl,
left: inner,
right: shift,
} = left.as_ref()
{
let is_val_shift = matches!(
shift.as_ref(),
StackValue::Const(
Value::I64(32) | Value::I32(32)
| Value::I64(8) | Value::I32(8)
)
);
if is_val_shift {
return strip_val_boilerplate(inner);
}
}
if let StackValue::BinOp {
op: B::BitAnd,
left: inner,
right: mask,
} = left.as_ref()
{
if matches!(
mask.as_ref(),
StackValue::Const(Value::I64(m))
if *m as u64 == 0xFFFF_FFFF_0000_0000
) {
return strip_val_boilerplate(inner);
}
}
}
if matches!(
right.as_ref(),
StackValue::Const(Value::I64(0) | Value::I32(0))
) {
return strip_val_boilerplate(left);
}
StackValue::BinOp {
op: B::BitOr,
left: Box::new(strip_val_boilerplate(left)),
right: Box::new(strip_val_boilerplate(right)),
}
}
StackValue::BinOp { op: B::BitAnd, left, right } => {
if let StackValue::Const(Value::I64(m)) = right.as_ref() {
let mu = *m as u64;
if mu == 0xFFFF_FFFF_0000_0000 {
return strip_val_boilerplate(left);
}
if mu == 0x0000_0000_FFFF_FFFF {
return strip_val_boilerplate(left);
}
let tag = mu & 0xFF;
let high = mu >> 32;
if tag <= 14 && high == 0xFFFF_FFFF {
return strip_val_boilerplate(left);
}
}
StackValue::BinOp {
op: B::BitAnd,
left: Box::new(strip_val_boilerplate(left)),
right: Box::new(strip_val_boilerplate(right)),
}
}
StackValue::BinOp { op, left, right } => {
StackValue::BinOp {
op: *op,
left: Box::new(strip_val_boilerplate(left)),
right: Box::new(strip_val_boilerplate(right)),
}
}
StackValue::UnOp { op, operand } => {
StackValue::UnOp {
op: *op,
operand: Box::new(strip_val_boilerplate(operand)),
}
}
_ => val.clone(),
}
}
pub fn unwrap_val_encoding(
ret: &StackValue,
host_calls: &[TrackedHostCall],
) -> Option<StackValue> {
match ret {
StackValue::CallResult(call_id) => {
for call in host_calls.iter().rev() {
if call.call_site_id == *call_id {
let name = call.host_func.name;
if matches!(
name,
"obj_from_u64" | "obj_from_i64"
| "obj_from_u128_pieces" | "obj_from_i128_pieces"
| "obj_from_u256_pieces" | "obj_from_i256_pieces"
) {
return call.args.first().cloned();
}
break;
}
}
None
}
StackValue::BinOp {
op: crate::ir::BinOp::BitOr,
left,
right,
} => {
if let StackValue::BinOp {
op: crate::ir::BinOp::Shl,
left: inner,
right: shift,
} = left.as_ref()
{
let is_shift_8 = matches!(
shift.as_ref(),
StackValue::Const(Value::I64(8))
);
let is_small_tag = matches!(
right.as_ref(),
StackValue::Const(Value::I64(t)) if *t <= 14
);
if is_shift_8 && is_small_tag {
return Some(*inner.clone());
}
}
None
}
_ => None,
}
}
pub fn try_decode_symbol_small(val: i64) -> Option<String> {
let v = val as u64;
if v & 0xFF != TAG_SYMBOL_SMALL {
return None;
}
let mut body = (v >> 8) & 0x00ff_ffff_ffff_ffff;
let mut chars = Vec::with_capacity(MAX_SMALL_CHARS as usize);
for _ in 0..MAX_SMALL_CHARS {
let code = (body >> ((MAX_SMALL_CHARS - 1) * CODE_BITS)) & 0x3F;
body <<= CODE_BITS;
if code == 0 {
continue; }
let ch = match code as u8 {
1 => b'_',
n @ 2..=11 => b'0' + n - 2,
n @ 12..=37 => b'A' + n - 12,
n @ 38..=63 => b'a' + n - 38,
_ => return None,
};
chars.push(ch);
}
if chars.is_empty() {
return None;
}
Some(String::from_utf8(chars).ok()?)
}
pub fn decode_symbol_vals_from_bytes(bytes: &[u8]) -> Vec<String> {
let mut result = Vec::new();
for chunk in bytes.chunks_exact(8) {
let val = i64::from_le_bytes(chunk.try_into().unwrap());
if let Some(sym) = try_decode_symbol_small(val) {
result.push(sym);
} else {
result.push(format!("unknown_{:016x}", val as u64));
}
}
result
}
pub fn decode_keys_from_linear_memory(
keys_ptr: u32,
len: u32,
analyzed: &AnalyzedModule,
) -> Option<Vec<String>> {
let bytes = analyzed.read_linear_memory(keys_ptr, len * 8)?;
let syms = decode_symbol_vals_from_bytes(&bytes);
if syms.iter().all(|s| !s.starts_with("unknown_")) {
return Some(syms);
}
let mut result = Vec::new();
for chunk in bytes.chunks_exact(8) {
let ptr = u32::from_le_bytes(chunk[0..4].try_into().ok()?);
let slen = u32::from_le_bytes(chunk[4..8].try_into().ok()?);
if slen > 64 { return None; } if let Some(str_bytes) = analyzed.read_linear_memory(ptr, slen) {
if let Ok(s) = String::from_utf8(str_bytes) {
result.push(s);
} else {
return None;
}
} else {
return None;
}
}
if result.len() == len as usize {
return Some(result);
}
None
}
fn try_decode_val(val: i64) -> Option<Expr> {
let v = val as u64;
let tag = v & 0xFF;
match tag {
0 if v == 0 => Some(Expr::Literal(Literal::I64(0))),
1 if v >> 8 == 0 => Some(Expr::Literal(Literal::I64(1))),
2 if v >> 8 == 0 => Some(Expr::Literal(Literal::Unit)),
3 => {
let error_code = (v >> 32) as u32;
let error_type = ((v >> 8) & 0xFF_FFFF) as u32;
if error_type == 0 {
Some(Expr::Var(format!("__contract_error_{error_code}")))
} else {
Some(Expr::Raw(format!("/* Error(type={error_type}, code={error_code}) */")))
}
}
4 => {
let value = (v >> 32) as u32;
Some(Expr::Literal(Literal::I64(value as i64)))
}
5 => {
let value = (v >> 32) as i32;
Some(Expr::Literal(Literal::I64(value as i64)))
}
6 => {
let value = v >> 8;
Some(Expr::Literal(Literal::I64(value as i64)))
}
7 => {
let body = v >> 8;
let value = if body & (1 << 55) != 0 {
(body | 0xFF00_0000_0000_0000) as i64
} else {
body as i64
};
Some(Expr::Literal(Literal::I64(value)))
}
8 => {
let value = v >> 8;
Some(Expr::Literal(Literal::I64(value as i64)))
}
9 => {
let value = v >> 8;
Some(Expr::Literal(Literal::I64(value as i64)))
}
10 => {
let value = v >> 8;
Some(Expr::Literal(Literal::I64(value as i64)))
}
11 => {
let body = v >> 8;
let value = if body & (1 << 55) != 0 {
(body | 0xFF00_0000_0000_0000) as i64
} else {
body as i64
};
Some(Expr::Literal(Literal::I64(value)))
}
12 => {
let value = v >> 8;
Some(Expr::Literal(Literal::I64(value as i64)))
}
13 => {
let body = v >> 8;
let value = if body & (1 << 55) != 0 {
(body | 0xFF00_0000_0000_0000) as i64
} else {
body as i64
};
Some(Expr::Literal(Literal::I64(value)))
}
TAG_SYMBOL_SMALL_U8 => {
try_decode_symbol_small(val).map(|sym| Expr::MacroCall {
name: "symbol_short".into(),
args: vec![Expr::Literal(Literal::Str(sym))],
})
}
_ => None,
}
}
pub fn resolve_arg(
val: &StackValue,
param_names: &[String],
call_result_names: &HashMap<usize, String>,
) -> Expr {
let stripped = strip_val_boilerplate(val);
resolve_arg_inner(&stripped, param_names, call_result_names)
}
fn resolve_arg_inner(
val: &StackValue,
param_names: &[String],
call_result_names: &HashMap<usize, String>,
) -> Expr {
match val {
StackValue::Const(Value::I32(v)) => Expr::Literal(Literal::I32(*v)),
StackValue::Const(Value::I64(v)) => {
if let Some(expr) = try_decode_val(*v) {
expr
} else {
Expr::Literal(Literal::I64(*v))
}
}
StackValue::Const(Value::F32(v)) => Expr::Literal(Literal::F32(*v)),
StackValue::Const(Value::F64(v)) => Expr::Literal(Literal::F64(*v)),
StackValue::Const(Value::V128(_)) => Expr::Raw("/* v128 */".into()),
StackValue::Param(idx) => {
if let Some(name) = param_names.get(*idx) {
Expr::Var(name.clone())
} else {
Expr::Var(format!("param_{idx}"))
}
}
StackValue::Local(local_id) => {
Expr::Var(format!("local_{}", local_id.index()))
}
StackValue::CallResult(call_id) => {
if let Some(var_name) = call_result_names.get(call_id) {
Expr::Var(var_name.clone())
} else {
let from_mem = MEMORY_STRINGS.with(|ms| {
ms.borrow().get(call_id).cloned()
});
if let Some(s) = from_mem {
Expr::Literal(Literal::Str(s))
} else {
Expr::Raw("/* computed */".into())
}
}
}
StackValue::BinOp { op, left, right } => {
let l = resolve_arg_inner(left, param_names, call_result_names);
let r = resolve_arg_inner(right, param_names, call_result_names);
Expr::BinOp {
left: Box::new(l),
op: *op,
right: Box::new(r),
}
}
StackValue::UnOp { op, operand } => {
let e = resolve_arg_inner(operand, param_names, call_result_names);
Expr::UnOp {
op: *op,
operand: Box::new(e),
}
}
StackValue::Global(_) => Expr::Raw("/* global */".into()),
StackValue::Unknown => Expr::Raw("/* unknown */".into()),
}
}
pub fn as_ref(expr: Expr) -> Expr {
match &expr {
Expr::Var(name) if name == "env" => expr,
Expr::Var(name) if name.starts_with('&') => expr,
Expr::Ref(_) => expr,
_ => Expr::Ref(Box::new(expr)),
}
}