use super::cfg::{ControlFlowGraph, Instr};
use super::reaching_definitions;
use crate::codegen::{Builtin, Expression};
use crate::sema::{
ast::{Diagnostic, Namespace, RetrieveType, StringLocation, Type},
eval::overflow_diagnostic,
};
use num_bigint::{BigInt, Sign};
use num_traits::{ToPrimitive, Zero};
use ripemd::Ripemd160;
use sha2::{Digest, Sha256};
use solang_parser::pt;
use solang_parser::pt::Loc;
use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Rem, Shl, Shr, Sub};
use tiny_keccak::{Hasher, Keccak};
pub fn constant_folding(cfg: &mut ControlFlowGraph, dry_run: bool, ns: &mut Namespace) {
for block_no in 0..cfg.blocks.len() {
let mut vars = cfg.blocks[block_no].defs.clone();
for instr_no in 0..cfg.blocks[block_no].instr.len() {
match &cfg.blocks[block_no].instr[instr_no] {
Instr::Set { loc, res, expr, .. } => {
let (expr, expr_constant) = expression(expr, Some(&vars), cfg, ns);
if expr_constant {
ns.var_constants.insert(*loc, expr.clone());
}
if !dry_run {
cfg.blocks[block_no].instr[instr_no] = Instr::Set {
loc: *loc,
res: *res,
expr,
};
}
}
Instr::Call {
res,
call,
args,
return_tys,
} => {
let args = args
.iter()
.map(|e| expression(e, Some(&vars), cfg, ns).0)
.collect();
if !dry_run {
cfg.blocks[block_no].instr[instr_no] = Instr::Call {
res: res.clone(),
call: call.clone(),
args,
return_tys: return_tys.clone(),
};
}
}
Instr::Return { value } => {
let value = value
.iter()
.map(|e| expression(e, Some(&vars), cfg, ns).0)
.collect();
if !dry_run {
cfg.blocks[block_no].instr[instr_no] = Instr::Return { value };
}
}
Instr::BranchCond {
cond,
true_block,
false_block,
} => {
let (cond, _) = expression(cond, Some(&vars), cfg, ns);
if !dry_run {
if let Expression::BoolLiteral { value: cond, .. } = cond {
cfg.blocks[block_no].instr[instr_no] = Instr::Branch {
block: if cond { *true_block } else { *false_block },
};
} else {
cfg.blocks[block_no].instr[instr_no] = Instr::BranchCond {
cond,
true_block: *true_block,
false_block: *false_block,
};
}
}
}
Instr::Store { dest, data } => {
let (dest, _) = expression(dest, Some(&vars), cfg, ns);
let (data, _) = expression(data, Some(&vars), cfg, ns);
if !dry_run {
cfg.blocks[block_no].instr[instr_no] = Instr::Store { dest, data };
}
}
Instr::AssertFailure {
encoded_args: Some(expr),
} => {
let (buf, _) = expression(expr, Some(&vars), cfg, ns);
if !dry_run {
cfg.blocks[block_no].instr[instr_no] = Instr::AssertFailure {
encoded_args: Some(buf),
};
}
}
Instr::Print { expr } => {
let (expr, _) = expression(expr, Some(&vars), cfg, ns);
if !dry_run {
cfg.blocks[block_no].instr[instr_no] = Instr::Print { expr };
}
}
Instr::ClearStorage { ty, storage } => {
let (storage, _) = expression(storage, Some(&vars), cfg, ns);
if !dry_run {
cfg.blocks[block_no].instr[instr_no] = Instr::ClearStorage {
ty: ty.clone(),
storage,
};
}
}
Instr::SetStorage {
ty,
storage,
value,
storage_type,
} => {
let (storage, _) = expression(storage, Some(&vars), cfg, ns);
let (value, _) = expression(value, Some(&vars), cfg, ns);
if !dry_run {
cfg.blocks[block_no].instr[instr_no] = Instr::SetStorage {
ty: ty.clone(),
storage,
value,
storage_type: storage_type.clone(),
};
}
}
Instr::LoadStorage {
ty,
storage,
res,
storage_type,
} => {
let (storage, _) = expression(storage, Some(&vars), cfg, ns);
if !dry_run {
cfg.blocks[block_no].instr[instr_no] = Instr::LoadStorage {
ty: ty.clone(),
storage,
res: *res,
storage_type: storage_type.clone(),
};
}
}
Instr::SetStorageBytes {
storage,
value,
offset,
} => {
let (storage, _) = expression(storage, Some(&vars), cfg, ns);
let (value, _) = expression(value, Some(&vars), cfg, ns);
let (offset, _) = expression(offset, Some(&vars), cfg, ns);
if !dry_run {
cfg.blocks[block_no].instr[instr_no] = Instr::SetStorageBytes {
storage,
value,
offset,
};
}
}
Instr::PushStorage {
res,
ty,
storage,
value,
} => {
let (storage, _) = expression(storage, Some(&vars), cfg, ns);
let value = value
.as_ref()
.map(|expr| expression(expr, Some(&vars), cfg, ns).0);
if !dry_run {
cfg.blocks[block_no].instr[instr_no] = Instr::PushStorage {
res: *res,
ty: ty.clone(),
storage,
value,
};
}
}
Instr::PopStorage { res, ty, storage } => {
let (storage, _) = expression(storage, Some(&vars), cfg, ns);
if !dry_run {
cfg.blocks[block_no].instr[instr_no] = Instr::PopStorage {
res: *res,
ty: ty.clone(),
storage,
};
}
}
Instr::PushMemory {
res,
ty,
array,
value,
} => {
let (value, _) = expression(value, Some(&vars), cfg, ns);
if !dry_run {
cfg.blocks[block_no].instr[instr_no] = Instr::PushMemory {
res: *res,
ty: ty.clone(),
array: *array,
value: Box::new(value),
};
}
}
Instr::Constructor {
success,
res,
contract_no,
encoded_args,
value,
gas,
salt,
address,
seeds,
loc,
accounts,
constructor_no,
} => {
let encoded_args = expression(encoded_args, Some(&vars), cfg, ns).0;
let value = value
.as_ref()
.map(|expr| expression(expr, Some(&vars), cfg, ns).0);
let gas = expression(gas, Some(&vars), cfg, ns).0;
let salt = salt
.as_ref()
.map(|expr| expression(expr, Some(&vars), cfg, ns).0);
let address = address
.as_ref()
.map(|expr| expression(expr, Some(&vars), cfg, ns).0);
let seeds = seeds
.as_ref()
.map(|expr| expression(expr, Some(&vars), cfg, ns).0);
let accounts = accounts.map(|expr| expression(expr, Some(&vars), cfg, ns).0);
if !dry_run {
cfg.blocks[block_no].instr[instr_no] = Instr::Constructor {
success: *success,
res: *res,
contract_no: *contract_no,
constructor_no: *constructor_no,
encoded_args,
value,
gas,
salt,
address,
seeds,
loc: *loc,
accounts,
};
}
}
Instr::ExternalCall {
loc,
success,
address,
payload,
value,
gas,
accounts,
callty,
seeds,
contract_function_no,
flags,
} => {
let value = expression(value, Some(&vars), cfg, ns).0;
let gas = expression(gas, Some(&vars), cfg, ns).0;
let payload = expression(payload, Some(&vars), cfg, ns).0;
let address = address
.as_ref()
.map(|expr| expression(expr, Some(&vars), cfg, ns).0);
let accounts = accounts.map(|expr| expression(expr, Some(&vars), cfg, ns).0);
let seeds = seeds
.as_ref()
.map(|expr| expression(expr, Some(&vars), cfg, ns).0);
let flags = flags
.as_ref()
.map(|expr| expression(expr, Some(&vars), cfg, ns).0);
if !dry_run {
cfg.blocks[block_no].instr[instr_no] = Instr::ExternalCall {
loc: *loc,
success: *success,
address,
accounts,
seeds,
payload,
value,
gas,
callty: callty.clone(),
contract_function_no: *contract_function_no,
flags,
};
}
}
Instr::SelfDestruct { recipient } => {
let (recipient, _) = expression(recipient, Some(&vars), cfg, ns);
if !dry_run {
cfg.blocks[block_no].instr[instr_no] = Instr::SelfDestruct { recipient };
}
}
Instr::EmitEvent {
event_no,
data,
topics,
} => {
let topics = topics
.iter()
.map(|e| expression(e, Some(&vars), cfg, ns).0)
.collect();
if !dry_run {
cfg.blocks[block_no].instr[instr_no] = Instr::EmitEvent {
event_no: *event_no,
data: expression(data, Some(&vars), cfg, ns).0,
topics,
};
}
}
Instr::MemCopy {
source,
destination,
bytes,
} => {
let bytes = expression(bytes, Some(&vars), cfg, ns);
let source = expression(source, Some(&vars), cfg, ns);
let destination = expression(destination, Some(&vars), cfg, ns);
if !dry_run {
cfg.blocks[block_no].instr[instr_no] = Instr::MemCopy {
source: source.0,
destination: destination.0,
bytes: bytes.0,
};
}
}
Instr::Switch {
cond,
cases,
default,
} => {
let cond = expression(cond, Some(&vars), cfg, ns);
let cases = cases
.iter()
.map(|(exp, goto)| (expression(exp, Some(&vars), cfg, ns).0, *goto))
.collect::<Vec<(Expression, usize)>>();
if !dry_run {
if let Expression::NumberLiteral { value: num, .. } = &cond.0 {
let mut simplified_branch = None;
for (match_item, block) in &cases {
if let Expression::NumberLiteral {
value: match_num, ..
} = match_item
{
if match_num == num {
simplified_branch = Some(*block);
}
}
}
cfg.blocks[block_no].instr[instr_no] = Instr::Branch {
block: simplified_branch.unwrap_or(*default),
};
break;
}
cfg.blocks[block_no].instr[instr_no] = Instr::Switch {
cond: cond.0,
cases,
default: *default,
};
}
}
Instr::ReturnData { data, data_len } => {
let data = expression(data, Some(&vars), cfg, ns);
let data_len = expression(data_len, Some(&vars), cfg, ns);
if !dry_run {
cfg.blocks[block_no].instr[instr_no] = Instr::ReturnData {
data: data.0,
data_len: data_len.0,
};
}
}
Instr::WriteBuffer { buf, offset, value } => {
let offset = expression(offset, Some(&vars), cfg, ns).0;
let value = expression(value, Some(&vars), cfg, ns).0;
if !dry_run {
cfg.blocks[block_no].instr[instr_no] = Instr::WriteBuffer {
buf: buf.clone(),
offset,
value,
};
}
}
_ => (),
}
reaching_definitions::apply_transfers(
&cfg.blocks[block_no].transfers[instr_no],
&mut vars,
);
}
}
}
fn expression(
expr: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
match expr {
Expression::Add {
loc,
ty,
overflowing,
left,
right,
} => add(loc, ty, *overflowing, left, right, vars, cfg, ns),
Expression::Subtract {
loc,
ty,
overflowing,
left,
right,
} => subtract(loc, ty, *overflowing, left, right, vars, cfg, ns),
Expression::AdvancePointer {
pointer,
bytes_offset: offset,
} => advance_pointer(pointer, offset, vars, cfg, ns),
Expression::Multiply {
loc,
ty,
overflowing,
left,
right,
} => multiply(loc, ty, *overflowing, left, right, vars, cfg, ns),
Expression::BitwiseAnd {
loc,
ty,
left,
right,
} => bitwise_and(loc, ty, left, right, vars, cfg, ns),
Expression::BitwiseOr {
loc,
ty,
left,
right,
} => bitwise_or(loc, ty, left, right, vars, cfg, ns),
Expression::BitwiseXor {
loc,
ty,
left,
right,
} => bitwise_xor(loc, ty, left, right, vars, cfg, ns),
Expression::ShiftLeft {
loc,
ty,
left: left_expr,
right: right_expr,
} => shift_left(loc, ty, left_expr, right_expr, vars, cfg, ns),
Expression::ShiftRight {
loc,
ty,
left: left_expr,
right: right_expr,
signed,
} => shift_right(loc, ty, left_expr, right_expr, *signed, vars, cfg, ns),
Expression::Power {
loc,
ty,
overflowing,
base,
exp,
} => power(loc, ty, *overflowing, base, exp, vars, cfg, ns),
Expression::UnsignedDivide {
loc,
ty,
left,
right,
} => divide(loc, ty, left, right, vars, cfg, ns, false),
Expression::SignedDivide {
loc,
ty,
left,
right,
} => divide(loc, ty, left, right, vars, cfg, ns, true),
Expression::SignedModulo {
loc,
ty,
left,
right,
} => modulo(loc, ty, left, right, vars, cfg, ns, true),
Expression::UnsignedModulo {
loc,
ty,
left,
right,
} => modulo(loc, ty, left, right, vars, cfg, ns, false),
Expression::ZeroExt { loc, ty, expr } => zero_ext(loc, ty, expr, vars, cfg, ns),
Expression::SignExt { loc, ty, expr } => sign_ext(loc, ty, expr, vars, cfg, ns),
Expression::Trunc { loc, ty, expr } => trunc(loc, ty, expr, vars, cfg, ns),
Expression::BitwiseNot { loc, ty, expr } => bitwise_not(loc, ty, expr, vars, cfg, ns),
Expression::Negate {
loc,
ty,
overflowing,
expr,
} => negate(loc, ty, expr, *overflowing, vars, cfg, ns),
Expression::Variable { loc, ty, var_no } => {
reference_variable(loc, *var_no, ty, vars, cfg, ns, expr)
}
Expression::Builtin {
loc,
tys,
kind:
kind @ Builtin::Keccak256
| kind @ Builtin::Ripemd160
| kind @ Builtin::Blake2_256
| kind @ Builtin::Blake2_128
| kind @ Builtin::Sha256,
args,
} => builtin_hash(loc, tys, *kind, args, vars, cfg, ns),
Expression::Keccak256 { loc, ty, exprs } => keccak_256(loc, ty, exprs, vars, cfg, ns),
Expression::StructLiteral { loc, ty, values } => {
struct_literal(loc, ty, values, vars, cfg, ns)
}
Expression::ArrayLiteral {
loc,
ty,
dimensions,
values,
} => array_literal(loc, ty, values, dimensions, vars, cfg, ns),
Expression::ConstArrayLiteral {
loc,
ty,
dimensions,
values,
} => const_array_literal(loc, ty, values, dimensions, vars, cfg, ns),
Expression::Load { loc, ty, expr } => load(loc, ty, expr, vars, cfg, ns),
Expression::Cast { loc, ty, expr } => cast(loc, ty, expr, vars, cfg, ns),
Expression::BytesCast {
loc,
ty,
from,
expr,
} => bytes_cast(loc, ty, from, expr, vars, cfg, ns),
Expression::More {
loc,
signed,
left,
right,
} => more(loc, left, right, vars, cfg, ns, *signed),
Expression::Less {
loc,
signed,
left,
right,
} => less(loc, left, right, vars, cfg, ns, *signed),
Expression::MoreEqual {
loc,
signed,
left,
right,
} => more_equal(loc, left, right, vars, cfg, ns, *signed),
Expression::LessEqual {
loc,
signed,
left,
right,
} => less_equal(loc, left, right, vars, cfg, ns, *signed),
Expression::Equal { loc, left, right } => equal(loc, left, right, vars, cfg, ns),
Expression::NotEqual { loc, left, right } => not_equal(loc, left, right, vars, cfg, ns),
Expression::Not { loc, expr } => not(loc, expr, vars, cfg, ns),
Expression::Subscript {
loc,
ty,
array_ty,
expr,
index,
} => subscript(loc, ty, array_ty, expr, index, vars, cfg, ns),
Expression::StructMember {
loc,
ty,
expr,
member,
} => struct_member(loc, ty, expr, *member, vars, cfg, ns),
Expression::StorageArrayLength {
loc,
ty,
array,
elem_ty,
} => storage_array_length(loc, ty, array, elem_ty, vars, cfg, ns),
Expression::StringCompare { loc, left, right } => {
string_compare(loc, left, right, vars, cfg, ns)
}
Expression::Builtin {
loc,
kind: Builtin::Concat,
args,
..
} => bytes_concat(loc, args, vars, cfg, ns),
Expression::Builtin {
loc,
tys,
kind,
args,
} => builtin(loc, tys, *kind, args, vars, cfg, ns),
Expression::AllocDynamicBytes {
loc,
ty,
size,
initializer,
} => (
Expression::AllocDynamicBytes {
loc: *loc,
ty: ty.clone(),
size: Box::new(expression(size, vars, cfg, ns).0),
initializer: initializer.clone(),
},
false,
),
Expression::NumberLiteral { .. }
| Expression::RationalNumberLiteral { .. }
| Expression::BoolLiteral { .. }
| Expression::BytesLiteral { .. }
| Expression::FunctionArg { .. } => (expr.clone(), true),
Expression::ReturnData { .. }
| Expression::Undefined { .. }
| Expression::FormatString { .. }
| Expression::GetRef { .. }
| Expression::InternalFunctionCfg { .. }
| Expression::VectorData { .. } => (expr.clone(), false),
_ => panic!("expr should not be in cfg: {expr:?}"),
}
}
fn bigint_to_expression(
loc: &Loc,
ty: &Type,
value: BigInt,
overflowing: bool,
ns: &mut Namespace,
) -> (Expression, bool) {
if !overflowing {
if let Some(diagnostic) = overflow_diagnostic(&value, ty, loc) {
ns.diagnostics.push(diagnostic);
}
}
let value = match ty {
Type::Uint(bits) => {
if value.sign() == Sign::Minus {
let mut bs = value.to_signed_bytes_le();
bs.resize(*bits as usize / 8, 0xff);
BigInt::from_bytes_le(Sign::Plus, &bs)
} else if value.bits() > *bits as u64 {
let (_, mut bs) = value.to_bytes_le();
bs.truncate(*bits as usize / 8);
BigInt::from_bytes_le(Sign::Plus, &bs)
} else {
value
}
}
Type::Int(bits) => {
let mut bs = value.to_signed_bytes_le();
if bs.len() * 8 > *bits as usize {
bs.truncate(*bits as usize / 8);
BigInt::from_signed_bytes_le(&bs)
} else {
value
}
}
Type::StorageRef(..) => value,
_ => unreachable!(),
};
(
Expression::NumberLiteral {
loc: *loc,
ty: ty.clone(),
value,
},
true,
)
}
fn get_definition<'a>(
def: &reaching_definitions::Def,
cfg: &'a ControlFlowGraph,
) -> Option<&'a Expression> {
if let Instr::Set { expr, .. } = &cfg.blocks[def.block_no].instr[def.instr_no] {
Some(expr)
} else {
None
}
}
fn constants_equal(left: &Expression, right: &Expression) -> bool {
match left {
Expression::NumberLiteral { value: left, .. } => match right {
Expression::NumberLiteral { value: right, .. } => left == right,
_ => false,
},
Expression::BytesLiteral { value: left, .. }
| Expression::AllocDynamicBytes {
initializer: Some(left),
..
} => match right {
Expression::BytesLiteral { value: right, .. }
| Expression::AllocDynamicBytes {
initializer: Some(right),
..
} => left == right,
_ => false,
},
_ => false,
}
}
fn add(
loc: &pt::Loc,
ty: &Type,
overflowing: bool,
left: &Expression,
right: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let left = expression(left, vars, cfg, ns);
let right = expression(right, vars, cfg, ns);
if let (
Expression::NumberLiteral { value: left, .. },
Expression::NumberLiteral { value: right, .. },
) = (&left.0, &right.0)
{
bigint_to_expression(loc, ty, left.add(right), overflowing, ns)
} else {
(
Expression::Add {
loc: *loc,
ty: ty.clone(),
overflowing,
left: Box::new(left.0),
right: Box::new(right.0),
},
left.1 && right.1,
)
}
}
fn subtract(
loc: &pt::Loc,
ty: &Type,
overflowing: bool,
left: &Expression,
right: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let left = expression(left, vars, cfg, ns);
let right = expression(right, vars, cfg, ns);
if let (
Expression::NumberLiteral { value: left, .. },
Expression::NumberLiteral { value: right, .. },
) = (&left.0, &right.0)
{
bigint_to_expression(loc, ty, left.sub(right), overflowing, ns)
} else {
(
Expression::Subtract {
loc: *loc,
ty: ty.clone(),
overflowing,
left: Box::new(left.0),
right: Box::new(right.0),
},
left.1 && right.1,
)
}
}
fn advance_pointer(
pointer: &Expression,
offset: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let offset = expression(offset, vars, cfg, ns);
match &offset.0 {
Expression::NumberLiteral { value: num, .. } if num.is_zero() => (pointer.clone(), false),
_ => (
Expression::AdvancePointer {
pointer: Box::new(pointer.clone()),
bytes_offset: Box::new(offset.0),
},
offset.1,
),
}
}
fn multiply(
loc: &pt::Loc,
ty: &Type,
overflowing: bool,
left: &Expression,
right: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let left = expression(left, vars, cfg, ns);
let right = expression(right, vars, cfg, ns);
if let (
Expression::NumberLiteral { value: left, .. },
Expression::NumberLiteral { value: right, .. },
) = (&left.0, &right.0)
{
bigint_to_expression(loc, ty, left.mul(right), overflowing, ns)
} else {
(
Expression::Multiply {
loc: *loc,
ty: ty.clone(),
overflowing,
left: Box::new(left.0),
right: Box::new(right.0),
},
left.1 && right.1,
)
}
}
fn bitwise_and(
loc: &pt::Loc,
ty: &Type,
left: &Expression,
right: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let left = expression(left, vars, cfg, ns);
let right = expression(right, vars, cfg, ns);
if let (
Expression::NumberLiteral { value: left, .. },
Expression::NumberLiteral { value: right, .. },
) = (&left.0, &right.0)
{
bigint_to_expression(loc, ty, left.bitand(right), true, ns)
} else {
(
Expression::BitwiseAnd {
loc: *loc,
ty: ty.clone(),
left: Box::new(left.0),
right: Box::new(right.0),
},
left.1 && right.1,
)
}
}
fn bitwise_or(
loc: &pt::Loc,
ty: &Type,
left: &Expression,
right: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let left = expression(left, vars, cfg, ns);
let right = expression(right, vars, cfg, ns);
if let (
Expression::NumberLiteral { value: left, .. },
Expression::NumberLiteral { value: right, .. },
) = (&left.0, &right.0)
{
bigint_to_expression(loc, ty, left.bitor(right), true, ns)
} else {
(
Expression::BitwiseOr {
loc: *loc,
ty: ty.clone(),
left: Box::new(left.0),
right: Box::new(right.0),
},
left.1 && right.1,
)
}
}
fn bitwise_xor(
loc: &pt::Loc,
ty: &Type,
left: &Expression,
right: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let left = expression(left, vars, cfg, ns);
let right = expression(right, vars, cfg, ns);
if let (
Expression::NumberLiteral { value: left, .. },
Expression::NumberLiteral { value: right, .. },
) = (&left.0, &right.0)
{
bigint_to_expression(loc, ty, left.bitxor(right), true, ns)
} else {
(
Expression::BitwiseXor {
loc: *loc,
ty: ty.clone(),
left: Box::new(left.0),
right: Box::new(right.0),
},
left.1 && right.1,
)
}
}
fn shift_left(
loc: &pt::Loc,
ty: &Type,
left_expr: &Expression,
right_expr: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let left = expression(left_expr, vars, cfg, ns);
let right = expression(right_expr, vars, cfg, ns);
if let (
Expression::NumberLiteral { value: left, .. },
Expression::NumberLiteral { value: right, .. },
) = (&left.0, &right.0)
{
if right.sign() == Sign::Minus || right >= &BigInt::from(left_expr.ty().bits(ns)) {
ns.diagnostics.push(Diagnostic::error(
*loc,
format!("left shift by {right} is not possible"),
));
} else {
let right: u64 = right.to_u64().unwrap();
return bigint_to_expression(loc, ty, left.shl(&right), true, ns);
}
}
(
Expression::ShiftLeft {
loc: *loc,
ty: ty.clone(),
left: Box::new(left.0),
right: Box::new(right.0),
},
left.1 && right.1,
)
}
fn shift_right(
loc: &pt::Loc,
ty: &Type,
left_expr: &Expression,
right_expr: &Expression,
signed: bool,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let left = expression(left_expr, vars, cfg, ns);
let right = expression(right_expr, vars, cfg, ns);
if let (
Expression::NumberLiteral { value: left, .. },
Expression::NumberLiteral { value: right, .. },
) = (&left.0, &right.0)
{
if right.sign() == Sign::Minus || right >= &BigInt::from(left_expr.ty().bits(ns)) {
ns.diagnostics.push(Diagnostic::error(
*loc,
format!("right shift by {right} is not possible"),
));
} else {
let right: u64 = right.to_u64().unwrap();
return bigint_to_expression(loc, ty, left.shr(&right), true, ns);
}
}
(
Expression::ShiftRight {
loc: *loc,
ty: ty.clone(),
left: Box::new(left.0),
right: Box::new(right.0),
signed,
},
left.1 && right.1,
)
}
fn power(
loc: &pt::Loc,
ty: &Type,
overflowing: bool,
base: &Expression,
exp: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let base = expression(base, vars, cfg, ns);
let exp = expression(exp, vars, cfg, ns);
if let (
Expression::NumberLiteral { value: left, .. },
Expression::NumberLiteral { value: right, .. },
) = (&base.0, &exp.0)
{
if right.sign() == Sign::Minus || right >= &BigInt::from(u16::MAX) {
ns.diagnostics.push(Diagnostic::error(
*loc,
format!("power {right} not possible"),
));
} else {
let right: u32 = right.to_u32().unwrap();
return bigint_to_expression(loc, ty, left.pow(right), overflowing, ns);
}
}
(
Expression::Power {
loc: *loc,
ty: ty.clone(),
overflowing,
base: Box::new(base.0),
exp: Box::new(exp.0),
},
base.1 && exp.1,
)
}
fn divide(
loc: &pt::Loc,
ty: &Type,
left: &Expression,
right: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
signed: bool,
) -> (Expression, bool) {
let left = expression(left, vars, cfg, ns);
let right = expression(right, vars, cfg, ns);
if let Expression::NumberLiteral { value: right, .. } = &right.0 {
if right.is_zero() {
ns.diagnostics
.push(Diagnostic::error(*loc, String::from("divide by zero")));
} else if let Expression::NumberLiteral { value: left, .. } = &left.0 {
return bigint_to_expression(loc, ty, left.div(right), false, ns);
}
}
(
if signed {
Expression::SignedDivide {
loc: *loc,
ty: ty.clone(),
left: Box::new(left.0),
right: Box::new(right.0),
}
} else {
Expression::UnsignedDivide {
loc: *loc,
ty: ty.clone(),
left: Box::new(left.0),
right: Box::new(right.0),
}
},
left.1 && right.1,
)
}
fn modulo(
loc: &pt::Loc,
ty: &Type,
left: &Expression,
right: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
signed: bool,
) -> (Expression, bool) {
let left = expression(left, vars, cfg, ns);
let right = expression(right, vars, cfg, ns);
if let Expression::NumberLiteral { value: right, .. } = &right.0 {
if right.is_zero() {
ns.diagnostics
.push(Diagnostic::error(*loc, String::from("divide by zero")));
} else if let Expression::NumberLiteral { value: left, .. } = &left.0 {
return bigint_to_expression(loc, ty, left.rem(right), false, ns);
}
}
(
if signed {
Expression::SignedModulo {
loc: *loc,
ty: ty.clone(),
left: Box::new(left.0),
right: Box::new(right.0),
}
} else {
Expression::UnsignedModulo {
loc: *loc,
ty: ty.clone(),
left: Box::new(left.0),
right: Box::new(right.0),
}
},
left.1 && right.1,
)
}
fn zero_ext(
loc: &pt::Loc,
ty: &Type,
expr: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let expr = expression(expr, vars, cfg, ns);
if let Expression::NumberLiteral { value, .. } = expr.0 {
(
Expression::NumberLiteral {
loc: *loc,
ty: ty.clone(),
value,
},
true,
)
} else {
(
Expression::ZeroExt {
loc: *loc,
ty: ty.clone(),
expr: Box::new(expr.0),
},
expr.1,
)
}
}
fn sign_ext(
loc: &pt::Loc,
ty: &Type,
expr: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let expr = expression(expr, vars, cfg, ns);
if let Expression::NumberLiteral { value, .. } = expr.0 {
(
Expression::NumberLiteral {
loc: *loc,
ty: ty.clone(),
value,
},
true,
)
} else {
(
Expression::SignExt {
loc: *loc,
ty: ty.clone(),
expr: Box::new(expr.0),
},
expr.1,
)
}
}
fn trunc(
loc: &pt::Loc,
ty: &Type,
expr: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let expr = expression(expr, vars, cfg, ns);
if let Expression::NumberLiteral { value, .. } = expr.0 {
bigint_to_expression(loc, ty, value, true, ns)
} else {
(
Expression::Trunc {
loc: *loc,
ty: ty.clone(),
expr: Box::new(expr.0),
},
expr.1,
)
}
}
fn bitwise_not(
loc: &pt::Loc,
ty: &Type,
expr: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let expr = expression(expr, vars, cfg, ns);
if let Expression::NumberLiteral { value, .. } = expr.0 {
bigint_to_expression(loc, ty, !value, true, ns)
} else {
(
Expression::BitwiseNot {
loc: *loc,
ty: ty.clone(),
expr: Box::new(expr.0),
},
expr.1,
)
}
}
fn negate(
loc: &pt::Loc,
ty: &Type,
expr: &Expression,
overflowing: bool,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let expr = expression(expr, vars, cfg, ns);
if let Expression::NumberLiteral { value, .. } = expr.0 {
bigint_to_expression(loc, ty, -value, overflowing, ns)
} else {
(
Expression::Negate {
loc: *loc,
ty: ty.clone(),
overflowing,
expr: Box::new(expr.0),
},
expr.1,
)
}
}
fn reference_variable(
loc: &pt::Loc,
var: usize,
ty: &Type,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
expr: &Expression,
) -> (Expression, bool) {
if !matches!(ty, Type::Ref(_) | Type::StorageRef(..)) {
if let Some(vars) = vars {
if let Some(defs) = vars.get(&var) {
let mut v = None;
for def in defs.keys() {
if let Some(expr) = get_definition(def, cfg) {
let expr = expression(expr, None, cfg, ns);
if expr.1 {
if let Some(last) = &v {
if !constants_equal(last, &expr.0) {
v = None;
break;
}
}
v = Some(expr.0);
} else {
v = None;
break;
}
} else {
v = None;
break;
}
}
if let Some(expr) = v {
if *loc != Loc::Codegen {
ns.var_constants.insert(*loc, expr.clone());
}
return (expr, true);
}
}
}
}
(expr.clone(), false)
}
fn builtin_hash(
loc: &pt::Loc,
tys: &[Type],
kind: Builtin,
args: &[Expression],
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let arg = expression(&args[0], vars, cfg, ns);
if let Expression::AllocDynamicBytes {
initializer: Some(bs),
..
} = arg.0
{
let hash: Vec<u8> = match kind {
Builtin::Keccak256 => {
let mut hasher = Keccak::v256();
hasher.update(&bs);
let mut hash = [0u8; 32];
hasher.finalize(&mut hash);
hash.to_vec()
}
Builtin::Ripemd160 => {
let mut hasher = Ripemd160::new();
hasher.update(&bs);
let result = hasher.finalize();
result[..].to_vec()
}
Builtin::Blake2_256 => {
let hash = blake2_rfc::blake2b::blake2b(32, &[], &bs);
hash.as_bytes().to_vec()
}
Builtin::Blake2_128 => {
let hash = blake2_rfc::blake2b::blake2b(16, &[], &bs);
hash.as_bytes().to_vec()
}
Builtin::Sha256 => {
let mut hasher = Sha256::new();
hasher.update(&bs);
let result = hasher.finalize();
result[..].to_vec()
}
_ => unreachable!("not a hash"),
};
(
Expression::BytesLiteral {
loc: *loc,
ty: tys[0].clone(),
value: hash,
},
true,
)
} else {
(
Expression::Builtin {
loc: *loc,
tys: tys.to_owned(),
kind,
args: vec![arg.0],
},
false,
)
}
}
fn keccak_256(
loc: &pt::Loc,
ty: &Type,
args: &[Expression],
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let mut all_constant = true;
let mut hasher = Keccak::v256();
let args = args
.iter()
.map(|expr| {
let (expr, _) = expression(expr, vars, cfg, ns);
if all_constant {
match &expr {
Expression::AllocDynamicBytes {
initializer: Some(value),
..
}
| Expression::BytesLiteral { value, .. } => {
hasher.update(value);
}
Expression::NumberLiteral { ty, value, .. } => {
let (sign, mut bs) = value.to_bytes_le();
match ty {
Type::Enum(_) => bs.resize(1, 0),
Type::Uint(bits) => bs.resize(*bits as usize / 8, 0),
Type::Int(bits) => {
let v = if sign == Sign::Minus { 0xffu8 } else { 0 };
bs.resize(*bits as usize / 8, v);
}
Type::Bytes(n) => {
while (*n as usize) < bs.len() {
bs.insert(0, 0);
}
}
Type::Address(_) => {
bs.resize(ns.address_length, 0);
}
_ => unreachable!(),
}
hasher.update(&bs);
}
_ => {
all_constant = false;
}
}
}
expr
})
.collect();
if all_constant {
let mut hash = [0u8; 32];
hasher.finalize(&mut hash);
let mut hash = hash.to_vec();
hash.reverse();
(
Expression::BytesLiteral {
loc: *loc,
ty: ty.clone(),
value: hash,
},
true,
)
} else {
(
Expression::Keccak256 {
loc: *loc,
ty: ty.clone(),
exprs: args,
},
false,
)
}
}
fn struct_literal(
loc: &pt::Loc,
ty: &Type,
args: &[Expression],
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let args = args
.iter()
.map(|expr| expression(expr, vars, cfg, ns).0)
.collect();
(
Expression::StructLiteral {
loc: *loc,
ty: ty.clone(),
values: args,
},
false,
)
}
fn array_literal(
loc: &pt::Loc,
ty: &Type,
args: &[Expression],
dimensions: &[u32],
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let args = args
.iter()
.map(|expr| expression(expr, vars, cfg, ns).0)
.collect();
(
Expression::ArrayLiteral {
loc: *loc,
ty: ty.clone(),
dimensions: dimensions.to_owned(),
values: args,
},
false,
)
}
fn const_array_literal(
loc: &pt::Loc,
ty: &Type,
args: &[Expression],
dimensions: &[u32],
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let args = args
.iter()
.map(|expr| expression(expr, vars, cfg, ns).0)
.collect();
(
Expression::ConstArrayLiteral {
loc: *loc,
ty: ty.clone(),
dimensions: dimensions.to_owned(),
values: args,
},
false,
)
}
fn load(
loc: &pt::Loc,
ty: &Type,
expr: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let (expr, _) = expression(expr, vars, cfg, ns);
(
Expression::Load {
loc: *loc,
ty: ty.clone(),
expr: Box::new(expr),
},
false,
)
}
fn cast(
loc: &pt::Loc,
ty: &Type,
expr: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let (expr, _) = expression(expr, vars, cfg, ns);
(
Expression::Cast {
loc: *loc,
ty: ty.clone(),
expr: Box::new(expr),
},
false,
)
}
fn bytes_cast(
loc: &pt::Loc,
to: &Type,
from: &Type,
expr: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let (expr, _) = expression(expr, vars, cfg, ns);
if let Expression::NumberLiteral {
loc,
ty: Type::Bytes(len),
value,
} = expr
{
let (_, mut bs) = value.to_bytes_be();
while bs.len() < len as usize {
bs.insert(0, 0);
}
(
Expression::AllocDynamicBytes {
loc,
ty: Type::DynamicBytes,
size: Expression::NumberLiteral {
loc,
ty: Type::Uint(32),
value: len.into(),
}
.into(),
initializer: Some(bs),
},
false,
)
} else {
(
Expression::BytesCast {
loc: *loc,
ty: to.clone(),
from: from.clone(),
expr: Box::new(expr),
},
false,
)
}
}
fn more(
loc: &pt::Loc,
left: &Expression,
right: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
signed: bool,
) -> (Expression, bool) {
let left = expression(left, vars, cfg, ns);
let right = expression(right, vars, cfg, ns);
(
Expression::More {
loc: *loc,
signed,
left: Box::new(left.0),
right: Box::new(right.0),
},
false,
)
}
fn less(
loc: &pt::Loc,
left: &Expression,
right: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
signed: bool,
) -> (Expression, bool) {
let left = expression(left, vars, cfg, ns);
let right = expression(right, vars, cfg, ns);
(
Expression::Less {
loc: *loc,
signed,
left: Box::new(left.0),
right: Box::new(right.0),
},
false,
)
}
fn more_equal(
loc: &pt::Loc,
left: &Expression,
right: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
signed: bool,
) -> (Expression, bool) {
let left = expression(left, vars, cfg, ns);
let right = expression(right, vars, cfg, ns);
(
Expression::MoreEqual {
loc: *loc,
signed,
left: Box::new(left.0),
right: Box::new(right.0),
},
false,
)
}
fn less_equal(
loc: &pt::Loc,
left: &Expression,
right: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
signed: bool,
) -> (Expression, bool) {
let left = expression(left, vars, cfg, ns);
let right = expression(right, vars, cfg, ns);
(
Expression::LessEqual {
loc: *loc,
signed,
left: Box::new(left.0),
right: Box::new(right.0),
},
false,
)
}
fn equal(
loc: &pt::Loc,
left: &Expression,
right: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let left = expression(left, vars, cfg, ns);
let right = expression(right, vars, cfg, ns);
if let (Expression::BytesLiteral { value: l, .. }, Expression::BytesLiteral { value: r, .. }) =
(&left.0, &right.0)
{
(
Expression::BoolLiteral {
loc: *loc,
value: l == r,
},
true,
)
} else {
(
Expression::Equal {
loc: *loc,
left: Box::new(left.0),
right: Box::new(right.0),
},
false,
)
}
}
fn not_equal(
loc: &pt::Loc,
left: &Expression,
right: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let left = expression(left, vars, cfg, ns);
let right = expression(right, vars, cfg, ns);
if let (Expression::BytesLiteral { value: l, .. }, Expression::BytesLiteral { value: r, .. }) =
(&left.0, &right.0)
{
(
Expression::BoolLiteral {
loc: *loc,
value: l != r,
},
true,
)
} else {
(
Expression::NotEqual {
loc: *loc,
left: Box::new(left.0),
right: Box::new(right.0),
},
false,
)
}
}
fn not(
loc: &pt::Loc,
expr: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let expr = expression(expr, vars, cfg, ns);
(
Expression::Not {
loc: *loc,
expr: Box::new(expr.0),
},
expr.1,
)
}
fn subscript(
loc: &pt::Loc,
ty: &Type,
array_ty: &Type,
array: &Expression,
index: &Expression,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let array = expression(array, vars, cfg, ns);
let index = expression(index, vars, cfg, ns);
(
Expression::Subscript {
loc: *loc,
ty: ty.clone(),
array_ty: array_ty.clone(),
expr: Box::new(array.0),
index: Box::new(index.0),
},
false,
)
}
fn struct_member(
loc: &pt::Loc,
ty: &Type,
expr: &Expression,
member: usize,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let strct = expression(expr, vars, cfg, ns);
(
Expression::StructMember {
loc: *loc,
ty: ty.clone(),
expr: Box::new(strct.0),
member,
},
false,
)
}
fn storage_array_length(
loc: &pt::Loc,
ty: &Type,
array: &Expression,
elem_ty: &Type,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let array = expression(array, vars, cfg, ns);
(
Expression::StorageArrayLength {
loc: *loc,
ty: ty.clone(),
array: Box::new(array.0),
elem_ty: elem_ty.clone(),
},
false,
)
}
fn string_compare(
loc: &pt::Loc,
left: &StringLocation<Expression>,
right: &StringLocation<Expression>,
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
if let (StringLocation::CompileTime(left), StringLocation::CompileTime(right)) = (left, right) {
(
Expression::BoolLiteral {
loc: *loc,
value: left == right,
},
true,
)
} else {
let left = if let StringLocation::RunTime(left) = left {
StringLocation::RunTime(Box::new(expression(left, vars, cfg, ns).0))
} else {
left.clone()
};
let right = if let StringLocation::RunTime(right) = right {
StringLocation::RunTime(Box::new(expression(right, vars, cfg, ns).0))
} else {
right.clone()
};
(
Expression::StringCompare {
loc: *loc,
left,
right,
},
false,
)
}
}
fn bytes_concat(
loc: &pt::Loc,
args: &[Expression],
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let mut last = None;
let mut res = Vec::new();
for arg in args {
let expr = expression(arg, vars, cfg, ns).0;
if let Expression::AllocDynamicBytes {
initializer: Some(bs),
..
} = &expr
{
if bs.is_empty() {
continue;
}
if let Some(Expression::AllocDynamicBytes {
size,
initializer: Some(init),
..
}) = &mut last
{
let Expression::NumberLiteral { value, .. } = size.as_mut() else {
unreachable!();
};
*value += bs.len();
init.extend_from_slice(bs);
} else {
last = Some(expr);
}
} else {
if let Some(expr) = last {
res.push(expr);
last = None;
}
res.push(expr);
}
}
if res.is_empty() {
if let Some(expr) = last {
(expr, false)
} else {
(
Expression::AllocDynamicBytes {
loc: *loc,
ty: Type::DynamicBytes,
size: Expression::NumberLiteral {
loc: *loc,
ty: Type::Uint(32),
value: 0.into(),
}
.into(),
initializer: None,
},
false,
)
}
} else {
if let Some(expr) = last {
res.push(expr);
}
if res.len() == 1 {
(res[0].clone(), false)
} else {
(
Expression::Builtin {
loc: *loc,
tys: vec![Type::DynamicBytes],
kind: Builtin::Concat,
args: res,
},
false,
)
}
}
}
fn builtin(
loc: &pt::Loc,
tys: &[Type],
kind: Builtin,
args: &[Expression],
vars: Option<&reaching_definitions::VarDefs>,
cfg: &ControlFlowGraph,
ns: &mut Namespace,
) -> (Expression, bool) {
let args = args
.iter()
.map(|expr| expression(expr, vars, cfg, ns).0)
.collect();
(
Expression::Builtin {
loc: *loc,
tys: tys.to_owned(),
kind,
args,
},
false,
)
}