Skip to content

Commit

Permalink
feat: add support for array literals and slices (#91)
Browse files Browse the repository at this point in the history
* fix inherits

* array literals work and fixed some array stuff

* lint

* slices work

* remove unneeded func
  • Loading branch information
brockelmore authored Aug 2, 2024
1 parent 2c0a834 commit 81c290c
Show file tree
Hide file tree
Showing 24 changed files with 587 additions and 236 deletions.
1 change: 1 addition & 0 deletions crates/graph/src/nodes/context/typing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ impl ContextNode {
.underlying(analyzer)?
.inherits
.iter()
.filter_map(|i| i.as_ref())
.any(|inherited| *inherited == fn_ctrt))
} else {
Ok(false)
Expand Down
31 changes: 12 additions & 19 deletions crates/graph/src/nodes/context/var/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,18 +310,13 @@ impl ContextVarNode {
}
}

pub fn len_var_to_array(
&self,
analyzer: &impl GraphBackend,
) -> Result<Option<ContextVarNode>, GraphError> {
if let Some(arr) = analyzer.search_for_ancestor(
self.0.into(),
&Edge::Context(ContextEdge::AttrAccess("length")),
) {
Ok(Some(ContextVarNode::from(arr).latest_version(analyzer)))
} else {
Ok(None)
}
pub fn len_var_to_array(&self, analyzer: &impl GraphBackend) -> Option<ContextVarNode> {
let arr = analyzer
.graph()
.edges_directed(self.first_version(analyzer).into(), Direction::Outgoing)
.find(|edge| *edge.weight() == Edge::Context(ContextEdge::AttrAccess("length")))
.map(|edge| edge.target())?;
Some(ContextVarNode::from(arr).latest_version(analyzer))
}

pub fn index_to_array(&self, analyzer: &impl GraphBackend) -> Option<ContextVarNode> {
Expand All @@ -335,13 +330,11 @@ impl ContextVarNode {

/// Goes from an index access (i.e. `x[idx]`) to the index (i.e. `idx`)
pub fn index_access_to_index(&self, analyzer: &impl GraphBackend) -> Option<ContextVarNode> {
let index = analyzer.find_child_exclude_via(
self.first_version(analyzer).into(),
&Edge::Context(ContextEdge::Index),
&[],
&|idx, _| Some(idx),
)?;
Some(ContextVarNode::from(index))
analyzer
.graph()
.edges_directed(self.first_version(analyzer).0.into(), Direction::Incoming)
.find(|edge| matches!(*edge.weight(), Edge::Context(ContextEdge::Index)))
.map(|e| ContextVarNode::from(e.source()))
}

pub fn index_or_attr_access(&self, analyzer: &impl GraphBackend) -> Vec<Self> {
Expand Down
14 changes: 10 additions & 4 deletions crates/graph/src/nodes/context/var/ranging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ impl ContextVarNode {
Ok(())
}

#[tracing::instrument(level = "trace", skip_all)]
pub fn try_set_range_min(
&self,
analyzer: &mut impl AnalyzerBackend,
Expand All @@ -350,7 +351,7 @@ impl ContextVarNode {

new_min.arenaize(analyzer, arena)?;

if self.is_concrete(analyzer)? {
let res = if self.is_concrete(analyzer)? {
let mut new_ty = self.ty(analyzer)?.clone();
new_ty.concrete_to_builtin(analyzer)?;
self.underlying_mut(analyzer)?.ty = new_ty;
Expand All @@ -364,9 +365,12 @@ impl ContextVarNode {
Ok(self
.underlying_mut(analyzer)?
.try_set_range_min(new_min, fallback))
}
};
self.cache_range(analyzer, arena)?;
res
}

#[tracing::instrument(level = "trace", skip_all)]
pub fn try_set_range_max(
&self,
analyzer: &mut impl AnalyzerBackend,
Expand All @@ -385,7 +389,7 @@ impl ContextVarNode {

new_max.arenaize(analyzer, arena)?;

if self.is_concrete(analyzer)? {
let res = if self.is_concrete(analyzer)? {
let mut new_ty = self.ty(analyzer)?.clone();
new_ty.concrete_to_builtin(analyzer)?;
self.underlying_mut(analyzer)?.ty = new_ty;
Expand All @@ -399,7 +403,9 @@ impl ContextVarNode {
Ok(self
.underlying_mut(analyzer)?
.try_set_range_max(new_max, fallback))
}
};
self.cache_range(analyzer, arena)?;
res
}

pub fn try_set_range_exclusions(
Expand Down
36 changes: 23 additions & 13 deletions crates/graph/src/nodes/contract_ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ impl ContractNode {
self.underlying_mut(analyzer)
.unwrap()
.inherits
.push(ContractNode::from(*found));
.push(Some(ContractNode::from(*found)));
analyzer.add_edge(*found, *self, Edge::InheritedContract);
});
self.order_inherits(analyzer);
Expand All @@ -119,17 +119,25 @@ impl ContractNode {
let inherits = self.underlying(analyzer).unwrap().inherits.clone();

let mut tmp_inherits = vec![];
tmp_inherits.resize(inherits.len(), ContractNode::from(NodeIdx::from(0)));
tmp_inherits.resize(inherits.len(), None);
inherits.into_iter().for_each(|inherited| {
let i_name = inherited.name(analyzer).unwrap();
let position = raw_inherits.iter().position(|raw| &i_name == raw).unwrap();
tmp_inherits[position] = inherited;
if let Some(inherited) = inherited {
let i_name = inherited.name(analyzer).unwrap();
let position = raw_inherits.iter().position(|raw| &i_name == raw).unwrap();
tmp_inherits[position] = Some(inherited);
}
});
self.underlying_mut(analyzer).unwrap().inherits = tmp_inherits;
}

pub fn direct_inherited_contracts(&self, analyzer: &impl GraphBackend) -> Vec<ContractNode> {
self.underlying(analyzer).unwrap().inherits.clone()
self.underlying(analyzer)
.unwrap()
.inherits
.iter()
.filter_map(|i| i.as_ref())
.cloned()
.collect()
}

pub fn all_inherited_contracts(&self, analyzer: &impl GraphBackend) -> Vec<ContractNode> {
Expand Down Expand Up @@ -384,7 +392,7 @@ pub struct Contract {
/// Raw inherited strings, ordered by least base to most base
pub raw_inherits: Vec<String>,
/// A list of contracts that this contract inherits (TODO: inheritance linearization)
pub inherits: Vec<ContractNode>,
pub inherits: Vec<Option<ContractNode>>,
/// Cached linearized functions
pub cached_functions: Option<BTreeMap<String, FunctionNode>>,
}
Expand Down Expand Up @@ -416,7 +424,7 @@ impl Contract {
{
let name = ContractNode::from(contract).name(analyzer).unwrap();
if &name == inherited_name {
inherits.push(ContractNode::from(contract));
inherits.push(Some(ContractNode::from(contract)));
found = true;
break;
}
Expand All @@ -430,7 +438,7 @@ impl Contract {
{
let name = ContractNode::from(contract).name(analyzer).unwrap();
if &name == inherited_name {
inherits.push(ContractNode::from(contract));
inherits.push(Some(ContractNode::from(contract)));
found = true;
break;
}
Expand Down Expand Up @@ -462,11 +470,13 @@ impl Contract {
let inherits = self.inherits.clone();

let mut tmp_inherits = vec![];
tmp_inherits.resize(inherits.len(), ContractNode::from(NodeIdx::from(0)));
tmp_inherits.resize(raw_inherits.len(), None);
inherits.into_iter().for_each(|inherited| {
let i_name = inherited.name(analyzer).unwrap();
let position = raw_inherits.iter().position(|raw| &i_name == raw).unwrap();
tmp_inherits[position] = inherited;
if let Some(inherited) = inherited {
let i_name = inherited.name(analyzer).unwrap();
let position = raw_inherits.iter().position(|raw| &i_name == raw).unwrap();
tmp_inherits[position] = Some(inherited);
}
});
self.inherits = tmp_inherits;
}
Expand Down
21 changes: 20 additions & 1 deletion crates/graph/src/range/elem/elem_enum/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,12 @@ impl<T: Ord> Elem<T> {
Elem::Expr(expr)
}

/// Creates a new range element that is a slice of the lhs with the rhs
pub fn slice(self, other: Self) -> Self {
let expr = RangeExpr::new(self, RangeOp::Slice, other);
Elem::Expr(expr)
}

/// Gets the length of a memory object
pub fn get_length(self) -> Self {
let expr = RangeExpr::new(self, RangeOp::GetLength, Elem::Null);
Expand Down Expand Up @@ -464,7 +470,7 @@ impl Elem<Concrete> {
rhs_min: &Self,
rhs_max: &Self,
eval: bool,
analyzer: &mut impl GraphBackend,
analyzer: &impl GraphBackend,
arena: &mut RangeArena<Elem<Concrete>>,
) -> Result<Option<bool>, GraphError> {
match self {
Expand Down Expand Up @@ -512,6 +518,19 @@ impl Elem<Concrete> {
_ => Ok(Some(false)),
}
}
Self::Expr(_) => {
let min = self.minimize(analyzer, arena)?;
let max = self.maximize(analyzer, arena)?;
if let Some(true) = min.overlaps_dual(rhs_min, rhs_max, eval, analyzer, arena)? {
Ok(Some(true))
} else if let Some(true) =
max.overlaps_dual(rhs_min, rhs_max, eval, analyzer, arena)?
{
Ok(Some(true))
} else {
Ok(None)
}
}
_ => Ok(None),
}
}
Expand Down
4 changes: 4 additions & 0 deletions crates/graph/src/range/elem/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ pub enum RangeOp {
SetLength,
/// Get Length of a memory object
GetLength,
/// Slice a memory object
Slice,
}

impl TryFrom<FlatExpr> for RangeOp {
Expand Down Expand Up @@ -170,6 +172,7 @@ impl RangeOp {
SetIndices => false,
GetIndex => false,
Concat => false,
Slice => false,
}
}

Expand Down Expand Up @@ -285,6 +288,7 @@ impl fmt::Display for RangeOp {
GetIndex => write!(f, "get_index"),
GetLength => write!(f, "get_length"),
SetLength => write!(f, "set_length"),
Slice => write!(f, "slice"),
}
}
}
2 changes: 0 additions & 2 deletions crates/graph/src/range/elem/reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,6 @@ impl RangeElem<Concrete> for Reference<Concrete> {

if let Some(idx) = arena.idx(&Elem::Reference(Reference::new(self.idx))) {
if let Some(Elem::Reference(ref arenaized)) = arena.ranges.get(idx) {
tracing::trace!("reference maximize cache hit");
if let Some(MinMaxed::Maximized(cached)) = arenaized.maximized.clone() {
return Ok(*cached);
}
Expand Down Expand Up @@ -320,7 +319,6 @@ impl RangeElem<Concrete> for Reference<Concrete> {
if let Some(idx) = arena.idx(&Elem::Reference(Reference::new(self.idx))) {
if let Some(Elem::Reference(ref arenaized)) = arena.ranges.get(idx) {
if let Some(MinMaxed::Minimized(cached)) = arenaized.minimized.clone() {
tracing::trace!("reference minimize cache hit");
return Ok(*cached);
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/graph/src/range/exec/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ impl RangeCast<Concrete, RangeDyn<Concrete>> for RangeDyn<Concrete> {
Some(Elem::ConcreteDyn(self.clone()))
}
(Some(Elem::Reference(_)), None) => Some(Elem::ConcreteDyn(self.clone())),
(Some(Elem::ConcreteDyn(_)), None) => Some(Elem::ConcreteDyn(self.clone())),
(None, Some(Elem::Reference(_))) => Some(Elem::ConcreteDyn(self.clone())),
(None, None) => Some(Elem::ConcreteDyn(self.clone())),
_ => None,
Expand Down
3 changes: 3 additions & 0 deletions crates/graph/src/range/exec/exec_op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@ impl ExecOp<Concrete> for RangeExpr<Concrete> {
);

let res = match self.op {
RangeOp::Slice => Some(exec_slice(
&lhs_min, &lhs_max, &rhs_min, &rhs_max, analyzer, arena,
)),
RangeOp::GetLength => exec_get_length(&lhs_min, &lhs_max, maximize, analyzer, arena),
RangeOp::GetIndex => exec_get_index(&self.lhs, &self.rhs, maximize, analyzer, arena),
RangeOp::SetLength => exec_set_length(&lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize),
Expand Down
90 changes: 90 additions & 0 deletions crates/graph/src/range/exec/mem_ops/mem_get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use shared::RangeArena;
use ethers_core::types::U256;
use solang_parser::pt::Loc;

use std::collections::BTreeMap;

impl RangeMemLen<Concrete> for RangeDyn<Concrete> {
fn range_get_length(&self) -> Option<Elem<Concrete>> {
Some(*self.len.clone())
Expand Down Expand Up @@ -68,6 +70,94 @@ impl RangeMemGet<Concrete, Elem<Concrete>> for Elem<Concrete> {
}
}

pub fn exec_slice(
arr_min: &Elem<Concrete>,
arr_max: &Elem<Concrete>,
start: &Elem<Concrete>,
end: &Elem<Concrete>,
analyzer: &impl GraphBackend,
arena: &mut RangeArena<Elem<Concrete>>,
) -> Elem<Concrete> {
let mut kvs = Default::default();
// slices are exclusive
let excl_end = end.clone() - Elem::from(Concrete::from(U256::from(1)));
fn match_key(
arr: &Elem<Concrete>,
start_idx: &Elem<Concrete>,
excl_end: &Elem<Concrete>,
analyzer: &impl GraphBackend,
arena: &mut RangeArena<Elem<Concrete>>,
kvs: &mut BTreeMap<Elem<Concrete>, Elem<Concrete>>,
) {
match arr {
Elem::Arena(_) => {
let (d, idx) = arr.dearenaize(arena);
match_key(&d, start_idx, excl_end, analyzer, arena, kvs);
arr.rearenaize(d, idx, arena);
}
Elem::Reference(_) => {
if let Ok(min) = arr.minimize(analyzer, arena) {
match_key(&min, start_idx, excl_end, analyzer, arena, kvs);
}

if let Ok(max) = arr.maximize(analyzer, arena) {
match_key(&max, start_idx, excl_end, analyzer, arena, kvs);
}
}
Elem::ConcreteDyn(d) => {
d.val.iter().for_each(|(k, (v, _op))| {
if let Ok(Some(true)) =
k.overlaps_dual(start_idx, excl_end, true, analyzer, arena)
{
let new_k = k.clone() - start_idx.clone();
kvs.insert(new_k, v.clone());
}
});
}
Elem::Concrete(c) => {
if let Some(size) = c.val.maybe_array_size() {
let min = U256::zero();
// Iterates through concrete indices to check if RHS contains the index
let mut curr = min;
while curr < size {
let as_rc = RangeConcrete::new(Concrete::from(curr), Loc::Implicit);
let as_elem = Elem::from(as_rc.clone());
if let Ok(Some(true)) =
as_elem.overlaps_dual(start_idx, excl_end, true, analyzer, arena)
{
if let Some(val) = c.range_get_index(&as_rc) {
let new_k = Elem::from(Concrete::from(curr)) - start_idx.clone();
kvs.insert(new_k, val.clone());
}
}
curr += U256::from(1);
}
}
}
Elem::Expr(_) => {
if let Ok(min) = arr.minimize(analyzer, arena) {
match_key(&min, start_idx, excl_end, analyzer, arena, kvs);
}

if let Ok(max) = arr.maximize(analyzer, arena) {
match_key(&max, start_idx, excl_end, analyzer, arena, kvs);
}
}
_ => {}
};
}

match_key(arr_min, start, &excl_end, analyzer, arena, &mut kvs);
match_key(arr_max, start, &excl_end, analyzer, arena, &mut kvs);

let len = Elem::Expr(RangeExpr::new(
end.clone(),
RangeOp::Sub(false),
start.clone(),
));
Elem::ConcreteDyn(RangeDyn::new(len, kvs, Loc::Implicit))
}

/// Executes the `get_length` operation given the minimum and maximum of an element. It returns either the _minimum_ bound or _maximum_ bound
/// of the operation.
pub fn exec_get_length(
Expand Down
Loading

0 comments on commit 81c290c

Please sign in to comment.