From a329c79441f1fd4a25d7548b1faac57d0a8f6efa Mon Sep 17 00:00:00 2001 From: brockelmore <31553173+brockelmore@users.noreply.github.com> Date: Fri, 21 Jun 2024 09:23:21 -0700 Subject: [PATCH] fix(ranges): Clean up range executions (#76) * idk * lint * begin range refactor * lint * add range exec tests * lint * more refactor * lint * refactor range execution * lint * further refactor of range exec * more range work * lint * more tests * lint * remove commented code * MOAR: mother of all refactors * lint * more improvements * lint --- Cargo.lock | 11 + Cargo.toml | 32 +- crates/analyzers/src/bounds.rs | 50 +- crates/analyzers/src/func_analyzer/mod.rs | 25 +- .../src/func_analyzer/report_display.rs | 49 +- crates/analyzers/src/lib.rs | 32 +- crates/analyzers/src/var_analyzer/mod.rs | 38 +- .../src/var_analyzer/report_display.rs | 46 +- crates/cli/src/main.rs | 58 +- crates/graph/Cargo.toml | 5 +- crates/graph/src/graph_elements.rs | 73 +- crates/graph/src/nodes/block.rs | 10 +- crates/graph/src/nodes/builtin.rs | 11 +- crates/graph/src/nodes/concrete.rs | 119 +- crates/graph/src/nodes/context/expr_ret.rs | 33 +- crates/graph/src/nodes/context/node.rs | 11 +- crates/graph/src/nodes/context/querying.rs | 14 +- crates/graph/src/nodes/context/solving.rs | 192 +- crates/graph/src/nodes/context/var/node.rs | 43 +- crates/graph/src/nodes/context/var/ranging.rs | 126 +- crates/graph/src/nodes/context/var/typing.rs | 110 +- .../graph/src/nodes/context/var/underlying.rs | 2 +- crates/graph/src/nodes/context/versioning.rs | 63 +- crates/graph/src/nodes/contract_ty.rs | 13 +- crates/graph/src/nodes/enum_ty.rs | 12 +- crates/graph/src/nodes/err_ty.rs | 15 +- crates/graph/src/nodes/func_ty.rs | 81 +- crates/graph/src/nodes/msg.rs | 9 +- crates/graph/src/nodes/source_unit.rs | 11 +- crates/graph/src/nodes/source_unit_part.rs | 11 +- crates/graph/src/nodes/struct_ty.rs | 26 +- crates/graph/src/nodes/ty_ty.rs | 23 +- crates/graph/src/nodes/var_ty.rs | 21 +- crates/graph/src/range/elem/concrete.rs | 142 +- crates/graph/src/range/elem/elem_enum.rs | 1391 ------------ .../graph/src/range/elem/elem_enum/arena.rs | 216 ++ .../graph/src/range/elem/elem_enum/impls.rs | 686 ++++++ crates/graph/src/range/elem/elem_enum/mod.rs | 28 + crates/graph/src/range/elem/elem_enum/ops.rs | 93 + .../src/range/elem/elem_enum/range_elem.rs | 617 ++++++ .../graph/src/range/elem/elem_enum/traits.rs | 114 + crates/graph/src/range/elem/elem_trait.rs | 97 +- crates/graph/src/range/elem/expr.rs | 1213 ----------- crates/graph/src/range/elem/expr/collapse.rs | 633 ++++++ crates/graph/src/range/elem/expr/mod.rs | 728 +++++++ .../graph/src/range/elem/expr/simplify/add.rs | 162 ++ .../graph/src/range/elem/expr/simplify/mod.rs | 55 + .../src/range/elem/expr/simplify/ords.rs | 122 ++ .../graph/src/range/elem/expr/simplify/sub.rs | 177 ++ crates/graph/src/range/elem/map_or_array.rs | 309 ++- crates/graph/src/range/elem/mod.rs | 54 +- crates/graph/src/range/elem/reference.rs | 195 +- crates/graph/src/range/exec/add.rs | 111 - crates/graph/src/range/exec/bitwise.rs | 780 ++++++- crates/graph/src/range/exec/cast.rs | 304 ++- crates/graph/src/range/exec/div.rs | 146 -- crates/graph/src/range/exec/exec_op.rs | 1868 +++-------------- crates/graph/src/range/exec/exp.rs | 85 - crates/graph/src/range/exec/logical.rs | 55 - crates/graph/src/range/exec/math_ops/add.rs | 569 +++++ crates/graph/src/range/exec/math_ops/div.rs | 494 +++++ crates/graph/src/range/exec/math_ops/exp.rs | 201 ++ crates/graph/src/range/exec/math_ops/mod.rs | 12 + .../graph/src/range/exec/math_ops/modulo.rs | 329 +++ crates/graph/src/range/exec/math_ops/mul.rs | 507 +++++ crates/graph/src/range/exec/math_ops/sub.rs | 567 +++++ crates/graph/src/range/exec/max.rs | 84 +- crates/graph/src/range/exec/mem_ops/concat.rs | 297 ++- .../graph/src/range/exec/mem_ops/mem_get.rs | 409 ++++ .../graph/src/range/exec/mem_ops/mem_set.rs | 405 +++- .../graph/src/range/exec/mem_ops/memcopy.rs | 25 + crates/graph/src/range/exec/mem_ops/mod.rs | 19 +- crates/graph/src/range/exec/min.rs | 84 +- crates/graph/src/range/exec/mod.rs | 34 +- crates/graph/src/range/exec/modulo.rs | 47 - crates/graph/src/range/exec/mul.rs | 111 - crates/graph/src/range/exec/ord.rs | 216 -- crates/graph/src/range/exec/shift.rs | 190 +- crates/graph/src/range/exec/sub.rs | 149 -- .../src/range/exec/truthy_ops/logical.rs | 145 ++ crates/graph/src/range/exec/truthy_ops/mod.rs | 5 + crates/graph/src/range/exec/truthy_ops/ord.rs | 333 +++ crates/graph/src/range/exec_traits.rs | 25 +- crates/graph/src/range/mod.rs | 11 + crates/graph/src/range/range_string.rs | 109 +- crates/graph/src/range/range_trait.rs | 57 +- crates/graph/src/range/solc_range.rs | 406 ++-- crates/graph/src/solvers/atoms.rs | 122 +- crates/graph/src/solvers/brute.rs | 285 ++- crates/graph/src/solvers/dl.rs | 272 ++- crates/graph/src/var_type.rs | 32 +- crates/pyrometer/benches/parse.rs | 37 +- crates/pyrometer/src/analyzer.rs | 137 +- crates/pyrometer/src/analyzer_backend.rs | 53 +- crates/pyrometer/src/graph_backend.rs | 217 +- crates/pyrometer/tests/helpers.rs | 37 +- crates/pyrometer/tests/no_killed_ctxs.rs | 3 +- crates/pyrometer/tests/test_data/bitwise.sol | 2 +- crates/pyrometer/tests/test_data/cast.sol | 2 +- .../pyrometer/tests/test_data/const_var.sol | 2 +- .../pyrometer/tests/test_data/intrinsics.sol | 2 +- crates/pyrometer/tests/test_data/math.sol | 40 +- crates/pyrometer/tests/test_data/storage.sol | 2 +- crates/shared/src/analyzer_like.rs | 11 +- crates/shared/src/graph_like.rs | 37 +- crates/solc-expressions/src/array.rs | 91 +- crates/solc-expressions/src/assign.rs | 62 +- crates/solc-expressions/src/bin_op.rs | 914 ++++---- crates/solc-expressions/src/cmp.rs | 142 +- crates/solc-expressions/src/cond_op.rs | 101 +- .../src/context_builder/expr.rs | 142 +- .../src/context_builder/fn_calls.rs | 53 +- .../src/context_builder/mod.rs | 83 +- .../src/context_builder/stmt.rs | 287 ++- crates/solc-expressions/src/env.rs | 6 +- .../src/func_call/func_caller.rs | 165 +- .../solc-expressions/src/func_call/helper.rs | 163 +- .../src/func_call/internal_call.rs | 38 +- .../src/func_call/intrinsic_call/abi.rs | 9 +- .../src/func_call/intrinsic_call/array.rs | 71 +- .../src/func_call/intrinsic_call/block.rs | 9 +- .../func_call/intrinsic_call/constructors.rs | 29 +- .../func_call/intrinsic_call/dyn_builtin.rs | 41 +- .../intrinsic_call/intrinsic_caller.rs | 60 +- .../func_call/intrinsic_call/precompile.rs | 18 +- .../src/func_call/intrinsic_call/solidity.rs | 29 +- .../src/func_call/intrinsic_call/types.rs | 53 +- crates/solc-expressions/src/func_call/join.rs | 608 +++--- .../src/func_call/modifier.rs | 184 +- .../src/func_call/namespaced_call.rs | 62 +- crates/solc-expressions/src/list.rs | 20 +- crates/solc-expressions/src/literal.rs | 8 +- crates/solc-expressions/src/loops.rs | 32 +- .../src/member_access/list_access.rs | 33 +- .../src/member_access/member_trait.rs | 16 +- .../src/pre_post_in_decrement.rs | 76 +- crates/solc-expressions/src/require.rs | 482 +++-- crates/solc-expressions/src/variable.rs | 90 +- .../solc-expressions/src/yul/yul_builder.rs | 131 +- .../solc-expressions/src/yul/yul_cond_op.rs | 109 +- crates/solc-expressions/src/yul/yul_funcs.rs | 101 +- 141 files changed, 14379 insertions(+), 9028 deletions(-) delete mode 100644 crates/graph/src/range/elem/elem_enum.rs create mode 100644 crates/graph/src/range/elem/elem_enum/arena.rs create mode 100644 crates/graph/src/range/elem/elem_enum/impls.rs create mode 100644 crates/graph/src/range/elem/elem_enum/mod.rs create mode 100644 crates/graph/src/range/elem/elem_enum/ops.rs create mode 100644 crates/graph/src/range/elem/elem_enum/range_elem.rs create mode 100644 crates/graph/src/range/elem/elem_enum/traits.rs delete mode 100644 crates/graph/src/range/elem/expr.rs create mode 100644 crates/graph/src/range/elem/expr/collapse.rs create mode 100644 crates/graph/src/range/elem/expr/mod.rs create mode 100644 crates/graph/src/range/elem/expr/simplify/add.rs create mode 100644 crates/graph/src/range/elem/expr/simplify/mod.rs create mode 100644 crates/graph/src/range/elem/expr/simplify/ords.rs create mode 100644 crates/graph/src/range/elem/expr/simplify/sub.rs delete mode 100644 crates/graph/src/range/exec/add.rs delete mode 100644 crates/graph/src/range/exec/div.rs delete mode 100644 crates/graph/src/range/exec/exp.rs delete mode 100644 crates/graph/src/range/exec/logical.rs create mode 100644 crates/graph/src/range/exec/math_ops/add.rs create mode 100644 crates/graph/src/range/exec/math_ops/div.rs create mode 100644 crates/graph/src/range/exec/math_ops/exp.rs create mode 100644 crates/graph/src/range/exec/math_ops/mod.rs create mode 100644 crates/graph/src/range/exec/math_ops/modulo.rs create mode 100644 crates/graph/src/range/exec/math_ops/mul.rs create mode 100644 crates/graph/src/range/exec/math_ops/sub.rs create mode 100644 crates/graph/src/range/exec/mem_ops/mem_get.rs create mode 100644 crates/graph/src/range/exec/mem_ops/memcopy.rs delete mode 100644 crates/graph/src/range/exec/modulo.rs delete mode 100644 crates/graph/src/range/exec/mul.rs delete mode 100644 crates/graph/src/range/exec/ord.rs delete mode 100644 crates/graph/src/range/exec/sub.rs create mode 100644 crates/graph/src/range/exec/truthy_ops/logical.rs create mode 100644 crates/graph/src/range/exec/truthy_ops/mod.rs create mode 100644 crates/graph/src/range/exec/truthy_ops/ord.rs diff --git a/Cargo.lock b/Cargo.lock index 0ce4871f..bd01078d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -755,6 +755,7 @@ dependencies = [ "itertools", "lazy_static", "petgraph", + "pretty_assertions", "shared", "solang-parser", "tracing", @@ -1328,6 +1329,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "primitive-types" version = "0.12.2" diff --git a/Cargo.toml b/Cargo.toml index c6fb748f..5bc8a535 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ [workspace] members = [ - "crates/analyzers", - "crates/cli", - "crates/graph", - "crates/pyrometer", - "crates/queries", - "crates/shared", - "crates/solc-expressions", + "crates/analyzers", + "crates/cli", + "crates/graph", + "crates/pyrometer", + "crates/queries", + "crates/shared", + "crates/solc-expressions", ] resolver = "2" @@ -17,14 +17,18 @@ authors = ["Brock Elmore"] license = "MIT OR Apache-2.0" homepage = "https://github.com/nascentxyz/pyrometer" repository = "https://github.com/nascentxyz/pyrometer" -exclude = ["benches/", "tests/", "examples/"] # exclude the benches directory from the build +exclude = [ + "benches/", + "tests/", + "examples/", +] # exclude the benches directory from the build rust-version = "1.74" [profile.release] debug = true [profile.dev] -opt-level = 1 # Enable some optimizations like tail call +# opt-level = 1 # Enable some optimizations like tail call inline = true [profile.bench] @@ -40,7 +44,11 @@ solc-expressions = { path = "crates/solc-expressions" } solang-parser = { version = "0.2.4", features = ["pt-serde"] } tracing = { version = "0.1", features = ["attributes"] } -tracing-subscriber = { version = "0.3", features = ["registry", "env-filter", "fmt"] } +tracing-subscriber = { version = "0.3", features = [ + "registry", + "env-filter", + "fmt", +] } tracing-tree = "0.3.0" ethers-core = "*" hex = "0.4.3" @@ -55,7 +63,7 @@ ahash = "0.8.10" # we patch ariadne to allow for counting by bytes because solang uses byte locations not char locations [patch.crates-io] -ariadne = {git = "https://github.com/brockelmore/ariadne"} +ariadne = { git = "https://github.com/brockelmore/ariadne" } # ###################################### # # Benchmarks @@ -63,4 +71,4 @@ ariadne = {git = "https://github.com/brockelmore/ariadne"} # [[bench]] # name = "parse" -# harness = false \ No newline at end of file +# harness = false diff --git a/crates/analyzers/src/bounds.rs b/crates/analyzers/src/bounds.rs index a3877649..636147a9 100644 --- a/crates/analyzers/src/bounds.rs +++ b/crates/analyzers/src/bounds.rs @@ -1,9 +1,12 @@ use crate::{FunctionVarsBoundAnalysis, LocSpan, LocStrSpan, ReportConfig, VarBoundAnalysis}; use graph::{ - nodes::ContextNode, range_string::ToRangeString, GraphBackend, Range, RangeEval, SolcRange, + elem::Elem, + nodes::{Concrete, ContextNode}, + range_string::ToRangeString, + GraphBackend, Range, RangeEval, SolcRange, }; -use shared::StorageLocation; +use shared::{RangeArena, StorageLocation}; use ariadne::{Color, Fmt, Label, Span}; use std::collections::{BTreeMap, BTreeSet}; @@ -71,9 +74,13 @@ pub struct OrderedAnalysis { } impl OrderedAnalysis { - pub fn from_bound_analysis(ba: VarBoundAnalysis, analyzer: &impl GraphBackend) -> Self { + pub fn from_bound_analysis( + ba: VarBoundAnalysis, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Self { let mut analyses: BTreeMap> = Default::default(); - if let Some(init) = ba.init_item(analyzer) { + if let Some(init) = ba.init_item(analyzer, arena) { let source: usize = *LocSpan(init.loc.1).source(); let mut set = BTreeSet::new(); set.insert(init.into()); @@ -83,7 +90,8 @@ impl OrderedAnalysis { .iter() .enumerate() .for_each(|(_i, bound_change)| { - let (parts, unsat) = range_parts(analyzer, &ba.report_config, &bound_change.1); + let (parts, unsat) = + range_parts(analyzer, arena, &ba.report_config, &bound_change.1); let item = StrippedAnalysisItem { init: false, name: ba.var_display_name.clone(), @@ -91,7 +99,7 @@ impl OrderedAnalysis { order: (bound_change.0.end() - bound_change.0.start()) as i32, //i as i32, // storage: ba.storage.clone(), ctx: ba.ctx, - ctx_conditionals: ba.conditionals(analyzer), + ctx_conditionals: ba.conditionals(analyzer, arena), parts, unsat, }; @@ -107,11 +115,12 @@ impl OrderedAnalysis { pub fn from_func_analysis( fvba: FunctionVarsBoundAnalysis, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Self { let mut analyses = Self::default(); fvba.vars_by_ctx.iter().for_each(|(_ctx, bas)| { bas.iter().for_each(|ba| { - analyses.extend(Self::from_bound_analysis(ba.clone(), analyzer)); + analyses.extend(Self::from_bound_analysis(ba.clone(), analyzer, arena)); }) }); analyses @@ -254,39 +263,40 @@ impl ToString for RangePart { /// Creates an Vec<[RangePart]> from a range based on the current [ReportConfig] pub fn range_parts( analyzer: &impl GraphBackend, + arena: &mut RangeArena>, report_config: &ReportConfig, range: &SolcRange, ) -> (Vec, bool) { let mut parts = vec![]; let min = if report_config.eval_bounds { range - .evaled_range_min(analyzer) + .evaled_range_min(analyzer, arena) .unwrap() - .to_range_string(false, analyzer) + .to_range_string(false, analyzer, arena) .s } else if report_config.simplify_bounds { range - .simplified_range_min(analyzer) + .simplified_range_min(analyzer, arena) .unwrap() - .to_range_string(false, analyzer) + .to_range_string(false, analyzer, arena) .s } else { - range.range_min().to_range_string(false, analyzer).s + range.range_min().to_range_string(false, analyzer, arena).s }; let max = if report_config.eval_bounds { range - .evaled_range_max(analyzer) + .evaled_range_max(analyzer, arena) .unwrap() - .to_range_string(true, analyzer) + .to_range_string(true, analyzer, arena) .s } else if report_config.simplify_bounds { range - .simplified_range_max(analyzer) + .simplified_range_max(analyzer, arena) .unwrap() - .to_range_string(true, analyzer) + .to_range_string(true, analyzer, arena) .s } else { - range.range_max().to_range_string(true, analyzer).s + range.range_max().to_range_string(true, analyzer, arena).s }; if min == max { @@ -301,8 +311,8 @@ pub fn range_parts( let mut excls = range_excl .iter() .map(|range| { - let min = range.to_range_string(false, analyzer).s; - let max = range.to_range_string(true, analyzer).s; + let min = range.to_range_string(false, analyzer, arena).s; + let max = range.to_range_string(true, analyzer, arena).s; if min == max { RangePart::Equal(min) } else { @@ -314,6 +324,6 @@ pub fn range_parts( excls })); } - let unsat = range.unsat(analyzer); + let unsat = range.unsat(analyzer, arena); (parts, unsat) } diff --git a/crates/analyzers/src/func_analyzer/mod.rs b/crates/analyzers/src/func_analyzer/mod.rs index f41db5cd..dc3c5bcf 100644 --- a/crates/analyzers/src/func_analyzer/mod.rs +++ b/crates/analyzers/src/func_analyzer/mod.rs @@ -4,10 +4,11 @@ use crate::{ }; use graph::{ - nodes::{ContextNode, KilledKind}, + elem::Elem, + nodes::{Concrete, ContextNode, KilledKind}, AnalyzerBackend, GraphBackend, }; -use shared::Search; +use shared::{RangeArena, Search}; use ariadne::{Color, Config, Fmt, Label, Report, Span}; use solang_parser::pt::CodeLocation; @@ -46,6 +47,7 @@ impl<'a> FunctionVarsBoundAnalysis { &self, file_mapping: &'a BTreeMap, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Vec> { let mut handled_ctx_switches = BTreeSet::default(); let reports = self @@ -56,7 +58,7 @@ impl<'a> FunctionVarsBoundAnalysis { let deps = ctx.ctx_deps(analyzer).unwrap(); let deps = deps .iter() - .map(|var| (var.as_controllable_name(analyzer).unwrap(), var)) + .map(|var| (var.as_controllable_name(analyzer, arena).unwrap(), var)) .collect::>(); // create the bound strings // let atoms = ctx.dep_atoms(analyzer).unwrap(); @@ -64,7 +66,7 @@ impl<'a> FunctionVarsBoundAnalysis { // let mut handled_atom = vec![]; // let mut bounds_string: Vec = vec![]; // atoms.iter().enumerate().for_each(|(i, atom)| { - // let atom_str = atom.to_range_string(true, analyzer).s; + // let atom_str = atom.to_range_string(true, analyzer, arena).s; // if !handled_atom.contains(&atom_str) { // handled_atom.push(atom_str.clone()); // bounds_string.push(format!("{}. {}", i + 1, atom_str)) @@ -78,7 +80,8 @@ impl<'a> FunctionVarsBoundAnalysis { .filter_map(|(i, (name, cvar))| { let range = cvar.ref_range(analyzer).unwrap()?; - let (parts, _unsat) = range_parts(analyzer, &self.report_config, &range); + let (parts, _unsat) = + range_parts(analyzer, arena, &self.report_config, &range); let ret = parts.into_iter().fold( format!("{}. {name}", i + 1), |mut acc, _part| { @@ -119,7 +122,7 @@ impl<'a> FunctionVarsBoundAnalysis { let mut labels: Vec<_> = analyses .iter() .flat_map(|analysis| { - let mut labels = analysis.labels(analyzer); + let mut labels = analysis.labels(analyzer, arena); labels.extend( analysis .spanned_ctx_info @@ -197,6 +200,7 @@ impl<'a> FunctionVarsBoundAnalysis { let range = var.ref_range(analyzer).unwrap()?; let (parts, _unsat) = range_parts( analyzer, + arena, &self.report_config, &range, ); @@ -255,7 +259,7 @@ impl<'a> FunctionVarsBoundAnalysis { .filter_map(|(loc, var)| { let range = var.ref_range(analyzer).unwrap()?; let (parts, _unsat) = - range_parts(analyzer, &self.report_config, &range); + range_parts(analyzer, arena, &self.report_config, &range); Some( Label::new(LocStrSpan::new(file_mapping, loc)) .with_message( @@ -306,6 +310,7 @@ impl FunctionVarsBoundAnalyzer for T where T: VarBoundAnalyzer + Search + Ana pub trait FunctionVarsBoundAnalyzer: VarBoundAnalyzer + Search + AnalyzerBackend + Sized { fn bounds_for_lineage<'a>( &'a self, + arena: &mut RangeArena>, file_mapping: &'a BTreeMap, ctx: ContextNode, edges: Vec, @@ -353,10 +358,11 @@ pub trait FunctionVarsBoundAnalyzer: VarBoundAnalyzer + Search + AnalyzerBackend let is_ret = var.is_return_node_in_any(&parents, self); if is_ret | report_config.show_tmps - | (report_config.show_consts && var.is_const(self).unwrap()) + | (report_config.show_consts && var.is_const(self, arena).unwrap()) | (report_config.show_symbolics && var.is_symbolic(self).unwrap()) { Some(self.bounds_for_var_in_family_tree( + arena, file_mapping, parents.clone(), var.name(self).unwrap(), @@ -385,6 +391,7 @@ pub trait FunctionVarsBoundAnalyzer: VarBoundAnalyzer + Search + AnalyzerBackend fn bounds_for_all<'a>( &'a self, + arena: &mut RangeArena>, file_mapping: &'a BTreeMap, ctx: ContextNode, report_config: ReportConfig, @@ -393,6 +400,6 @@ pub trait FunctionVarsBoundAnalyzer: VarBoundAnalyzer + Search + AnalyzerBackend if edges.is_empty() { edges.push(ctx); } - self.bounds_for_lineage(file_mapping, ctx, edges, report_config) + self.bounds_for_lineage(arena, file_mapping, ctx, edges, report_config) } } diff --git a/crates/analyzers/src/func_analyzer/report_display.rs b/crates/analyzers/src/func_analyzer/report_display.rs index 9bead892..be106ff4 100644 --- a/crates/analyzers/src/func_analyzer/report_display.rs +++ b/crates/analyzers/src/func_analyzer/report_display.rs @@ -1,6 +1,8 @@ use crate::{FunctionVarsBoundAnalysis, LocStrSpan, ReportDisplay, ReportKind}; -use graph::GraphBackend; +use graph::{elem::Elem, nodes::Concrete, GraphBackend}; + +use shared::RangeArena; use ariadne::{Cache, Color, Config, Fmt, Label, Report, Span}; @@ -27,7 +29,7 @@ impl<'a> ReportDisplay for CLIFunctionVarsBoundAnalysis<'a> { fn report_kind(&self) -> ReportKind { ReportKind::Custom("Bounds", Color::Cyan) } - fn msg(&self, analyzer: &impl GraphBackend) -> String { + fn msg(&self, analyzer: &impl GraphBackend, _arena: &mut RangeArena>) -> String { format!( "Bounds for function: {}", format!( @@ -41,17 +43,25 @@ impl<'a> ReportDisplay for CLIFunctionVarsBoundAnalysis<'a> { ) } - fn labels(&self, _analyzer: &impl GraphBackend) -> Vec> { + fn labels( + &self, + _analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, + ) -> Vec> { vec![] } - fn reports(&self, analyzer: &impl GraphBackend) -> Vec> { + fn reports( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Vec> { let mut report = Report::build( self.report_kind(), self.func_var_bound_analysis.ctx_loc.source(), self.func_var_bound_analysis.ctx_loc.start(), ) - .with_message(self.msg(analyzer)) + .with_message(self.msg(analyzer, arena)) .with_config( Config::default() .with_cross_gap(false) @@ -59,7 +69,7 @@ impl<'a> ReportDisplay for CLIFunctionVarsBoundAnalysis<'a> { .with_tab_width(4), ); - report.add_labels(self.labels(analyzer)); + report.add_labels(self.labels(analyzer, arena)); if let Some((killed_span, kind)) = &self.func_var_bound_analysis.ctx_killed { report = report.with_label( Label::new(killed_span.clone()) @@ -70,23 +80,34 @@ impl<'a> ReportDisplay for CLIFunctionVarsBoundAnalysis<'a> { let mut reports = vec![report.finish()]; - reports.extend( - self.func_var_bound_analysis - .reports_for_forks(self.file_mapping, analyzer), - ); + reports.extend(self.func_var_bound_analysis.reports_for_forks( + self.file_mapping, + analyzer, + arena, + )); reports } - fn print_reports(&self, mut src: &mut impl Cache, analyzer: &impl GraphBackend) { - let reports = &self.reports(analyzer); + fn print_reports( + &self, + mut src: &mut impl Cache, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) { + let reports = &self.reports(analyzer, arena); for report in reports.iter() { report.print(&mut src).unwrap(); } } - fn eprint_reports(&self, mut src: &mut impl Cache, analyzer: &impl GraphBackend) { - let reports = &self.reports(analyzer); + fn eprint_reports( + &self, + mut src: &mut impl Cache, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) { + let reports = &self.reports(analyzer, arena); reports.iter().for_each(|report| { report.eprint(&mut src).unwrap(); }); diff --git a/crates/analyzers/src/lib.rs b/crates/analyzers/src/lib.rs index a2fbc7fe..f469f356 100644 --- a/crates/analyzers/src/lib.rs +++ b/crates/analyzers/src/lib.rs @@ -1,8 +1,8 @@ pub mod bounds; use ariadne::{Cache, Label, Report, ReportKind, Span}; -use graph::{AnalyzerBackend, GraphBackend}; -use shared::Search; +use graph::{elem::Elem, nodes::Concrete, AnalyzerBackend, GraphBackend}; +use shared::{RangeArena, Search}; use solang_parser::pt::Loc; use std::collections::BTreeMap; @@ -166,9 +166,27 @@ impl Default for ReportConfig { pub trait ReportDisplay { fn report_kind(&self) -> ReportKind; - fn msg(&self, analyzer: &impl GraphBackend) -> String; - fn labels(&self, analyzer: &impl GraphBackend) -> Vec>; - fn reports(&self, analyzer: &impl GraphBackend) -> Vec>; - fn print_reports(&self, src: &mut impl Cache, analyzer: &impl GraphBackend); - fn eprint_reports(&self, src: &mut impl Cache, analyzer: &impl GraphBackend); + fn msg(&self, analyzer: &impl GraphBackend, arena: &mut RangeArena>) -> String; + fn labels( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Vec>; + fn reports( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Vec>; + fn print_reports( + &self, + src: &mut impl Cache, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ); + fn eprint_reports( + &self, + src: &mut impl Cache, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ); } diff --git a/crates/analyzers/src/var_analyzer/mod.rs b/crates/analyzers/src/var_analyzer/mod.rs index 9fac71ef..26399012 100644 --- a/crates/analyzers/src/var_analyzer/mod.rs +++ b/crates/analyzers/src/var_analyzer/mod.rs @@ -4,10 +4,11 @@ use crate::{ }; use graph::{ - nodes::{ContextNode, ContextVarNode, KilledKind}, + elem::Elem, + nodes::{Concrete, ContextNode, ContextVarNode, KilledKind}, AnalyzerBackend, GraphBackend, Range, SolcRange, }; -use shared::{Search, StorageLocation}; +use shared::{RangeArena, Search, StorageLocation}; use std::collections::BTreeSet; @@ -66,7 +67,11 @@ impl Default for VarBoundAnalysis { } impl VarBoundAnalysis { - pub fn conditionals(&self, analyzer: &impl GraphBackend) -> Vec<(String, Vec)> { + pub fn conditionals( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Vec<(String, Vec)> { let deps = self.ctx.ctx_deps(analyzer).unwrap(); let deps = deps .iter() @@ -77,18 +82,22 @@ impl VarBoundAnalysis { .enumerate() .filter_map(|(_i, (_name, cvar))| { let range = cvar.ref_range(analyzer).unwrap()?; - let parts = range_parts(analyzer, &self.report_config, &range).0; + let parts = range_parts(analyzer, arena, &self.report_config, &range).0; Some((cvar.display_name(analyzer).unwrap(), parts)) }) .collect() } /// Creates an [AnalysisItem] if there is a initial bound for a variable - pub fn init_item(&self, analyzer: &impl GraphBackend) -> Option { + pub fn init_item( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Option { let mut parts = vec![]; let mut unsat = false; if let Some(init_range) = &self.var_def.1 { - (parts, unsat) = range_parts(analyzer, &self.report_config, init_range) + (parts, unsat) = range_parts(analyzer, arena, &self.report_config, init_range) } if parts.is_empty() { None @@ -100,7 +109,7 @@ impl VarBoundAnalysis { loc: self.var_def.0.clone(), storage: self.storage, ctx: self.ctx, - ctx_conditionals: self.conditionals(analyzer), + ctx_conditionals: self.conditionals(analyzer, arena), parts, unsat, }) @@ -114,6 +123,7 @@ pub trait VarBoundAnalyzer: Search + AnalyzerBackend + Sized { /// generate a bound analysis for a variable throughout the lineage fn bounds_for_var_in_family_tree( &self, + arena: &mut RangeArena>, file_mapping: &'_ BTreeMap, ordered_ctxs: Vec, var_name: String, @@ -125,6 +135,7 @@ pub trait VarBoundAnalyzer: Search + AnalyzerBackend + Sized { .filter_map(|ctx| Some((ctx, ctx.var_by_name(self, &var_name)?))) .for_each(|(_ctx, cvar)| { let analysis = self.bounds_for_var_node( + arena, &inherited, file_mapping, &var_name, @@ -140,6 +151,7 @@ pub trait VarBoundAnalyzer: Search + AnalyzerBackend + Sized { /// Analyzes the bounds for a variable up to the provided node fn bounds_for_var_node( &self, + arena: &mut RangeArena>, inherited: &Option, file_mapping: &'_ BTreeMap, var_name: &str, @@ -232,14 +244,14 @@ pub trait VarBoundAnalyzer: Search + AnalyzerBackend + Sized { }; if let Some(curr_range) = comparator.ref_range(self).unwrap() { - let mut cr_min = curr_range.evaled_range_min(self).unwrap(); - let mut cr_max = curr_range.evaled_range_max(self).unwrap(); + let mut cr_min = curr_range.evaled_range_min(self, arena).unwrap(); + let mut cr_max = curr_range.evaled_range_max(self, arena).unwrap(); let mut cr_excl = curr_range.range_exclusions(); if needs_curr { if let Some(next_range) = curr.ref_range(self).unwrap() { - let nr_min = next_range.evaled_range_min(self).unwrap(); - let nr_max = next_range.evaled_range_max(self).unwrap(); + let nr_min = next_range.evaled_range_min(self, arena).unwrap(); + let nr_max = next_range.evaled_range_max(self, arena).unwrap(); let nr_excl = &next_range.range_exclusions(); // check if there was a bound change @@ -264,8 +276,8 @@ pub trait VarBoundAnalyzer: Search + AnalyzerBackend + Sized { while let Some(next) = curr.next_version(self) { if let Some(next_range) = next.ref_range(self).unwrap() { - let nr_min = next_range.evaled_range_min(self).unwrap(); - let nr_max = next_range.evaled_range_max(self).unwrap(); + let nr_min = next_range.evaled_range_min(self, arena).unwrap(); + let nr_max = next_range.evaled_range_max(self, arena).unwrap(); let nr_excl = &next_range.range_exclusions(); // check if there was a bound change diff --git a/crates/analyzers/src/var_analyzer/report_display.rs b/crates/analyzers/src/var_analyzer/report_display.rs index 3f876c7d..a4b1e48a 100644 --- a/crates/analyzers/src/var_analyzer/report_display.rs +++ b/crates/analyzers/src/var_analyzer/report_display.rs @@ -3,7 +3,9 @@ use crate::{ LocStrSpan, ReportDisplay, ReportKind, VarBoundAnalysis, }; -use graph::GraphBackend; +use graph::{elem::Elem, nodes::Concrete, GraphBackend}; + +use shared::RangeArena; use ariadne::{Cache, Color, Config, Fmt, Label, Report, Span}; @@ -11,16 +13,20 @@ impl ReportDisplay for VarBoundAnalysis { fn report_kind(&self) -> ReportKind { ReportKind::Custom("Bounds", Color::Cyan) } - fn msg(&self, analyzer: &impl GraphBackend) -> String { + fn msg(&self, analyzer: &impl GraphBackend, _arena: &mut RangeArena>) -> String { format!( "Bounds for {} in {}:", self.var_display_name, self.ctx.underlying(analyzer).unwrap().path ) } - fn labels(&self, analyzer: &impl GraphBackend) -> Vec> { + fn labels( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Vec> { let mut labels = if self.report_config.show_initial_bounds { - if let Some(init_item) = self.init_item(analyzer) { + if let Some(init_item) = self.init_item(analyzer, arena) { vec![init_item.into()] } else { vec![] @@ -35,7 +41,7 @@ impl ReportDisplay for VarBoundAnalysis { .enumerate() .map(|(_i, bound_change)| { let (parts, unsat) = - range_parts(analyzer, &self.report_config, &bound_change.1); + range_parts(analyzer, arena, &self.report_config, &bound_change.1); AnalysisItem { init: false, name: self.var_display_name.clone(), @@ -43,7 +49,7 @@ impl ReportDisplay for VarBoundAnalysis { order: (bound_change.0.end() - bound_change.0.start()) as i32, storage: self.storage, ctx: self.ctx, - ctx_conditionals: self.conditionals(analyzer), + ctx_conditionals: self.conditionals(analyzer, arena), parts, unsat, } @@ -55,13 +61,17 @@ impl ReportDisplay for VarBoundAnalysis { labels } - fn reports(&self, analyzer: &impl GraphBackend) -> Vec> { + fn reports( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Vec> { let mut report = Report::build( self.report_kind(), self.var_def.0.source(), self.var_def.0.start(), ) - .with_message(self.msg(analyzer)) + .with_message(self.msg(analyzer, arena)) .with_config( Config::default() .with_cross_gap(false) @@ -69,7 +79,7 @@ impl ReportDisplay for VarBoundAnalysis { .with_tab_width(4), ); - report.add_labels(self.labels(analyzer)); + report.add_labels(self.labels(analyzer, arena)); if let Some((killed_span, kind)) = &self.ctx_killed { report = report.with_label( @@ -93,15 +103,25 @@ impl ReportDisplay for VarBoundAnalysis { reports } - fn print_reports(&self, mut src: &mut impl Cache, analyzer: &impl GraphBackend) { - let reports = self.reports(analyzer); + fn print_reports( + &self, + mut src: &mut impl Cache, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) { + let reports = self.reports(analyzer, arena); reports.into_iter().for_each(|report| { report.print(&mut src).unwrap(); }); } - fn eprint_reports(&self, mut src: &mut impl Cache, analyzer: &impl GraphBackend) { - let reports = self.reports(analyzer); + fn eprint_reports( + &self, + mut src: &mut impl Cache, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) { + let reports = self.reports(analyzer, arena); reports.into_iter().for_each(|report| { report.eprint(&mut src).unwrap(); }); diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index a473dc7c..a55d8426 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -226,9 +226,11 @@ fn main() { show_nonreverts: args.show_nonreverts.unwrap_or(true), }, }; - let mut analyzer = Analyzer::default(); - analyzer.max_depth = args.max_stack_depth; - analyzer.root = Root::RemappingsDirectory(env::current_dir().unwrap()); + let mut analyzer = Analyzer { + max_depth: args.max_stack_depth, + root: Root::RemappingsDirectory(env::current_dir().unwrap()), + ..Default::default() + }; println!("debug panic: {}", args.debug_panic); analyzer.debug_panic = args.debug_panic; @@ -253,15 +255,17 @@ fn main() { panic!("Unsupported file type") }; + let mut arena_base = Default::default(); + let arena = &mut arena_base; let t0 = std::time::Instant::now(); - let maybe_entry = analyzer.parse(&sol, ¤t_path, true); + let maybe_entry = analyzer.parse(arena, &sol, ¤t_path, true); let t_end = t0.elapsed(); let parse_time = t_end.as_millis(); println!("DONE ANALYZING IN: {parse_time}ms. Writing to cli..."); if args.stats { - println!("{}", analyzer.stats(t_end)); + println!("{}", analyzer.stats(t_end, arena)); } // println!("Arena: {:#?}", analyzer.range_arena); @@ -290,19 +294,19 @@ fn main() { analyzer.print_errors(&file_mapping, &mut source_map); if args.open_dot { - analyzer.open_dot() + analyzer.open_dot(arena) } if args.dot { - println!("{}", analyzer.dot_str_no_tmps()); + println!("{}", analyzer.dot_str_no_tmps(arena)); } if args.mermaid { - println!("{}", analyzer.mermaid_str()); + println!("{}", analyzer.mermaid_str(arena)); } if args.open_mermaid { - analyzer.open_mermaid(); + analyzer.open_mermaid(arena); } // println!("{}", analyzer.range_arena.ranges.iter().map(|i| { @@ -375,7 +379,7 @@ fn main() { if !args.funcs.is_empty() { if args.funcs.iter().any(|analyze_for| { FunctionNode::from(func) - .name(&analyzer) + .name(&mut analyzer) .unwrap() .starts_with(analyze_for) }) { @@ -395,17 +399,17 @@ fn main() { if let Some(mut solver) = BruteBinSearchSolver::maybe_new( c.ctx_deps(&analyzer).unwrap(), &mut analyzer, + arena, ) .unwrap() { - println!("created solver"); - match solver.solve(&mut analyzer).unwrap() { + match solver.solve(&mut analyzer, arena).unwrap() { AtomicSolveStatus::Unsat => { println!("TRUE UNSAT: {}", c.path(&analyzer)); } AtomicSolveStatus::Sat(ranges) => { - println!("-----------------------"); - println!("sat for: {}", c.path(&analyzer)); + // println!("-----------------------"); + // println!("sat for: {}", c.path(&analyzer)); ranges.iter().for_each(|(atomic, conc)| { println!( "{}: {}", @@ -415,17 +419,17 @@ fn main() { }); } AtomicSolveStatus::Indeterminate => { - println!("-----------------------"); - println!("sat for: {}", c.path(&analyzer)); - println!("MAYBE UNSAT"); + // println!("-----------------------"); + // println!("sat for: {}", c.path(&analyzer)); + // println!("MAYBE UNSAT"); } } } - println!("-----------------------"); + // println!("-----------------------"); let analysis = analyzer - .bounds_for_lineage(&file_mapping, *c, vec![*c], config) + .bounds_for_lineage(arena, &file_mapping, *c, vec![*c], config) .as_cli_compat(&file_mapping); - analysis.print_reports(&mut source_map, &analyzer); + analysis.print_reports(&mut source_map, &analyzer, arena); // return; } }); @@ -433,9 +437,9 @@ fn main() { } } else if let Some(ctx) = FunctionNode::from(func).maybe_body_ctx(&mut analyzer) { let analysis = analyzer - .bounds_for_all(&file_mapping, ctx, config) + .bounds_for_all(arena, &file_mapping, ctx, config) .as_cli_compat(&file_mapping); - analysis.print_reports(&mut source_map, &analyzer); + analysis.print_reports(&mut source_map, &analyzer, arena); } } } else { @@ -451,19 +455,19 @@ fn main() { let funcs = contract.funcs(&analyzer); for func in funcs.into_iter() { if !args.funcs.is_empty() { - if args.funcs.contains(&func.name(&analyzer).unwrap()) { + if args.funcs.contains(&func.name(&mut analyzer).unwrap()) { let ctx = func.body_ctx(&mut analyzer); let analysis = analyzer - .bounds_for_all(&file_mapping, ctx, config) + .bounds_for_all(arena, &file_mapping, ctx, config) .as_cli_compat(&file_mapping); - analysis.print_reports(&mut source_map, &analyzer); + analysis.print_reports(&mut source_map, &analyzer, arena); } } else { let ctx = func.body_ctx(&mut analyzer); let analysis = analyzer - .bounds_for_all(&file_mapping, ctx, config) + .bounds_for_all(arena, &file_mapping, ctx, config) .as_cli_compat(&file_mapping); - analysis.print_reports(&mut source_map, &analyzer); + analysis.print_reports(&mut source_map, &analyzer, arena); } } }); diff --git a/crates/graph/Cargo.toml b/crates/graph/Cargo.toml index 6bc26f70..af3dff3a 100644 --- a/crates/graph/Cargo.toml +++ b/crates/graph/Cargo.toml @@ -21,4 +21,7 @@ tracing.workspace = true tracing-subscriber.workspace = true itertools = "0.10.5" -lazy_static = "1.4.0" \ No newline at end of file +lazy_static = "1.4.0" + +[dev-dependencies] +pretty_assertions = "1.4.0" diff --git a/crates/graph/src/graph_elements.rs b/crates/graph/src/graph_elements.rs index bc3bbf95..652d643d 100644 --- a/crates/graph/src/graph_elements.rs +++ b/crates/graph/src/graph_elements.rs @@ -1,10 +1,11 @@ use crate::elem::Elem; use crate::{nodes::*, VarType}; -use shared::{AnalyzerLike, GraphLike, Heirarchical, NodeIdx}; +use shared::{AnalyzerLike, GraphLike, Heirarchical, NodeIdx, RangeArena}; use lazy_static::lazy_static; -use solang_parser::pt::Identifier; +use petgraph::{Directed, Graph}; +use solang_parser::pt::{Identifier, Loc}; use std::collections::HashMap; @@ -20,10 +21,20 @@ pub trait AnalyzerBackend: Function = Function, > + GraphBackend { + fn add_concrete_var( + &mut self, + ctx: ContextNode, + concrete: Concrete, + loc: Loc, + ) -> Result; } pub trait AsDotStr { - fn as_dot_str(&self, analyzer: &impl GraphBackend) -> String; + fn as_dot_str( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> String; } #[derive(Debug, Clone, Ord, Eq, PartialEq, PartialOrd)] @@ -102,24 +113,28 @@ pub enum Node { Block(Block), } -pub fn as_dot_str(idx: NodeIdx, analyzer: &impl GraphBackend) -> String { +pub fn as_dot_str( + idx: NodeIdx, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> String { use crate::Node::*; match analyzer.node(idx) { - Context(_) => ContextNode::from(idx).as_dot_str(analyzer), - ContextVar(_) => ContextVarNode::from(idx).as_dot_str(analyzer), + Context(_) => ContextNode::from(idx).as_dot_str(analyzer, arena), + ContextVar(_) => ContextVarNode::from(idx).as_dot_str(analyzer, arena), ContextFork => "Context Fork".to_string(), FunctionCall => "Function Call".to_string(), Builtin(bi) => bi.as_string(analyzer).unwrap(), - VarType(v_ty) => v_ty.as_dot_str(analyzer), - Contract(_c) => ContractNode::from(idx).as_dot_str(analyzer), - Function(_f) => FunctionNode::from(idx).as_dot_str(analyzer), - FunctionParam(_fp) => FunctionParamNode::from(idx).as_dot_str(analyzer), - FunctionReturn(_fr) => FunctionReturnNode::from(idx).as_dot_str(analyzer), - Struct(_s) => StructNode::from(idx).as_dot_str(analyzer), - Enum(_e) => EnumNode::from(idx).as_dot_str(analyzer), - Field(_f) => FieldNode::from(idx).as_dot_str(analyzer), - Var(_v) => VarNode::from(idx).as_dot_str(analyzer), - Ty(_t) => TyNode::from(idx).as_dot_str(analyzer), + VarType(v_ty) => v_ty.as_string(analyzer).unwrap(), + Contract(_c) => ContractNode::from(idx).as_dot_str(analyzer, arena), + Function(_f) => FunctionNode::from(idx).as_dot_str(analyzer, arena), + FunctionParam(_fp) => FunctionParamNode::from(idx).as_dot_str(analyzer, arena), + FunctionReturn(_fr) => FunctionReturnNode::from(idx).as_dot_str(analyzer, arena), + Struct(_s) => StructNode::from(idx).as_dot_str(analyzer, arena), + Enum(_e) => EnumNode::from(idx).as_dot_str(analyzer, arena), + Field(_f) => FieldNode::from(idx).as_dot_str(analyzer, arena), + Var(_v) => VarNode::from(idx).as_dot_str(analyzer, arena), + Ty(_t) => TyNode::from(idx).as_dot_str(analyzer, arena), // Concrete(c) => c.as_human_string(), e => format!("{e:?}"), } @@ -380,3 +395,29 @@ pub enum ContextEdge { /// Unused Range, } + +#[derive(Default)] +pub(crate) struct DummyGraph { + pub range_arena: RangeArena>, +} + +impl GraphLike for DummyGraph { + type Node = Node; + type Edge = Edge; + type RangeElem = Elem; + fn graph_mut(&mut self) -> &mut Graph { + panic!("Dummy Graph") + } + + fn graph(&self) -> &Graph { + panic!("Dummy Graph") + } + fn range_arena(&self) -> &RangeArena> { + &self.range_arena + } + fn range_arena_mut(&mut self) -> &mut RangeArena> { + &mut self.range_arena + } +} + +impl GraphBackend for DummyGraph {} diff --git a/crates/graph/src/nodes/block.rs b/crates/graph/src/nodes/block.rs index ded6ebc3..4ac5bf6a 100644 --- a/crates/graph/src/nodes/block.rs +++ b/crates/graph/src/nodes/block.rs @@ -1,5 +1,5 @@ -use crate::{AsDotStr, GraphBackend, GraphError, Node}; -use shared::NodeIdx; +use crate::{nodes::Concrete, range::elem::Elem, AsDotStr, GraphBackend, GraphError, Node}; +use shared::{NodeIdx, RangeArena}; use ethers_core::types::{Address, H256, U256}; @@ -24,7 +24,11 @@ impl BlockNode { } impl AsDotStr for BlockNode { - fn as_dot_str(&self, analyzer: &impl GraphBackend) -> String { + fn as_dot_str( + &self, + analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, + ) -> String { format!("block {{ {:?} }}", self.underlying(analyzer).unwrap()) } } diff --git a/crates/graph/src/nodes/builtin.rs b/crates/graph/src/nodes/builtin.rs index c9feff60..757ca985 100644 --- a/crates/graph/src/nodes/builtin.rs +++ b/crates/graph/src/nodes/builtin.rs @@ -1,7 +1,7 @@ use crate::{nodes::Concrete, AnalyzerBackend, GraphBackend, GraphError, Node, SolcRange, VarType}; use crate::range::elem::*; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; use ethers_core::types::{Address, H256, I256, U256}; use solang_parser::pt::{Expression, Loc, Type}; @@ -251,6 +251,7 @@ impl Builtin { pub fn try_from_ty( ty: Type, analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, ) -> Option { use Type::*; match ty { @@ -265,8 +266,8 @@ impl Builtin { Rational => Some(Builtin::Rational), DynamicBytes => Some(Builtin::DynamicBytes), Mapping { key, value, .. } => { - let key_idx = analyzer.parse_expr(&key, None); - let val_idx = analyzer.parse_expr(&value, None); + let key_idx = analyzer.parse_expr(arena, &key, None); + let val_idx = analyzer.parse_expr(arena, &value, None); let key_var_ty = VarType::try_from_idx(analyzer, key_idx)?; let val_var_ty = VarType::try_from_idx(analyzer, val_idx)?; Some(Builtin::Mapping(key_var_ty, val_var_ty)) @@ -279,7 +280,7 @@ impl Builtin { let inputs = params .iter() .filter_map(|(_, param)| param.as_ref()) - .map(|param| analyzer.parse_expr(¶m.ty, None)) + .map(|param| analyzer.parse_expr(arena, ¶m.ty, None)) .collect::>(); let inputs = inputs .iter() @@ -290,7 +291,7 @@ impl Builtin { let tmp_outputs = params .iter() .filter_map(|(_, param)| param.as_ref()) - .map(|param| analyzer.parse_expr(¶m.ty, None)) + .map(|param| analyzer.parse_expr(arena, ¶m.ty, None)) .collect::>(); outputs = tmp_outputs .iter() diff --git a/crates/graph/src/nodes/concrete.rs b/crates/graph/src/nodes/concrete.rs index bd1c5ef3..eeafe272 100644 --- a/crates/graph/src/nodes/concrete.rs +++ b/crates/graph/src/nodes/concrete.rs @@ -106,12 +106,6 @@ impl Default for Concrete { } } -// impl From for Concrete { -// fn from(u: usize) -> Self { -// Concrete::Uint(256, U256::from(u)) -// } -// } - impl From for Concrete { fn from(u: U256) -> Self { Concrete::Uint(256, u) @@ -130,6 +124,14 @@ impl From> for Concrete { } } +impl From for Concrete { + fn from(u: u8) -> Self { + let mut h = H256::default(); + h.0[0] = u; + Concrete::Bytes(1, h) + } +} + impl From for Concrete { fn from(u: H256) -> Self { Concrete::Bytes(32, u) @@ -163,13 +165,26 @@ impl From for Concrete { } } -impl> From> for Concrete { - fn from(u: Vec) -> Self { - Concrete::Array(u.into_iter().map(|t| t.into()).collect()) +impl From<&str> for Concrete { + fn from(u: &str) -> Self { + Concrete::String(u.to_string()) } } +// impl> From> for Concrete { +// fn from(u: Vec) -> Self { +// Concrete::Array(u.into_iter().map(|t| t.into()).collect()) +// } +// } + impl Concrete { + pub fn raw_bits_u256(&self) -> Option { + match self { + Concrete::Int(_, val) => Some(val.into_raw()), + _ => self.into_u256(), + } + } + pub fn set_indices(&mut self, other: &Self) { match (self, other) { (Concrete::DynBytes(s), Concrete::DynBytes(o)) => { @@ -308,8 +323,23 @@ impl Concrete { } } + pub fn is_zero(&self) -> bool { + self.into_u256() == Some(U256::zero()) + } + + pub fn is_one(&self) -> bool { + self.into_u256() == Some(U256::from(1)) + } + /// Cast from one concrete variant given another concrete variant pub fn cast_from(self, other: &Self) -> Option { + if let (Concrete::DynBytes(s), Concrete::DynBytes(o)) = (&self, other) { + if s.len() < o.len() { + let mut t = s.clone(); + t.resize(o.len(), 0); + return Some(Concrete::DynBytes(t)); + } + } self.cast(other.as_builtin()) } @@ -346,6 +376,13 @@ impl Concrete { matches!(self, Concrete::Int(_, _)) } + pub fn size_wrap(self) -> Self { + match self { + Concrete::Int(size, val) => Concrete::Int(256, val).cast(Builtin::Int(size)).unwrap(), + _ => self, + } + } + /// Performs a literal cast to another type pub fn literal_cast(self, builtin: Builtin) -> Option { match self { @@ -462,33 +499,39 @@ impl Concrete { bit_repr.cast(builtin) } Builtin::Int(size) => { - // no op - if r_size == size { - Some(self) - } else { - let mask = if size == 256 { - U256::MAX / 2 - } else { - U256::from(2).pow((size - 1).into()) - 1 - }; + match r_size.cmp(&size) { + std::cmp::Ordering::Less => { + // upcast + Some(Concrete::Int(size, val)) + } + std::cmp::Ordering::Equal => { + // noop + Some(self) + } + std::cmp::Ordering::Greater => { + // downcast + let mask = if size == 256 { + U256::MAX / 2 + } else { + U256::from(2).pow((size).into()) - 1 + }; - let (_sign, abs) = val.into_sign_and_abs(); + let raw = val.into_raw(); - if abs < mask { - Some(Concrete::Int(size, val)) - } else { - // check if the top bit for the new value is set on the existing value - // if it is, then the cast will result in a negative number - let top_mask = - if abs & (U256::from(1) << U256::from(size)) != U256::zero() { - // sign extension - ((U256::from(1) << U256::from(257 - size)) - U256::from(1)) - << U256::from(size - 1) - } else { - U256::from(0) - }; - - Some(Concrete::Int(size, I256::from_raw((abs & mask) | top_mask))) + if raw < mask / U256::from(2) { + Some(Concrete::Int(size, val)) + } else { + let base_value = raw & mask; + let res = + if base_value >> (size - 1) & U256::from(1) == U256::from(1) { + let top = U256::MAX << size; + base_value | top + } else { + base_value + }; + + Some(Concrete::Int(size, I256::from_raw(res))) + } } } } @@ -687,7 +730,7 @@ impl Concrete { } /// Gets the default max for a given concrete variant. - pub fn max(&self) -> Option { + pub fn max_of_type(&self) -> Option { match self { Concrete::Uint(size, _) => { let max = if *size == 256 { @@ -798,7 +841,7 @@ impl Concrete { } /// Gets the default min for a given concrete variant. - pub fn min(&self) -> Option { + pub fn min_of_type(&self) -> Option { match self { Concrete::Uint(size, _) => Some(Concrete::Uint(*size, 0.into())), Concrete::Int(size, _) => { @@ -851,6 +894,10 @@ impl Concrete { } } + pub fn is_negative(&self) -> bool { + matches!(self, Concrete::Int(_, val) if *val < I256::from(0)) + } + pub fn as_hex_string(&self) -> String { match self { Concrete::Uint(_, val) => { diff --git a/crates/graph/src/nodes/context/expr_ret.rs b/crates/graph/src/nodes/context/expr_ret.rs index b9b6b782..2195fef9 100644 --- a/crates/graph/src/nodes/context/expr_ret.rs +++ b/crates/graph/src/nodes/context/expr_ret.rs @@ -1,5 +1,9 @@ -use crate::{nodes::context::ContextVarNode, AsDotStr, GraphBackend, GraphError, Node, VarType}; -use shared::NodeIdx; +use crate::{ + nodes::{context::ContextVarNode, Concrete}, + range::elem::Elem, + AsDotStr, GraphBackend, GraphError, Node, VarType, +}; +use shared::{NodeIdx, RangeArena}; /// The reason a context was killed #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] @@ -47,8 +51,12 @@ impl ExprRet { pub fn debug_str(&self, analyzer: &impl GraphBackend) -> String { match self { ExprRet::Single(inner) | ExprRet::SingleLiteral(inner) => match analyzer.node(*inner) { - Node::ContextVar(_) => ContextVarNode::from(*inner).display_name(analyzer).unwrap(), - e => format!("{:?}", e), + Node::ContextVar(_) => format!( + "idx_{}: {}", + inner.index(), + ContextVarNode::from(*inner).display_name(analyzer).unwrap() + ), + e => format!("idx_{}: {:?}", inner.index(), e), }, ExprRet::Multi(inner) => { format!( @@ -198,14 +206,18 @@ impl ExprRet { } /// Try to convert to a solidity-like function input string, i.e. `(uint256, uint256, bytes32)` - pub fn try_as_func_input_str(&self, analyzer: &impl GraphBackend) -> String { + pub fn try_as_func_input_str( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> String { match self { ExprRet::Single(inner) | ExprRet::SingleLiteral(inner) => { let idx = inner; match VarType::try_from_idx(analyzer, *idx) { Some(var_ty) => { if let Ok(ty) = var_ty.unresolved_as_resolved(analyzer) { - format!("({})", ty.as_dot_str(analyzer)) + format!("({})", ty.as_dot_str(analyzer, arena)) } else { "".to_string() } @@ -216,7 +228,10 @@ impl ExprRet { ExprRet::Multi(inner) => { let mut strs = vec![]; for ret in inner.iter() { - strs.push(ret.try_as_func_input_str(analyzer).replace(['(', ')'], "")); + strs.push( + ret.try_as_func_input_str(analyzer, arena) + .replace(['(', ')'], ""), + ); } format!("({})", strs.join(", ")) } @@ -276,4 +291,8 @@ impl ExprRet { ExprRet::Null => 0, } } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } } diff --git a/crates/graph/src/nodes/context/node.rs b/crates/graph/src/nodes/context/node.rs index 4a6fb6aa..6c4bb8d5 100644 --- a/crates/graph/src/nodes/context/node.rs +++ b/crates/graph/src/nodes/context/node.rs @@ -1,9 +1,10 @@ use crate::{ - nodes::{Context, ContextVarNode, KilledKind}, + nodes::{Concrete, Context, ContextVarNode, KilledKind}, + range::elem::Elem, AnalyzerBackend, AsDotStr, GraphBackend, GraphError, Node, }; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; use solang_parser::pt::Loc; @@ -12,7 +13,11 @@ use solang_parser::pt::Loc; pub struct ContextNode(pub usize); impl AsDotStr for ContextNode { - fn as_dot_str(&self, analyzer: &impl GraphBackend) -> String { + fn as_dot_str( + &self, + analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, + ) -> String { format!("Context {{ {} }}", self.path(analyzer)) } } diff --git a/crates/graph/src/nodes/context/querying.rs b/crates/graph/src/nodes/context/querying.rs index 717289eb..16fd28a9 100644 --- a/crates/graph/src/nodes/context/querying.rs +++ b/crates/graph/src/nodes/context/querying.rs @@ -115,12 +115,14 @@ impl ContextNode { .map(|modifier_set| { let as_vec = modifier_set.iter().collect::>(); - if as_vec.len() > 2 { - panic!("3+ visible functions with the same name. This is invalid solidity, {as_vec:#?}") - } else if as_vec.len() == 2 { - as_vec[0].get_overriding(as_vec[1], analyzer) - } else { - Ok(*as_vec[0]) + match as_vec.len() { + 2 => { + as_vec[0].get_overriding(as_vec[1], analyzer) + } + 3.. => { + panic!("3+ visible functions with the same name. This is invalid solidity, {as_vec:#?}") + } + _ => Ok(*as_vec[0]) } }) .collect() diff --git a/crates/graph/src/nodes/context/solving.rs b/crates/graph/src/nodes/context/solving.rs index c66aef3d..af834be9 100644 --- a/crates/graph/src/nodes/context/solving.rs +++ b/crates/graph/src/nodes/context/solving.rs @@ -1,30 +1,35 @@ use crate::elem::Elem; + use crate::{ - as_dot_str, - nodes::{ContextNode, ContextVarNode}, - range::{elem::RangeOp, Range, RangeEval}, + nodes::{Concrete, ContextNode, ContextVarNode}, + range::Range, solvers::{ dl::{DLSolver, SolveStatus}, Atomize, SolverAtom, }, - AnalyzerBackend, AsDotStr, GraphBackend, GraphError, Node, + AnalyzerBackend, GraphBackend, GraphError, }; use std::borrow::Cow; -use shared::NodeIdx; - -use petgraph::dot::Dot; +use shared::RangeArena; use std::collections::BTreeMap; impl ContextNode { /// Use a Difference Logic solver to see if it is unreachable - pub fn unreachable(&self, analyzer: &impl GraphBackend) -> Result { + pub fn unreachable( + &self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result { // println!("checking unreachable: {}", self.path(analyzer)); let mut solver = self.dl_solver(analyzer)?.clone(); - match solver.solve_partial(analyzer)? { - SolveStatus::Unsat => Ok(true), - e => { + match solver.solve_partial(analyzer, arena)? { + SolveStatus::Unsat => { + tracing::trace!("{} is unreachable via UNSAT", self.path(analyzer)); + Ok(true) + } + _e => { // println!("other: {e:?}"); Ok(false) } @@ -35,12 +40,13 @@ impl ContextNode { pub fn dep_atoms( &self, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, GraphError> { let deps: Vec<_> = self.ctx_deps(analyzer)?; let mut ranges = BTreeMap::default(); deps.iter().try_for_each(|dep| { let mut range = dep.range(analyzer)?.unwrap(); - let r: Cow<'_, _> = range.flattened_range(analyzer)?; + let r: Cow<'_, _> = range.flattened_range(analyzer, arena)?; ranges.insert(*dep, r.into_owned()); Ok(()) })?; @@ -48,10 +54,10 @@ impl ContextNode { Ok(ranges .iter() .filter_map(|(_dep, range)| { - if let Some(atom) = Elem::Arena(range.min).atomize(analyzer) { + if let Some(atom) = Elem::Arena(range.min).atomize(analyzer, arena) { Some(atom) } else { - Elem::Arena(range.max).atomize(analyzer) + Elem::Arena(range.max).atomize(analyzer, arena) } }) .collect::>()) @@ -79,11 +85,27 @@ impl ContextNode { Ok(deps) } + pub fn debug_ctx_deps( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { + let deps = self.ctx_deps(analyzer)?; + deps.iter().enumerate().for_each(|(i, var)| { + println!( + "{i}. {}", + var.as_controllable_name(analyzer, arena).unwrap() + ) + }); + Ok(()) + } + /// Adds a dependency for this context to exit successfully pub fn add_ctx_dep( &self, dep: ContextVarNode, analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, ) -> Result<(), GraphError> { tracing::trace!( "Adding ctx ({}) dependency: {}, is_controllable: {}", @@ -96,20 +118,31 @@ impl ContextNode { if !self.underlying(analyzer)?.ctx_deps.contains(&dep) { // dep.cache_flattened_range(analyzer)?; let mut range = dep.range(analyzer)?.unwrap(); - let r = range.flattened_range(analyzer)?.into_owned(); + + let min = range.simplified_range_min(analyzer, arena)?; + let max = range.simplified_range_max(analyzer, arena)?; + + let true_elem = Elem::from(true); + let trivial_sat = min == true_elem && max == true_elem; + if trivial_sat || min == Elem::Null || max == Elem::Null { + return Ok(()); + } + + let r = range.flattened_range(analyzer, arena)?.into_owned(); + // add the atomic constraint - if let Some(atom) = Elem::Arena(r.min).atomize(analyzer) { + if let Some(atom) = Elem::Arena(r.min).atomize(analyzer, arena) { let mut solver = std::mem::take(&mut self.underlying_mut(analyzer)?.dl_solver); - let constraints = solver.add_constraints(vec![atom], analyzer); + let constraints = solver.add_constraints(vec![atom], analyzer, arena); constraints .into_iter() .for_each(|(constraint, normalized)| { solver.add_constraint(constraint, normalized); }); self.underlying_mut(analyzer)?.dl_solver = solver; - } else if let Some(atom) = Elem::Arena(r.max).atomize(analyzer) { + } else if let Some(atom) = Elem::Arena(r.max).atomize(analyzer, arena) { let mut solver = std::mem::take(&mut self.underlying_mut(analyzer)?.dl_solver); - let constraints = solver.add_constraints(vec![atom], analyzer); + let constraints = solver.add_constraints(vec![atom], analyzer, arena); constraints .into_iter() .for_each(|(constraint, normalized)| { @@ -124,125 +157,4 @@ impl ContextNode { } Ok(()) } - - /// Creates a DAG of the context dependencies and opens it with graphviz - pub fn deps_dag(&self, g: &impl GraphBackend) -> Result<(), GraphError> { - let deps = self.ctx_deps(g)?; - // #[derive(Debug, Copy, Clone)] - // pub enum DepEdge { - // Lhs, - // Rhs, - // } - - let mut gr: petgraph::Graph = - petgraph::Graph::default(); - - let mut contains: BTreeMap> = - BTreeMap::default(); - deps.iter().try_for_each(|dep| { - let mapping = dep.graph_dependent_on(g)?; - mapping.into_iter().for_each(|(_k, tmp)| { - if let Some(rhs) = tmp.rhs { - let lhs = if let Some(ver) = contains.keys().find(|other| { - other.ref_range(g).unwrap() == tmp.lhs.ref_range(g).unwrap() - && tmp.lhs.display_name(g).unwrap() == other.display_name(g).unwrap() - }) { - *contains.get(ver).unwrap() - } else { - let lhs = gr.add_node(tmp.lhs.into()); - contains.insert(tmp.lhs, lhs); - lhs - }; - - let new_rhs = if let Some(ver) = contains.keys().find(|other| { - other.range(g).unwrap() == rhs.range(g).unwrap() - && rhs.display_name(g).unwrap() == other.display_name(g).unwrap() - }) { - *contains.get(ver).unwrap() - } else { - let new_rhs = gr.add_node(rhs.into()); - contains.insert(rhs, new_rhs); - new_rhs - }; - gr.add_edge(lhs, new_rhs, tmp.op); - } - }); - Ok(()) - })?; - - let mut dot_str = Vec::new(); - let raw_start_str = r##"digraph G { - node [shape=box, style="filled, rounded", color="#565f89", fontcolor="#d5daf0", fontname="Helvetica", fillcolor="#24283b"]; - edge [color="#414868", fontcolor="#c0caf5", fontname="Helvetica"]; - bgcolor="#1a1b26";"##; - dot_str.push(raw_start_str.to_string()); - let nodes_and_edges_str = format!( - "{:?}", - Dot::with_attr_getters( - &gr, - &[ - petgraph::dot::Config::GraphContentOnly, - petgraph::dot::Config::NodeNoLabel, - petgraph::dot::Config::EdgeNoLabel - ], - &|_graph, edge_ref| { - let e = edge_ref.weight(); - format!("label = \"{e:?}\"") - }, - &|_graph, (idx, node_ref)| { - let inner = match g.node(*node_ref) { - Node::ContextVar(cvar) => { - let range_str = if let Some(r) = cvar.ty.ref_range(g).unwrap() { - r.as_dot_str(g) - // format!("[{}, {}]", r.min.eval(self).to_range_string(self).s, r.max.eval(self).to_range_string(self).s) - } else { - "".to_string() - }; - - format!( - "{} -- {} -- range: {}", - cvar.display_name, - cvar.ty.as_string(g).unwrap(), - range_str - ) - } - _ => as_dot_str(idx, g), - }; - format!( - "label = \"{}\", color = \"{}\"", - inner.replace('\"', "\'"), - g.node(*node_ref).dot_str_color() - ) - } - ) - ); - dot_str.push(nodes_and_edges_str); - let raw_end_str = r#"}"#; - dot_str.push(raw_end_str.to_string()); - let dot_str = dot_str.join("\n"); - - println!("{dot_str}"); - use std::env::temp_dir; - use std::fs; - use std::io::Write; - use std::process::Command; - let mut dir = temp_dir(); - let file_name = "dot.dot"; - dir.push(file_name); - - let mut file = fs::File::create(dir.clone()).unwrap(); - file.write_all(dot_str.as_bytes()).unwrap(); - Command::new("dot") - .arg("-Tsvg") - .arg(dir) - .arg("-o") - .arg("dot.svg") - .output() - .expect("failed to execute process"); - Command::new("open") - .arg("dot.svg") - .output() - .expect("failed to execute process"); - Ok(()) - } } diff --git a/crates/graph/src/nodes/context/var/node.rs b/crates/graph/src/nodes/context/var/node.rs index 4fcb102e..8d1df01f 100644 --- a/crates/graph/src/nodes/context/var/node.rs +++ b/crates/graph/src/nodes/context/var/node.rs @@ -1,10 +1,10 @@ use crate::{ - nodes::{ContextNode, ContextVar, TmpConstruction, VarNode}, - range::{elem::RangeElem, range_string::ToRangeString, Range}, + nodes::{Concrete, ContextNode, ContextVar, TmpConstruction, VarNode}, + range::{elem::*, range_string::ToRangeString, Range}, AsDotStr, ContextEdge, Edge, GraphBackend, GraphError, Node, }; -use shared::{NodeIdx, Search, StorageLocation}; +use shared::{NodeIdx, RangeArena, Search, StorageLocation}; use petgraph::{visit::EdgeRef, Direction}; use solang_parser::pt::Loc; @@ -15,19 +15,23 @@ use std::collections::BTreeMap; pub struct ContextVarNode(pub usize); impl AsDotStr for ContextVarNode { - fn as_dot_str(&self, analyzer: &impl GraphBackend) -> String { + fn as_dot_str( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> String { let underlying = self.underlying(analyzer).unwrap(); let range_str = if let Some(r) = underlying.ty.ref_range(analyzer).unwrap() { format!( "[{}, {}]", - r.evaled_range_min(analyzer) + r.evaled_range_min(analyzer, arena) .unwrap() - .to_range_string(false, analyzer) + .to_range_string(false, analyzer, arena) .s, - r.evaled_range_max(analyzer) + r.evaled_range_max(analyzer, arena) .unwrap() - .to_range_string(true, analyzer) + .to_range_string(true, analyzer, arena) .s ) } else { @@ -142,19 +146,22 @@ impl ContextVarNode { Ok(self.underlying(analyzer)?.name.clone()) } - pub fn as_controllable_name(&self, analyzer: &impl GraphBackend) -> Result { - if let Some(ref_range) = self.ref_range(analyzer)? { - let min_name = ref_range - .range_min() - .simplify_minimize(analyzer)? - .to_range_string(false, analyzer) - .s; - + pub fn as_controllable_name( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result { + if self.is_fundamental(analyzer)? { + self.display_name(analyzer) + } else if let Some(ref_range) = self.ref_range(analyzer)? { + let min_name = ref_range.range_min().simplify_minimize(analyzer, arena)?; + let min_name = min_name.to_range_string(false, analyzer, arena).s; let max_name = ref_range .range_max() - .simplify_maximize(analyzer)? - .to_range_string(true, analyzer) + .simplify_maximize(analyzer, arena)? + .to_range_string(true, analyzer, arena) .s; + if max_name == min_name { Ok(max_name) } else { diff --git a/crates/graph/src/nodes/context/var/ranging.rs b/crates/graph/src/nodes/context/var/ranging.rs index 4a8b20e3..8f0fbef1 100644 --- a/crates/graph/src/nodes/context/var/ranging.rs +++ b/crates/graph/src/nodes/context/var/ranging.rs @@ -1,10 +1,11 @@ +use crate::range::elem::*; use crate::{ nodes::{Concrete, ContextVarNode}, range::{range_string::ToRangeString, Range, RangeEval}, AnalyzerBackend, GraphBackend, GraphError, SolcRange, VarType, }; -use crate::range::elem::*; +use shared::RangeArena; use solang_parser::pt::Loc; @@ -13,17 +14,21 @@ impl ContextVarNode { self.underlying(analyzer)?.ty.range(analyzer) } - pub fn range_string(&self, analyzer: &impl GraphBackend) -> Result, GraphError> { + pub fn range_string( + &self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { if let Some(range) = self.ref_range(analyzer)? { Ok(Some(format!( "[ {}, {} ]", range - .evaled_range_min(analyzer)? - .to_range_string(false, analyzer) + .evaled_range_min(analyzer, arena)? + .to_range_string(false, analyzer, arena) .s, range - .evaled_range_max(analyzer)? - .to_range_string(true, analyzer) + .evaled_range_max(analyzer, arena)? + .to_range_string(true, analyzer, arena) .s ))) } else { @@ -33,18 +38,19 @@ impl ContextVarNode { pub fn simplified_range_string( &self, - analyzer: &impl GraphBackend, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, GraphError> { if let Some(range) = self.ref_range(analyzer)? { Ok(Some(format!( "[ {}, {} ]", range - .simplified_range_min(analyzer)? - .to_range_string(false, analyzer) + .simplified_range_min(analyzer, arena)? + .to_range_string(false, analyzer, arena) .s, range - .simplified_range_max(analyzer)? - .to_range_string(true, analyzer) + .simplified_range_max(analyzer, arena)? + .to_range_string(true, analyzer, arena) .s ))) } else { @@ -84,9 +90,10 @@ impl ContextVarNode { pub fn evaled_range_min( &self, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result>, GraphError> { if let Some(r) = self.ref_range(analyzer)? { - Ok(Some(r.evaled_range_min(analyzer)?)) + Ok(Some(r.evaled_range_min(analyzer, arena)?)) } else { Ok(None) } @@ -95,9 +102,10 @@ impl ContextVarNode { pub fn evaled_range_max( &self, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result>, GraphError> { if let Some(r) = self.ref_range(analyzer)? { - Ok(Some(r.evaled_range_max(analyzer)?)) + Ok(Some(r.evaled_range_max(analyzer, arena)?)) } else { Ok(None) } @@ -117,10 +125,14 @@ impl ContextVarNode { } } - pub fn cache_range(&self, analyzer: &mut impl GraphBackend) -> Result<(), GraphError> { + pub fn cache_range( + &self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { if let Some(mut range) = self.ty_mut(analyzer)?.take_range() { // range.cache_flatten(analyzer)?; - range.cache_eval(analyzer)?; + range.cache_eval(analyzer, arena)?; self.set_range(analyzer, range)?; } Ok(()) @@ -129,17 +141,22 @@ impl ContextVarNode { pub fn cache_flattened_range( &self, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) -> Result<(), GraphError> { if let Some(mut range) = self.ty_mut(analyzer)?.take_range() { - range.cache_flatten(analyzer)?; + range.cache_flatten(analyzer, arena)?; self.set_range(analyzer, range)?; } Ok(()) } - pub fn cache_eval_range(&self, analyzer: &mut impl GraphBackend) -> Result<(), GraphError> { + pub fn cache_eval_range( + &self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { if let Some(mut range) = self.ty_mut(analyzer)?.take_range() { - range.cache_eval(analyzer)?; + range.cache_eval(analyzer, arena)?; self.set_range(analyzer, range)?; } Ok(()) @@ -178,9 +195,10 @@ impl ContextVarNode { &self, elem: Elem, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result { if let Some(r) = self.ref_range(analyzer)? { - Ok(r.contains_elem(&elem, analyzer)) + Ok(r.contains_elem(&elem, analyzer, arena)) } else { Ok(false) } @@ -190,36 +208,44 @@ impl ContextVarNode { pub fn set_range_min( &self, analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, mut new_min: Elem, ) -> Result<(), GraphError> { assert!(self.latest_version(analyzer) == *self); - if new_min.recursive_dependent_on(analyzer)?.contains(self) { + if new_min + .recursive_dependent_on(analyzer, arena)? + .contains(self) + { if let Some(prev) = self.previous_or_inherited_version(analyzer) { - new_min.filter_recursion((*self).into(), prev.into(), analyzer); + new_min.filter_recursion((*self).into(), prev.into(), analyzer, arena); } else { return Err(GraphError::UnbreakableRecursion(format!("The variable {}'s range is self-referential and we cannot break the recursion.", self.display_name(analyzer)?))); } } - new_min.arenaize(analyzer)?; + tracing::trace!("new min: {new_min}"); + new_min.arenaize(analyzer, arena)?; // new_min.cache_flatten(analyzer)?; // new_min.cache_minimize(analyzer)?; tracing::trace!( - "setting range minimum: {} (node idx: {}), current:{}, new_min:{}, deps: {:#?}", + "setting range minimum: {} (node idx: {}), current:{}, new_min:{} ({}), deps: {:#?}", self.display_name(analyzer)?, self.0, - self.range_min(analyzer)?.unwrap(), + self.range_min(analyzer) + .unwrap_or_default() + .unwrap_or_default(), + new_min.recurse_dearenaize(analyzer, arena), new_min, - new_min.recursive_dependent_on(analyzer)? + new_min.recursive_dependent_on(analyzer, arena)? ); 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; - self.set_range_min(analyzer, new_min)?; + self.set_range_min(analyzer, arena, new_min)?; } else { let fallback = if self.needs_fallback(analyzer)? { self.fallback_range(analyzer)? @@ -229,7 +255,7 @@ impl ContextVarNode { self.underlying_mut(analyzer)? .set_range_min(new_min, fallback)?; } - self.cache_range(analyzer)?; + self.cache_range(analyzer, arena)?; Ok(()) } @@ -237,16 +263,20 @@ impl ContextVarNode { pub fn set_range_max( &self, analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, mut new_max: Elem, ) -> Result<(), GraphError> { assert!(self.latest_version(analyzer) == *self); - if new_max.recursive_dependent_on(analyzer)?.contains(self) { + if new_max + .recursive_dependent_on(analyzer, arena)? + .contains(self) + { if let Some(prev) = self.previous_or_inherited_version(analyzer) { - new_max.filter_recursion((*self).into(), prev.into(), analyzer); + new_max.filter_recursion((*self).into(), prev.into(), analyzer, arena); } } - new_max.arenaize(analyzer)?; + new_max.arenaize(analyzer, arena)?; tracing::trace!( "setting range maximum: {:?}, {}, current: {}, new: {}", @@ -260,7 +290,7 @@ impl ContextVarNode { let mut new_ty = self.ty(analyzer)?.clone(); new_ty.concrete_to_builtin(analyzer)?; self.underlying_mut(analyzer)?.ty = new_ty; - self.set_range_max(analyzer, new_max)?; + self.set_range_max(analyzer, arena, new_max)?; } else { let fallback = if self.needs_fallback(analyzer)? { self.fallback_range(analyzer)? @@ -272,7 +302,7 @@ impl ContextVarNode { .set_range_max(new_max, fallback)?; } - self.cache_range(analyzer)?; + self.cache_range(analyzer, arena)?; Ok(()) } @@ -305,22 +335,26 @@ impl ContextVarNode { pub fn try_set_range_min( &self, analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, mut new_min: Elem, ) -> Result { assert!(self.latest_version(analyzer) == *self); - if new_min.recursive_dependent_on(analyzer)?.contains(self) { + if new_min + .recursive_dependent_on(analyzer, arena)? + .contains(self) + { if let Some(prev) = self.previous_version(analyzer) { - new_min.filter_recursion((*self).into(), prev.into(), analyzer); + new_min.filter_recursion((*self).into(), prev.into(), analyzer, arena); } } - new_min.arenaize(analyzer)?; + new_min.arenaize(analyzer, arena)?; 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; - self.try_set_range_min(analyzer, new_min) + self.try_set_range_min(analyzer, arena, new_min) } else { let fallback = if self.needs_fallback(analyzer)? { self.fallback_range(analyzer)? @@ -336,22 +370,26 @@ impl ContextVarNode { pub fn try_set_range_max( &self, analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, mut new_max: Elem, ) -> Result { assert!(self.latest_version(analyzer) == *self); - if new_max.recursive_dependent_on(analyzer)?.contains(self) { + if new_max + .recursive_dependent_on(analyzer, arena)? + .contains(self) + { if let Some(prev) = self.previous_version(analyzer) { - new_max.filter_recursion((*self).into(), prev.into(), analyzer); + new_max.filter_recursion((*self).into(), prev.into(), analyzer, arena); } } - new_max.arenaize(analyzer)?; + new_max.arenaize(analyzer, arena)?; 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; - self.try_set_range_max(analyzer, new_max) + self.try_set_range_max(analyzer, arena, new_max) } else { let fallback = if self.needs_fallback(analyzer)? { self.fallback_range(analyzer)? @@ -390,9 +428,13 @@ impl ContextVarNode { .try_set_range_exclusions(new_exclusions, fallback)) } - pub fn range_deps(&self, analyzer: &impl GraphBackend) -> Result, GraphError> { + pub fn range_deps( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { if let Some(range) = self.ref_range(analyzer)? { - Ok(range.dependent_on(analyzer)) + Ok(range.dependent_on(analyzer, arena)) } else { Ok(vec![]) } diff --git a/crates/graph/src/nodes/context/var/typing.rs b/crates/graph/src/nodes/context/var/typing.rs index 5603b8b3..4ca75ccf 100644 --- a/crates/graph/src/nodes/context/var/typing.rs +++ b/crates/graph/src/nodes/context/var/typing.rs @@ -1,11 +1,14 @@ use crate::{ elem::Elem, nodes::{Builtin, Concrete, ContextNode, ContextVarNode}, - range::{elem::RangeElem, RangeEval}, + range::{ + elem::{RangeElem, RangeExpr, RangeOp}, + RangeEval, + }, AnalyzerBackend, ContextEdge, Edge, GraphBackend, GraphError, Node, VarType, }; -use shared::{Search, StorageLocation}; +use shared::{RangeArena, Search, StorageLocation}; use ethers_core::types::{I256, U256}; use petgraph::{visit::EdgeRef, Direction}; @@ -16,6 +19,42 @@ impl ContextVarNode { Ok(&self.underlying(analyzer)?.ty) } + pub fn ty_max_concrete( + &self, + analyzer: &impl GraphBackend, + ) -> Result, GraphError> { + if let Ok(b) = self.underlying(analyzer)?.ty.as_builtin(analyzer) { + if let Some(zero) = b.zero_concrete() { + return Ok(Concrete::max_of_type(&zero)); + } + } + + Ok(None) + } + pub fn ty_min_concrete( + &self, + analyzer: &impl GraphBackend, + ) -> Result, GraphError> { + if let Ok(b) = self.underlying(analyzer)?.ty.as_builtin(analyzer) { + if let Some(zero) = b.zero_concrete() { + return Ok(Concrete::min_of_type(&zero)); + } + } + + Ok(None) + } + + pub fn ty_zero_concrete( + &self, + analyzer: &impl GraphBackend, + ) -> Result, GraphError> { + if let Ok(b) = self.underlying(analyzer)?.ty.as_builtin(analyzer) { + return Ok(b.zero_concrete()); + } + + Ok(None) + } + pub fn ty_eq_ty( &self, other: &VarType, @@ -204,9 +243,13 @@ impl ContextVarNode { }) } - pub fn is_const(&self, analyzer: &impl GraphBackend) -> Result { + pub fn is_const( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result { let underlying = self.underlying(analyzer)?; - underlying.ty.is_const(analyzer) + underlying.ty.is_const(analyzer, arena) } pub fn is_symbolic(&self, analyzer: &impl GraphBackend) -> Result { @@ -312,13 +355,34 @@ impl ContextVarNode { self.ty(analyzer)?.ty_eq(other.ty(analyzer)?, analyzer) } + /// Performs an in-place cast pub fn cast_from( &self, other: &Self, analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, ) -> Result<(), GraphError> { - let to_ty = other.ty(analyzer)?.clone(); - self.cast_from_ty(to_ty, analyzer)?; + let other_ty = other.ty(analyzer)?.clone(); + if other_ty.ty_eq(&self.underlying(analyzer)?.ty, analyzer)? { + return Ok(()); + } + + let min_expr = Elem::Expr(RangeExpr::new( + self.range_min(analyzer)?.expect("Should have a minimum"), + RangeOp::Cast, + Elem::from(*other), + )); + + let max_expr = Elem::Expr(RangeExpr::new( + self.range_max(analyzer)?.expect("Should have a maximum"), + RangeOp::Cast, + Elem::from(*other), + )); + + self.underlying_mut(analyzer)?.ty = other_ty; + + self.set_range_min(analyzer, arena, min_expr)?; + self.set_range_max(analyzer, arena, max_expr)?; Ok(()) } @@ -366,6 +430,7 @@ impl ContextVarNode { &self, to_ty: VarType, analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, ) -> Result<(), GraphError> { let from_ty = self.ty(analyzer)?.clone(); if !from_ty.ty_eq(&to_ty, analyzer)? { @@ -376,8 +441,8 @@ impl ContextVarNode { if let (Some(mut r), Some(r2)) = (self.ty_mut(analyzer)?.take_range(), to_ty.range(analyzer)?) { - r.min.arenaize(analyzer)?; - r.max.arenaize(analyzer)?; + r.min.arenaize(analyzer, arena)?; + r.max.arenaize(analyzer, arena)?; let mut min_expr = r .min @@ -390,11 +455,11 @@ impl ContextVarNode { .cast(r2.min.clone()) .max(r.max.clone().cast(r2.min)); - min_expr.arenaize(analyzer)?; - max_expr.arenaize(analyzer)?; + min_expr.arenaize(analyzer, arena)?; + max_expr.arenaize(analyzer, arena)?; let zero = Elem::from(Concrete::from(U256::zero())); - if r.contains_elem(&zero, analyzer) { + if r.contains_elem(&zero, analyzer, arena) { min_expr = min_expr.min(zero.clone()); max_expr = max_expr.max(zero); } @@ -410,7 +475,7 @@ impl ContextVarNode { let int_min = int.min_concrete().unwrap(); let bit_repr = int_min.bit_representation().unwrap(); let bit_repr = bit_repr.into(); - if r.contains_elem(&bit_repr, analyzer) { + if r.contains_elem(&bit_repr, analyzer, arena) { min_expr = min_expr.min(int_min.clone().into()); max_expr = max_expr.max(int_min.into()); } @@ -420,7 +485,7 @@ impl ContextVarNode { // from ty is int, to ty is uint if let Some(r) = self.ref_range(analyzer)? { let neg1 = Concrete::from(I256::from(-1i32)); - if r.contains_elem(&neg1.clone().into(), analyzer) { + if r.contains_elem(&neg1.clone().into(), analyzer, arena) { max_expr = max_expr.max(neg1.bit_representation().unwrap().into()); } @@ -431,8 +496,8 @@ impl ContextVarNode { } r.min = min_expr; r.max = max_expr; - r.min.arenaize(analyzer)?; - r.max.arenaize(analyzer)?; + r.min.arenaize(analyzer, arena)?; + r.max.arenaize(analyzer, arena)?; self.set_range(analyzer, r)?; } } @@ -466,9 +531,13 @@ impl ContextVarNode { Ok(()) } - pub fn try_increase_size(&self, analyzer: &mut impl AnalyzerBackend) -> Result<(), GraphError> { + pub fn try_increase_size( + &self, + analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { let from_ty = self.ty(analyzer)?.clone(); - self.cast_from_ty(from_ty.max_size(analyzer)?, analyzer)?; + self.cast_from_ty(from_ty.max_size(analyzer)?, analyzer, arena)?; Ok(()) } @@ -480,6 +549,7 @@ impl ContextVarNode { &self, to_ty: &VarType, analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, ) -> Result, Elem)>, GraphError> { if let Some(to_range) = to_ty.range(analyzer)? { let mut min_expr = (*self) @@ -500,7 +570,7 @@ impl ContextVarNode { if let Some(r) = self.ref_range(analyzer)? { let zero = Elem::from(Concrete::from(U256::zero())); - if r.contains_elem(&zero, analyzer) { + if r.contains_elem(&zero, analyzer, arena) { min_expr = min_expr.min(zero.clone()); max_expr = max_expr.max(zero); } @@ -517,7 +587,7 @@ impl ContextVarNode { let int_min = int.min_concrete().unwrap(); let bit_repr = int_min.bit_representation().unwrap(); let bit_repr = bit_repr.into(); - if r.contains_elem(&bit_repr, analyzer) { + if r.contains_elem(&bit_repr, analyzer, arena) { min_expr = min_expr.min(int_min.clone().into()); max_expr = max_expr.max(int_min.into()); } @@ -527,7 +597,7 @@ impl ContextVarNode { // from ty is int, to ty is uint if let Some(r) = self.ref_range(analyzer)? { let neg1 = Concrete::from(I256::from(-1i32)); - if r.contains_elem(&neg1.clone().into(), analyzer) { + if r.contains_elem(&neg1.clone().into(), analyzer, arena) { max_expr = max_expr.max(neg1.bit_representation().unwrap().into()); } } diff --git a/crates/graph/src/nodes/context/var/underlying.rs b/crates/graph/src/nodes/context/var/underlying.rs index ac799221..ff31d6fb 100644 --- a/crates/graph/src/nodes/context/var/underlying.rs +++ b/crates/graph/src/nodes/context/var/underlying.rs @@ -228,7 +228,7 @@ impl ContextVar { pub fn new_from_builtin( loc: Loc, bn_node: BuiltInNode, - analyzer: &impl GraphBackend, + analyzer: &mut impl GraphBackend, ) -> Result { Ok(ContextVar { loc: Some(loc), diff --git a/crates/graph/src/nodes/context/versioning.rs b/crates/graph/src/nodes/context/versioning.rs index 714bc308..0af39443 100644 --- a/crates/graph/src/nodes/context/versioning.rs +++ b/crates/graph/src/nodes/context/versioning.rs @@ -1,3 +1,4 @@ +use crate::nodes::Context; use crate::ContextEdge; use crate::Edge; use crate::{ @@ -299,7 +300,7 @@ impl ContextNode { Some(CallFork::Call(call)) => format!("call {{ {} }}", call.path(analyzer)), None => unreachable!(), }; - Err(GraphError::ChildRedefinition(format!( + Err(GraphError::ChildRedefinition(panic!( "This is a bug. Tried to redefine a child context, parent:\n{}, current child:\n{},\nnew child: Fork({}, {})", self.path(analyzer), child_str, @@ -311,6 +312,64 @@ impl ContextNode { } } + pub fn set_join_forks( + &self, + loc: Loc, + end_worlds: Vec, + analyzer: &mut impl AnalyzerBackend, + ) -> Result, GraphError> { + // if we have 4 worlds we need to represent + // we need to construct a tree like this + // a + // | + // |----------| + // a1 a2 + // | | + // |------| |------| + // a3 a4 a5 a6 + // + // each fork adds 1 world + + let _edges = self.all_edges(analyzer)?; + let mut stack = std::collections::VecDeque::new(); + stack.push_front(*self); + + for _ in 0..end_worlds.len().saturating_sub(1) { + let curr = stack.pop_front().unwrap(); + + let left_ctx = Context::new_subctx( + curr, + None, + loc, + Some("join_left"), + None, + false, + analyzer, + None, + )?; + let left_subctx = ContextNode::from(analyzer.add_node(Node::Context(left_ctx))); + let right_ctx = Context::new_subctx( + curr, + None, + loc, + Some("join_right"), + None, + false, + analyzer, + None, + )?; + let right_subctx = ContextNode::from(analyzer.add_node(Node::Context(right_ctx))); + curr.set_child_fork(left_subctx, right_subctx, analyzer)?; + left_subctx.set_continuation_ctx(analyzer, curr, "join_left")?; + right_subctx.set_continuation_ctx(analyzer, curr, "join_right")?; + + stack.push_back(left_subctx); + stack.push_back(right_subctx); + } + + self.all_edges(analyzer) + } + /// Adds a child to the context pub fn set_child_call( &self, @@ -329,7 +388,7 @@ impl ContextNode { None => unreachable!(), }; tracing::trace!("Error setting child as a call"); - Err(GraphError::ChildRedefinition(format!( + Err(GraphError::ChildRedefinition(panic!( "This is a bug. Tried to redefine a child context, parent: {}, current child: {}, new child: {}", self.path(analyzer), child_str, diff --git a/crates/graph/src/nodes/contract_ty.rs b/crates/graph/src/nodes/contract_ty.rs index 85cdda7b..fc9ebb78 100644 --- a/crates/graph/src/nodes/contract_ty.rs +++ b/crates/graph/src/nodes/contract_ty.rs @@ -1,8 +1,9 @@ use crate::{ - nodes::{FunctionNode, SourceUnitNode, SourceUnitPartNode, StructNode, VarNode}, + nodes::{Concrete, FunctionNode, SourceUnitNode, SourceUnitPartNode, StructNode, VarNode}, + range::elem::Elem, AnalyzerBackend, AsDotStr, Edge, GraphBackend, GraphError, Node, }; -use shared::{NodeIdx, Search}; +use shared::{NodeIdx, RangeArena, Search}; use petgraph::{visit::EdgeRef, Direction}; use solang_parser::pt::{ContractDefinition, ContractTy, Identifier, Loc}; @@ -14,7 +15,11 @@ use std::collections::BTreeMap; pub struct ContractNode(pub usize); impl AsDotStr for ContractNode { - fn as_dot_str(&self, analyzer: &impl GraphBackend) -> String { + fn as_dot_str( + &self, + analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, + ) -> String { let underlying = self.underlying(analyzer).unwrap(); format!( "{} {}", @@ -180,7 +185,7 @@ impl ContractNode { pub fn funcs_mapping( &self, - analyzer: &(impl Search + AnalyzerBackend), + analyzer: &mut (impl Search + AnalyzerBackend), ) -> BTreeMap { analyzer .search_children_depth(self.0.into(), &Edge::Func, 1, 0) diff --git a/crates/graph/src/nodes/enum_ty.rs b/crates/graph/src/nodes/enum_ty.rs index dbd835ff..e02b0cdb 100644 --- a/crates/graph/src/nodes/enum_ty.rs +++ b/crates/graph/src/nodes/enum_ty.rs @@ -1,6 +1,8 @@ -use crate::{nodes::Concrete, AsDotStr, GraphBackend, GraphError, Node, SolcRange}; +use crate::{ + nodes::Concrete, range::elem::Elem, AsDotStr, GraphBackend, GraphError, Node, SolcRange, +}; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; use ethers_core::types::U256; use solang_parser::pt::{EnumDefinition, Identifier, Loc}; @@ -10,7 +12,11 @@ use solang_parser::pt::{EnumDefinition, Identifier, Loc}; pub struct EnumNode(pub usize); impl AsDotStr for EnumNode { - fn as_dot_str(&self, analyzer: &impl GraphBackend) -> String { + fn as_dot_str( + &self, + analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, + ) -> String { let underlying = self.underlying(analyzer).unwrap(); format!( "enum {} {{ {} }}", diff --git a/crates/graph/src/nodes/err_ty.rs b/crates/graph/src/nodes/err_ty.rs index 30007db0..4ecd30e6 100644 --- a/crates/graph/src/nodes/err_ty.rs +++ b/crates/graph/src/nodes/err_ty.rs @@ -1,6 +1,8 @@ -use crate::{AnalyzerBackend, AsDotStr, GraphBackend, GraphError, Node}; +use crate::{ + nodes::Concrete, range::elem::Elem, AnalyzerBackend, AsDotStr, GraphBackend, GraphError, Node, +}; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; use solang_parser::pt::{ErrorDefinition, ErrorParameter, Expression, Identifier, Loc}; #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] @@ -20,7 +22,11 @@ impl ErrorNode { } } impl AsDotStr for ErrorNode { - fn as_dot_str(&self, analyzer: &impl GraphBackend) -> String { + fn as_dot_str( + &self, + analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, + ) -> String { let underlying = self.underlying(analyzer).unwrap(); format!( "error {}", @@ -97,11 +103,12 @@ impl From for Node { impl ErrorParam { pub fn new( analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, param: ErrorParameter, ) -> Self { ErrorParam { loc: param.loc, - ty: analyzer.parse_expr(¶m.ty, None), + ty: analyzer.parse_expr(arena, ¶m.ty, None), name: param.name, } } diff --git a/crates/graph/src/nodes/func_ty.rs b/crates/graph/src/nodes/func_ty.rs index 0fb4f98f..cf60328b 100644 --- a/crates/graph/src/nodes/func_ty.rs +++ b/crates/graph/src/nodes/func_ty.rs @@ -1,10 +1,12 @@ use crate::{ + nodes::Concrete, nodes::{ContextNode, ContractNode, SourceUnitNode, SourceUnitPartNode}, + range::elem::Elem, AnalyzerBackend, AsDotStr, ContextEdge, Edge, GraphBackend, GraphError, Node, SolcRange, VarType, }; -use shared::{NodeIdx, Search, StorageLocation}; +use shared::{NodeIdx, RangeArena, Search, StorageLocation}; use petgraph::{visit::EdgeRef, Direction}; use solang_parser::{ @@ -377,6 +379,7 @@ impl FunctionNode { pub fn set_params_and_ret( &self, analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, ) -> Result<(), GraphError> { let underlying = self.underlying(analyzer)?.clone(); let mut params_strs = vec![]; @@ -386,7 +389,7 @@ impl FunctionNode { .enumerate() .filter_map(|(i, (_loc, input))| { if let Some(input) = input { - let param = FunctionParam::new(analyzer, input, i); + let param = FunctionParam::new(analyzer, arena, input, i); let input_node = analyzer.add_node(param); params_strs.push( FunctionParamNode::from(input_node) @@ -405,7 +408,7 @@ impl FunctionNode { .into_iter() .filter_map(|(_loc, output)| { if let Some(output) = output { - let ret = FunctionReturn::new(analyzer, output); + let ret = FunctionReturn::new(analyzer, arena, output); let output_node = analyzer.add_node(ret); analyzer.add_edge(output_node, *self, Edge::FunctionReturn); Some(output_node.into()) @@ -439,13 +442,34 @@ impl FunctionNode { // // } // } - pub fn returns<'a>(&self, analyzer: &'a impl GraphBackend) -> &'a [FunctionReturnNode] { - self.underlying(analyzer) - .unwrap() - .cache - .returns - .as_ref() - .unwrap() + pub fn returns( + &self, + arena: &mut RangeArena>, + analyzer: &mut impl AnalyzerBackend, + ) -> Vec { + if let Some(cached) = self.underlying(analyzer).unwrap().cache.returns.as_ref() { + cached.to_vec() + } else { + let underlying = self.underlying(analyzer).unwrap().clone(); + let rets = underlying + .returns + .into_iter() + .filter_map(|(_loc, output)| { + if let Some(output) = output { + let ret = FunctionReturn::new(analyzer, arena, output); + let output_node = analyzer.add_node(ret); + analyzer.add_edge(output_node, *self, Edge::FunctionReturn); + Some(output_node.into()) + } else { + None + } + }) + .collect::>(); + + let underlying_mut = self.underlying_mut(analyzer).unwrap(); + underlying_mut.cache.returns = Some(rets.clone()); + rets + } } pub fn is_public_or_ext(&self, analyzer: &impl GraphBackend) -> Result { @@ -512,11 +536,15 @@ impl FunctionNode { } impl AsDotStr for FunctionNode { - fn as_dot_str(&self, analyzer: &impl GraphBackend) -> String { + fn as_dot_str( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> String { let inputs = self .params(analyzer) .iter() - .map(|param_node: &FunctionParamNode| param_node.as_dot_str(analyzer)) + .map(|param_node: &FunctionParamNode| param_node.as_dot_str(analyzer, arena)) .collect::>() .join(", "); @@ -769,12 +797,16 @@ impl From for Function { pub struct FunctionParamNode(pub usize); impl AsDotStr for FunctionParamNode { - fn as_dot_str(&self, analyzer: &impl GraphBackend) -> String { + fn as_dot_str( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> String { let var_ty = VarType::try_from_idx(analyzer, self.underlying(analyzer).unwrap().ty) .expect("Non-typeable as type"); format!( "{}{}{}", - var_ty.as_dot_str(analyzer), + var_ty.as_dot_str(analyzer, arena), if let Some(stor) = &self.underlying(analyzer).unwrap().storage { format!(" {stor} ") } else { @@ -840,7 +872,7 @@ impl FunctionParamNode { let var_ty = VarType::try_from_idx(analyzer, self.underlying(analyzer)?.ty).ok_or( GraphError::NodeConfusion("Non-typeable as type".to_string()), )?; - Ok(var_ty.as_dot_str(analyzer)) + var_ty.as_string(analyzer) } pub fn ty(&self, analyzer: &impl GraphBackend) -> Result { @@ -878,12 +910,13 @@ impl From for Node { impl FunctionParam { pub fn new( analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, param: Parameter, order: usize, ) -> Self { FunctionParam { loc: param.loc, - ty: analyzer.parse_expr(¶m.ty, None), + ty: analyzer.parse_expr(arena, ¶m.ty, None), order, storage: param.storage.map(|s| s.into()), name: param.name, @@ -895,12 +928,16 @@ impl FunctionParam { pub struct FunctionReturnNode(pub usize); impl AsDotStr for FunctionReturnNode { - fn as_dot_str(&self, analyzer: &impl GraphBackend) -> String { + fn as_dot_str( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> String { let var_ty = VarType::try_from_idx(analyzer, self.underlying(analyzer).unwrap().ty) .expect("Non-typeable as type"); format!( "{}{}{}", - var_ty.as_dot_str(analyzer), + var_ty.as_dot_str(analyzer, arena), if let Some(stor) = &self.underlying(analyzer).unwrap().storage { format!(" {stor} ") } else { @@ -976,10 +1013,14 @@ pub struct FunctionReturn { } impl FunctionReturn { - pub fn new(analyzer: &mut impl AnalyzerBackend, param: Parameter) -> Self { + pub fn new( + analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, + param: Parameter, + ) -> Self { FunctionReturn { loc: param.loc, - ty: analyzer.parse_expr(¶m.ty, None), + ty: analyzer.parse_expr(arena, ¶m.ty, None), storage: param.storage.map(|s| s.into()), name: param.name, } diff --git a/crates/graph/src/nodes/msg.rs b/crates/graph/src/nodes/msg.rs index e882503c..c7f71544 100644 --- a/crates/graph/src/nodes/msg.rs +++ b/crates/graph/src/nodes/msg.rs @@ -1,9 +1,10 @@ use crate::{ nodes::{Builtin, Concrete, ContextNode, ContextVar}, + range::elem::Elem, AnalyzerBackend, AsDotStr, GraphBackend, GraphError, Node, }; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; use ethers_core::types::{Address, U256}; use solang_parser::pt::Loc; @@ -27,7 +28,11 @@ impl MsgNode { } impl AsDotStr for MsgNode { - fn as_dot_str(&self, analyzer: &impl GraphBackend) -> String { + fn as_dot_str( + &self, + analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, + ) -> String { format!("msg {{ {:?} }}", self.underlying(analyzer).unwrap()) } } diff --git a/crates/graph/src/nodes/source_unit.rs b/crates/graph/src/nodes/source_unit.rs index aabc2db9..dce79147 100644 --- a/crates/graph/src/nodes/source_unit.rs +++ b/crates/graph/src/nodes/source_unit.rs @@ -1,9 +1,10 @@ use crate::{ - nodes::{ContractNode, FunctionNode, SourceUnitPartNode, StructNode, VarNode}, + nodes::{Concrete, ContractNode, FunctionNode, SourceUnitPartNode, StructNode, VarNode}, + range::elem::Elem, AsDotStr, GraphBackend, GraphError, Node, }; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; #[derive(Default, Clone, Debug, PartialOrd, PartialEq, Ord, Eq)] pub struct SourceUnit { @@ -36,7 +37,11 @@ impl From for SourceUnitNode { } impl AsDotStr for SourceUnitNode { - fn as_dot_str(&self, analyzer: &impl GraphBackend) -> String { + fn as_dot_str( + &self, + analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, + ) -> String { let underlying = self.underlying(analyzer).unwrap(); format!("SourceUnit({})", underlying.file) } diff --git a/crates/graph/src/nodes/source_unit_part.rs b/crates/graph/src/nodes/source_unit_part.rs index 22791787..34c2e183 100644 --- a/crates/graph/src/nodes/source_unit_part.rs +++ b/crates/graph/src/nodes/source_unit_part.rs @@ -1,9 +1,10 @@ use crate::{ - nodes::{ContractNode, FunctionNode, StructNode, VarNode}, + nodes::{Concrete, ContractNode, FunctionNode, StructNode, VarNode}, + range::elem::Elem, AsDotStr, GraphBackend, GraphError, Node, }; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; #[derive(Default, Clone, Debug, PartialOrd, PartialEq, Ord, Eq)] pub struct SourceUnitPart { @@ -41,7 +42,11 @@ impl From for SourceUnitPartNode { } impl AsDotStr for SourceUnitPartNode { - fn as_dot_str(&self, analyzer: &impl GraphBackend) -> String { + fn as_dot_str( + &self, + analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, + ) -> String { let underlying = self.underlying(analyzer).unwrap(); format!("SourceUnitPart({}, {})", underlying.file, underlying.part) } diff --git a/crates/graph/src/nodes/struct_ty.rs b/crates/graph/src/nodes/struct_ty.rs index 4eef093b..18ba87e9 100644 --- a/crates/graph/src/nodes/struct_ty.rs +++ b/crates/graph/src/nodes/struct_ty.rs @@ -1,6 +1,9 @@ -use crate::{AnalyzerBackend, AsDotStr, Edge, GraphBackend, GraphError, Node, VarType}; +use crate::{ + nodes::Concrete, range::elem::Elem, AnalyzerBackend, AsDotStr, Edge, GraphBackend, GraphError, + Node, VarType, +}; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; use petgraph::{visit::EdgeRef, Direction}; use solang_parser::pt::{Expression, Identifier, Loc, StructDefinition, VariableDeclaration}; @@ -64,7 +67,11 @@ impl StructNode { } impl AsDotStr for StructNode { - fn as_dot_str(&self, analyzer: &impl GraphBackend) -> String { + fn as_dot_str( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> String { let underlying = self.underlying(analyzer).unwrap(); format!( "struct {} {{ {} }}", @@ -75,7 +82,7 @@ impl AsDotStr for StructNode { }, self.fields(analyzer) .iter() - .map(|field_node| { field_node.as_dot_str(analyzer) }) + .map(|field_node| { field_node.as_dot_str(analyzer, arena) }) .collect::>() .join("; ") ) @@ -152,12 +159,16 @@ impl FieldNode { } impl AsDotStr for FieldNode { - fn as_dot_str(&self, analyzer: &impl GraphBackend) -> String { + fn as_dot_str( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> String { let underlying = self.underlying(analyzer).unwrap(); format!( "{} {}", if let Some(var_ty) = VarType::try_from_idx(analyzer, underlying.ty) { - var_ty.as_dot_str(analyzer) + var_ty.as_dot_str(analyzer, arena) } else { "".to_string() }, @@ -198,9 +209,10 @@ impl From for Node { impl Field { pub fn new( analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, var_def: VariableDeclaration, ) -> Field { - let ty_idx = analyzer.parse_expr(&var_def.ty, None); + let ty_idx = analyzer.parse_expr(arena, &var_def.ty, None); Field { loc: var_def.loc, ty: ty_idx, diff --git a/crates/graph/src/nodes/ty_ty.rs b/crates/graph/src/nodes/ty_ty.rs index af658539..b3d4e42d 100644 --- a/crates/graph/src/nodes/ty_ty.rs +++ b/crates/graph/src/nodes/ty_ty.rs @@ -1,6 +1,9 @@ -use crate::{AnalyzerBackend, AsDotStr, GraphBackend, GraphError, Node, VarType}; +use crate::{ + nodes::Concrete, range::elem::Elem, AnalyzerBackend, AsDotStr, GraphBackend, GraphError, Node, + VarType, +}; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; use solang_parser::pt::{Expression, Identifier, Loc, TypeDefinition}; @@ -38,12 +41,16 @@ impl From for TyNode { } impl AsDotStr for TyNode { - fn as_dot_str(&self, analyzer: &impl GraphBackend) -> String { + fn as_dot_str( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> String { let underlying = self.underlying(analyzer).unwrap(); format!( "{} {}", if let Some(var_ty) = VarType::try_from_idx(analyzer, underlying.ty) { - var_ty.as_dot_str(analyzer) + var_ty.as_dot_str(analyzer, arena) } else { "".to_string() }, @@ -66,10 +73,14 @@ impl From for Node { } impl Ty { - pub fn new(analyzer: &mut impl AnalyzerBackend, ty: TypeDefinition) -> Ty { + pub fn new( + analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, + ty: TypeDefinition, + ) -> Ty { Ty { loc: ty.loc, - ty: analyzer.parse_expr(&ty.ty, None), + ty: analyzer.parse_expr(arena, &ty.ty, None), name: ty.name, } } diff --git a/crates/graph/src/nodes/var_ty.rs b/crates/graph/src/nodes/var_ty.rs index 4391952c..802315cc 100644 --- a/crates/graph/src/nodes/var_ty.rs +++ b/crates/graph/src/nodes/var_ty.rs @@ -1,9 +1,12 @@ use crate::{ - nodes::{ContextVar, ContextVarNode, ContractNode, SourceUnitNode, SourceUnitPartNode}, + nodes::{ + Concrete, ContextVar, ContextVarNode, ContractNode, SourceUnitNode, SourceUnitPartNode, + }, + range::elem::Elem, AnalyzerBackend, AsDotStr, ContextEdge, Edge, GraphBackend, GraphError, Node, VarType, }; -use shared::{NodeIdx, Search}; +use shared::{NodeIdx, RangeArena, Search}; use petgraph::{visit::EdgeRef, Direction}; use solang_parser::pt::{ @@ -46,11 +49,12 @@ impl VarNode { pub fn parse_initializer( &self, analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, parent: NodeIdx, ) -> Result<(), GraphError> { if let Some(expr) = self.underlying(analyzer)?.initializer_expr.clone() { tracing::trace!("Parsing variable initializer"); - let init = analyzer.parse_expr(&expr, Some(parent)); + let init = analyzer.parse_expr(arena, &expr, Some(parent)); let underlying = self.underlying(analyzer)?.clone(); let mut set = false; if let Some(ty) = VarType::try_from_idx(analyzer, underlying.ty) { @@ -174,12 +178,16 @@ impl VarNode { } impl AsDotStr for VarNode { - fn as_dot_str(&self, analyzer: &impl GraphBackend) -> String { + fn as_dot_str( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> String { let underlying = self.underlying(analyzer).unwrap(); format!( "{}{} {}", if let Some(var_ty) = VarType::try_from_idx(analyzer, underlying.ty) { - var_ty.as_dot_str(analyzer) + var_ty.as_dot_str(analyzer, arena) } else { "".to_string() }, @@ -237,11 +245,12 @@ impl From for Node { impl Var { pub fn new( analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, var: VariableDefinition, in_contract: bool, ) -> Var { tracing::trace!("Parsing Var type"); - let ty = analyzer.parse_expr(&var.ty, None); + let ty = analyzer.parse_expr(arena, &var.ty, None); Var { loc: var.loc, ty, diff --git a/crates/graph/src/range/elem/concrete.rs b/crates/graph/src/range/elem/concrete.rs index 21fa678d..8ee3f789 100644 --- a/crates/graph/src/range/elem/concrete.rs +++ b/crates/graph/src/range/elem/concrete.rs @@ -1,22 +1,54 @@ use crate::{ nodes::{Concrete, ContextVarNode}, - range::elem::{Elem, RangeElem}, + range::elem::{Elem, RangeArenaLike, RangeElem}, GraphBackend, GraphError, }; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; use std::hash::{Hash, Hasher}; +use ethers_core::types::{I256, U256}; use solang_parser::pt::Loc; /// A concrete value for a range element #[derive(Default, Clone, Debug, Ord, PartialOrd)] pub struct RangeConcrete { + /// The value of the concrete pub val: T, + /// The source code location pub loc: Loc, } +pub fn rc_uint_sized(n: u128) -> RangeConcrete { + let size: u16 = ((32 - ((n.leading_zeros() + 128) / 8)) * 8).max(8) as u16; + RangeConcrete::new(Concrete::Uint(size, U256::from(n)), Loc::Implicit) +} + +pub fn rc_uint256(n: u128) -> RangeConcrete { + RangeConcrete::new(Concrete::Uint(256, U256::from(n)), Loc::Implicit) +} + +pub fn rc_int_sized(n: i128) -> RangeConcrete { + let size: u16 = ((32 - ((n.abs().leading_zeros() + 128) / 8)) * 8).max(8) as u16; + RangeConcrete::new(Concrete::Int(size, I256::from(n)), Loc::Implicit) +} + +pub fn rc_i256_sized(n: I256) -> RangeConcrete { + let size: u16 = ((32 - ((n.abs().leading_zeros()) / 8)) * 8).max(8) as u16; + RangeConcrete::new(Concrete::Int(size, n), Loc::Implicit) +} + +pub fn rc_int256(n: i128) -> RangeConcrete { + RangeConcrete::new(Concrete::Int(256, I256::from(n)), Loc::Implicit) +} + +impl RangeConcrete { + pub fn new(val: T, loc: Loc) -> Self { + Self { val, loc } + } +} + impl PartialEq for RangeConcrete { fn eq(&self, other: &Self) -> bool { self.val == other.val @@ -24,7 +56,7 @@ impl PartialEq for RangeConcrete { } impl Eq for RangeConcrete {} -impl Hash for RangeConcrete { +impl Hash for RangeConcrete { fn hash(&self, state: &mut H) { self.val.hash(state); } @@ -40,15 +72,24 @@ impl From for RangeConcrete { } impl RangeConcrete { - pub fn as_bytes(&self, _analyzer: &impl GraphBackend, _maximize: bool) -> Option> { + pub fn as_bytes( + &self, + _analyzer: &impl GraphBackend, + _maximize: bool, + _arena: &mut RangeArena>, + ) -> Option> { Some(self.val.as_bytes()) } } impl RangeElem for RangeConcrete { type GraphError = GraphError; - fn arenaize(&mut self, analyzer: &mut impl GraphBackend) -> Result<(), GraphError> { - let _ = analyzer.range_arena_idx_or_upsert(Elem::Concrete(self.clone())); + fn arenaize( + &mut self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { + let _ = arena.idx_or_upsert(Elem::Concrete(self.clone()), analyzer); Ok(()) } @@ -56,15 +97,17 @@ impl RangeElem for RangeConcrete { &self, _seen: &mut Vec, _analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, ) -> Result { Ok(false) } fn depends_on( &self, - var: ContextVarNode, - seen: &mut Vec, - analyzer: &impl GraphBackend, + _var: ContextVarNode, + _seen: &mut Vec, + _analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, ) -> Result { Ok(false) } @@ -73,23 +116,36 @@ impl RangeElem for RangeConcrete { &self, _maximize: bool, _analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, ) -> Result, GraphError> { Ok(Elem::Concrete(self.clone())) } - fn is_flatten_cached(&self, _analyzer: &impl GraphBackend) -> bool { + fn is_flatten_cached( + &self, + _analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, + ) -> bool { true } - fn is_min_max_cached(&self, _analyzer: &impl GraphBackend) -> (bool, bool) { + fn is_min_max_cached( + &self, + _analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, + ) -> (bool, bool) { (true, true) } - fn cache_flatten(&mut self, _: &mut impl GraphBackend) -> Result<(), GraphError> { + fn cache_flatten( + &mut self, + _: &mut impl GraphBackend, + _arena: &mut RangeArena>, + ) -> Result<(), GraphError> { Ok(()) } - fn range_eq(&self, other: &Self, analyzer: &impl GraphBackend) -> bool { + fn range_eq(&self, other: &Self, arena: &mut RangeArena>) -> bool { match (self.val.into_u256(), other.val.into_u256()) { (Some(self_val), Some(other_val)) => self_val == other_val, _ => match (&self.val, &other.val) { @@ -101,17 +157,11 @@ impl RangeElem for RangeConcrete { (Concrete::Array(a), Concrete::Array(b)) => { if a.len() == b.len() { a.iter().zip(b.iter()).all(|(a, b)| { - let a = RangeConcrete { - val: a.clone(), - loc: self.loc, - }; + let a = RangeConcrete::new(a.clone(), self.loc); - let b = RangeConcrete { - val: b.clone(), - loc: other.loc, - }; + let b = RangeConcrete::new(b.clone(), other.loc); - a.range_eq(&b, analyzer) + a.range_eq(&b, arena) }) } else { false @@ -122,7 +172,11 @@ impl RangeElem for RangeConcrete { } } - fn range_ord(&self, other: &Self, _analyzer: &impl GraphBackend) -> Option { + fn range_ord( + &self, + other: &Self, + _arena: &mut RangeArena>, + ) -> Option { match (self.val.into_u256(), other.val.into_u256()) { (Some(self_val), Some(other_val)) => Some(self_val.cmp(&other_val)), (Some(_), _) => { @@ -156,37 +210,66 @@ impl RangeElem for RangeConcrete { } } - fn dependent_on(&self, _analyzer: &impl GraphBackend) -> Vec { + fn dependent_on( + &self, + _analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, + ) -> Vec { vec![] } - fn filter_recursion(&mut self, _: NodeIdx, _: NodeIdx, _analyzer: &mut impl GraphBackend) {} + fn filter_recursion( + &mut self, + _: NodeIdx, + _: NodeIdx, + _analyzer: &mut impl GraphBackend, + _arena: &mut RangeArena>, + ) { + } - fn maximize(&self, _analyzer: &impl GraphBackend) -> Result, GraphError> { + fn maximize( + &self, + _analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, + ) -> Result, GraphError> { Ok(Elem::Concrete(self.clone())) } - fn minimize(&self, _analyzer: &impl GraphBackend) -> Result, GraphError> { + fn minimize( + &self, + _analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, + ) -> Result, GraphError> { Ok(Elem::Concrete(self.clone())) } fn simplify_maximize( &self, _analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, ) -> Result, GraphError> { Ok(Elem::Concrete(self.clone())) } fn simplify_minimize( &self, _analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, ) -> Result, GraphError> { Ok(Elem::Concrete(self.clone())) } - fn cache_maximize(&mut self, _g: &mut impl GraphBackend) -> Result<(), GraphError> { + fn cache_maximize( + &mut self, + _g: &mut impl GraphBackend, + _arena: &mut RangeArena>, + ) -> Result<(), GraphError> { Ok(()) } - fn cache_minimize(&mut self, _g: &mut impl GraphBackend) -> Result<(), GraphError> { + fn cache_minimize( + &mut self, + _g: &mut impl GraphBackend, + _arena: &mut RangeArena>, + ) -> Result<(), GraphError> { Ok(()) } fn uncache(&mut self) {} @@ -194,6 +277,7 @@ impl RangeElem for RangeConcrete { fn recursive_dependent_on( &self, _: &impl GraphBackend, + _arena: &mut RangeArena>, ) -> Result, GraphError> { Ok(vec![]) } diff --git a/crates/graph/src/range/elem/elem_enum.rs b/crates/graph/src/range/elem/elem_enum.rs deleted file mode 100644 index 3252a4c0..00000000 --- a/crates/graph/src/range/elem/elem_enum.rs +++ /dev/null @@ -1,1391 +0,0 @@ -use crate::elem::MinMaxed; -use crate::{ - nodes::{Concrete, ContextVarNode}, - range::elem::{ - collapse, MaybeCollapsed, RangeConcrete, RangeDyn, RangeElem, RangeExpr, RangeOp, Reference, - }, - GraphBackend, GraphError, -}; -use solang_parser::pt::Loc; -use std::cell::RefCell; -use std::rc::Rc; - -use shared::{NodeIdx, RangeArenaIdx}; - -use ethers_core::types::I256; -use tracing::instrument; - -use std::{ - collections::BTreeMap, - hash::{Hash, Hasher}, - ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Rem, Shl, Shr, Sub}, -}; - -/// A core range element. -#[derive(Default, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] -pub enum Elem { - /// A range element that is a reference to another node - Reference(Reference), - /// A concrete range element of type `T`. e.g.: some number like `10` - ConcreteDyn(RangeDyn), - /// A concrete range element of type `T`. e.g.: some number like `10` - Concrete(RangeConcrete), - /// A range element that is an expression composed of other range elements - Expr(RangeExpr), - /// A range element that is a pointer to another expression in an arena - Arena(RangeArenaIdx), - /// A null range element useful in range expressions that dont have a rhs - #[default] - Null, -} - -impl Hash for Elem { - fn hash(&self, state: &mut H) { - match self { - Elem::Reference(r) => r.hash(state), - Elem::Concrete(c) => c.hash(state), - Elem::Expr(expr) => expr.hash(state), - Elem::ConcreteDyn(d) => d.hash(state), - Elem::Null => (-1i32).hash(state), - Elem::Arena(idx) => idx.hash(state), - } - } -} - -impl Elem { - pub fn node_idx(&self) -> Option { - match self { - Self::Reference(Reference { idx, .. }) => Some(*idx), - _ => None, - } - } - - pub fn concrete(&self) -> Option { - match self { - Self::Concrete(RangeConcrete { val: c, .. }) => Some(c.clone()), - _ => None, - } - } - - pub fn maybe_concrete(&self) -> Option> { - match self { - Elem::Concrete(a) => Some(a.clone()), - _ => None, - } - } - - pub fn maybe_concrete_value(&self) -> Option> { - match self { - Elem::Concrete(a) => Some(a.clone()), - _ => None, - } - } - - pub fn maybe_range_dyn(&self) -> Option> { - match self { - Elem::ConcreteDyn(a) => Some(a.clone()), - _ => None, - } - } - - pub fn is_conc(&self) -> bool { - match self { - Elem::Concrete(_a) => true, - Elem::ConcreteDyn(a) => { - a.len.maybe_concrete().is_some() - && a.val - .iter() - .all(|(key, (val, _))| key.is_conc() && val.is_conc()) - } - Elem::Expr(expr) => expr.lhs.is_conc() && expr.rhs.is_conc(), - _ => false, - } - } -} - -impl Elem { - pub fn contains_node(&self, node_idx: NodeIdx) -> bool { - match self { - Self::Reference(d) => d.idx == node_idx, - Self::Concrete(_) => false, - Self::Expr(expr) => expr.contains_node(node_idx), - Self::ConcreteDyn(d) => d.contains_node(node_idx), - Self::Null => false, - Elem::Arena(_) => todo!(), - } - } - - pub fn expect_into_expr(self) -> RangeExpr { - match self { - Self::Expr(expr) => expr, - _ => panic!("Not expression"), - } - } - - pub fn dyn_map(&self) -> Option<&BTreeMap> { - match self { - Self::ConcreteDyn(dyn_range) => Some(&dyn_range.val), - _ => None, - } - } - - pub fn dyn_map_mut(&mut self) -> Option<&mut BTreeMap> { - match self { - Self::ConcreteDyn(ref mut dyn_range) => Some(&mut dyn_range.val), - _ => None, - } - } - - /// Creates a new range element that is a cast from one type to another - pub fn cast(self, other: Self) -> Self { - let expr = RangeExpr::new(self, RangeOp::Cast, other); - Elem::Expr(expr) - } - - pub fn concat(self, other: Self) -> Self { - let expr = RangeExpr::new(self, RangeOp::Concat, other); - Elem::Expr(expr) - } - - /// Creates a new range element that is the minimum of two range elements - pub fn min(self, other: Self) -> Self { - let expr = RangeExpr::new(self, RangeOp::Min, other); - Elem::Expr(expr) - } - - /// Creates a new range element that is the maximum of two range elements - pub fn max(self, other: Self) -> Self { - let expr = RangeExpr::new(self, RangeOp::Max, other); - Elem::Expr(expr) - } - - /// Creates a new range element that is a boolean of equality of two range elements - pub fn eq(self, other: Self) -> Self { - let expr = RangeExpr::new(self, RangeOp::Eq, other); - Elem::Expr(expr) - } - - /// Creates a new range element that is a boolean of inequality of two range elements - pub fn neq(self, other: Self) -> Self { - let expr = RangeExpr::new(self, RangeOp::Neq, other); - Elem::Expr(expr) - } - - /// Creates a new range element that is one range element to the power of another - pub fn pow(self, other: Self) -> Self { - let expr = RangeExpr::new(self, RangeOp::Exp, other); - Elem::Expr(expr) - } - - /// Creates a new range element that is a memcopy of another - pub fn memcopy(self) -> Self { - let expr = RangeExpr::new(self, RangeOp::Memcopy, Elem::Null); - Elem::Expr(expr) - } - - /// Creates a new range element that applies a setting of indices of a memory object - pub fn set_indices(self, other: RangeDyn) -> Self { - let expr = RangeExpr::new(self, RangeOp::SetIndices, Elem::ConcreteDyn(other)); - Elem::Expr(expr) - } - - /// Creates a new range element that sets the length of a memory object - pub fn set_length(self, other: Self) -> Self { - let expr = RangeExpr::new(self, RangeOp::SetLength, 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); - Elem::Expr(expr) - } - - /// Gets the length of a memory object - pub fn get_index(self, other: Self) -> Self { - let expr = RangeExpr::new(self, RangeOp::GetIndex, other); - Elem::Expr(expr) - } -} - -impl From> for Elem { - fn from(dy: Reference) -> Self { - Elem::Reference(dy) - } -} - -impl From> for Elem { - fn from(c: RangeConcrete) -> Self { - Elem::Concrete(c) - } -} - -impl Add for Elem { - type Output = Self; - - fn add(self, other: Elem) -> Self { - let expr = RangeExpr::new(self, RangeOp::Add(false), other); - Self::Expr(expr) - } -} - -impl Sub for Elem { - type Output = Self; - - fn sub(self, other: Elem) -> Self { - let expr = RangeExpr::new(self, RangeOp::Sub(false), other); - Self::Expr(expr) - } -} - -impl Mul for Elem { - type Output = Self; - - fn mul(self, other: Elem) -> Self { - let expr = RangeExpr::new(self, RangeOp::Mul(false), other); - Self::Expr(expr) - } -} - -impl Div for Elem { - type Output = Self; - - fn div(self, other: Elem) -> Self { - let expr = RangeExpr::new(self, RangeOp::Div(false), other); - Self::Expr(expr) - } -} - -impl Shl for Elem { - type Output = Self; - - fn shl(self, other: Elem) -> Self { - let expr = RangeExpr::new(self, RangeOp::Shl, other); - Self::Expr(expr) - } -} - -impl Shr for Elem { - type Output = Self; - - fn shr(self, other: Elem) -> Self { - let expr = RangeExpr::new(self, RangeOp::Shr, other); - Self::Expr(expr) - } -} - -impl Rem for Elem { - type Output = Self; - - fn rem(self, other: Elem) -> Self { - let expr = RangeExpr::new(self, RangeOp::Mod, other); - Self::Expr(expr) - } -} - -impl BitAnd for Elem { - type Output = Self; - - fn bitand(self, other: Self) -> Self::Output { - let expr = RangeExpr::new(self, RangeOp::BitAnd, other); - Self::Expr(expr) - } -} - -impl BitOr for Elem { - type Output = Self; - - fn bitor(self, other: Self) -> Self::Output { - let expr = RangeExpr::new(self, RangeOp::BitOr, other); - Self::Expr(expr) - } -} - -impl BitXor for Elem { - type Output = Self; - - fn bitxor(self, other: Self) -> Self::Output { - let expr = RangeExpr::new(self, RangeOp::BitXor, other); - Self::Expr(expr) - } -} - -impl From for Elem { - fn from(idx: NodeIdx) -> Self { - Elem::Reference(Reference::new(idx)) - } -} - -impl Elem { - pub fn replace_dep( - &mut self, - to_replace: NodeIdx, - replacement: Self, - analyzer: &mut impl GraphBackend, - ) { - match self { - Elem::Reference(Reference { idx, .. }) => { - if *idx == to_replace { - *self = replacement; - } - } - Elem::Concrete(_) => {} - Elem::Expr(expr) => { - expr.lhs - .replace_dep(to_replace, replacement.clone(), analyzer); - expr.rhs.replace_dep(to_replace, replacement, analyzer); - expr.maximized = None; - expr.minimized = None; - } - Elem::ConcreteDyn(d) => { - d.len.replace_dep(to_replace, replacement.clone(), analyzer); - let vals = std::mem::take(&mut d.val); - d.val = vals - .into_iter() - .map(|(mut k, (mut v, op))| { - k.replace_dep(to_replace, replacement.clone(), analyzer); - v.replace_dep(to_replace, replacement.clone(), analyzer); - (k, (v, op)) - }) - .collect(); - } - Elem::Null => {} - Elem::Arena(_) => { - let mut s = self.dearenaize(analyzer).borrow().clone(); - s.replace_dep(to_replace, replacement, analyzer); - *self = Elem::Arena(analyzer.range_arena_idx_or_upsert(s)); - } - } - } - - pub fn recurse_dearenaize(&self, analyzer: &impl GraphBackend) -> Self { - match self { - Self::Arena(arena_idx) => analyzer.range_arena().ranges[*arena_idx] - .borrow() - .clone() - .recurse_dearenaize(analyzer), - Self::Expr(expr) => expr.recurse_dearenaize(analyzer), - e => e.clone(), - } - } - - pub fn dearenaize(&self, analyzer: &impl GraphBackend) -> Rc> { - match self { - Self::Arena(arena_idx) => analyzer.range_arena().ranges[*arena_idx].clone(), - _ => unreachable!(), - } - } - - pub fn arena_eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Arena(a), Self::Arena(b)) => a == b, - (Self::Concrete(a), Self::Concrete(b)) => a == b, - (Self::ConcreteDyn(a), Self::ConcreteDyn(b)) => { - a.len == b.len - && a.val.len() == b.val.len() - && a.val - .iter() - .zip(b.val.iter()) - .all(|((a, op_a), (b, op_b))| a.arena_eq(b) && op_a == op_b) - } - (Self::Reference(a), Self::Reference(b)) => a == b, - (Self::Expr(a), Self::Expr(b)) => { - a.lhs.arena_eq(&b.lhs) && a.rhs.arena_eq(&b.rhs) && a.op == b.op - } - (Elem::Null, Elem::Null) => true, - _ => false, - } - } - pub fn as_bytes(&self, analyzer: &impl GraphBackend, maximize: bool) -> Option> { - let evaled = if maximize { - self.maximize(analyzer).ok()? - } else { - self.minimize(analyzer).ok()? - }; - - match evaled { - Elem::Concrete(c) => c.as_bytes(analyzer, maximize), - Elem::ConcreteDyn(c) => c.as_bytes(analyzer, maximize), - _ => None, - } - } - - pub fn overlaps( - &self, - other: &Self, - eval: bool, - analyzer: &impl GraphBackend, - ) -> Result, GraphError> { - match (self, other) { - (Elem::Concrete(s), Elem::Concrete(o)) => Ok(Some(o.val == s.val)), - (Elem::Reference(s), Elem::Reference(o)) => { - if eval { - let lhs_min = s.minimize(analyzer)?; - let rhs_max = o.maximize(analyzer)?; - - match lhs_min.range_ord(&rhs_max, analyzer) { - Some(std::cmp::Ordering::Less) => { - // we know our min is less than the other max - // check that the max is greater than or eq their min - let lhs_max = s.maximize(analyzer)?; - let rhs_min = o.minimize(analyzer)?; - Ok(Some(matches!( - lhs_max.range_ord(&rhs_min, analyzer), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) - ))) - } - Some(std::cmp::Ordering::Equal) => Ok(Some(true)), - _ => Ok(Some(false)), - } - } else if s == o { - Ok(Some(true)) - } else { - Ok(None) - } - } - (Elem::Reference(s), c @ Elem::Concrete(_)) => { - if eval { - let lhs_min = s.minimize(analyzer)?; - - match lhs_min.range_ord(c, analyzer) { - Some(std::cmp::Ordering::Less) => { - // we know our min is less than the other max - // check that the max is greater than or eq their min - let lhs_max = s.maximize(analyzer)?; - Ok(Some(matches!( - lhs_max.range_ord(c, analyzer), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) - ))) - } - Some(std::cmp::Ordering::Equal) => Ok(Some(true)), - _ => Ok(Some(false)), - } - } else { - Ok(None) - } - } - (Elem::Concrete(_), Elem::Reference(_)) => other.overlaps(self, eval, analyzer), - _ => Ok(None), - } - } - pub fn overlaps_dual( - &self, - rhs_min: &Self, - rhs_max: &Self, - eval: bool, - analyzer: &impl GraphBackend, - ) -> Result, GraphError> { - match self { - Self::Reference(d) => { - if eval { - let lhs_min = d.minimize(analyzer)?; - let rhs_max = rhs_max.maximize(analyzer)?; - - match lhs_min.range_ord(&rhs_max, analyzer) { - Some(std::cmp::Ordering::Less) => { - // we know our min is less than the other max - // check that the max is greater than or eq their min - let lhs_max = d.maximize(analyzer)?; - let rhs_min = rhs_min.minimize(analyzer)?; - Ok(Some(matches!( - lhs_max.range_ord(&rhs_min, analyzer), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) - ))) - } - Some(std::cmp::Ordering::Equal) => Ok(Some(true)), - _ => Ok(Some(false)), - } - } else if self == rhs_min || self == rhs_max { - Ok(Some(true)) - } else { - Ok(None) - } - } - Self::Concrete(_) => match rhs_min.range_ord(self, analyzer) { - Some(std::cmp::Ordering::Less) => Ok(Some(matches!( - rhs_max.range_ord(self, analyzer), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) - ))), - Some(std::cmp::Ordering::Equal) => Ok(Some(true)), - _ => Ok(Some(false)), - }, - _ => Ok(None), - } - } - pub fn is_negative( - &self, - maximize: bool, - analyzer: &impl GraphBackend, - ) -> Result { - let res = match self { - Elem::Concrete(RangeConcrete { - val: Concrete::Int(_, val), - .. - }) if val < &I256::zero() => true, - Elem::Reference(dy) => { - if maximize { - dy.maximize(analyzer)?.is_negative(maximize, analyzer)? - } else { - dy.minimize(analyzer)?.is_negative(maximize, analyzer)? - } - } - Elem::Expr(expr) => { - if maximize { - expr.maximize(analyzer)?.is_negative(maximize, analyzer)? - } else { - expr.minimize(analyzer)?.is_negative(maximize, analyzer)? - } - } - _ => false, - }; - Ok(res) - } - - pub fn pre_evaled_is_negative(&self) -> bool { - matches!(self, Elem::Concrete(RangeConcrete { val: Concrete::Int(_, val), ..}) if val < &I256::zero()) - } - - pub fn inverse_if_boolean(&self) -> Option { - match self { - Self::Reference(Reference { idx: _, .. }) => Some(Elem::Expr(RangeExpr::new( - self.clone(), - RangeOp::Not, - Elem::Null, - ))), - Self::Concrete(_) => Some(Elem::Expr(RangeExpr::new( - self.clone(), - RangeOp::Not, - Elem::Null, - ))), - Self::Expr(expr) => Some(Elem::Expr(expr.inverse_if_boolean()?)), - Self::ConcreteDyn(_d) => None, - Self::Null => None, - Self::Arena(_) => todo!(), - } - } - - pub fn arenaized_flattened( - &self, - max: bool, - analyzer: &impl GraphBackend, - ) -> Option>> { - if let Some(idx) = analyzer.range_arena_idx(self) { - if let Ok(t) = analyzer.range_arena().ranges[idx].try_borrow() { - match &*t { - Elem::Expr(ref arenaized) => { - if max { - arenaized.flattened_max.clone() - } else { - arenaized.flattened_min.clone() - } - } - Elem::Reference(ref arenaized) => { - if max { - arenaized.flattened_max.clone() - } else { - arenaized.flattened_min.clone() - } - } - Elem::ConcreteDyn(ref arenaized) => { - if max { - arenaized.flattened_max.clone() - } else { - arenaized.flattened_min.clone() - } - } - c @ Elem::Concrete(_) => Some(Box::new(c.clone())), - c @ Elem::Null => Some(Box::new(c.clone())), - a @ Elem::Arena(_) => a.arenaized_flattened(max, analyzer), - } - } else { - None - } - } else { - None - } - } - - pub fn set_arenaized_flattened( - &self, - max: bool, - elem: &Elem, - analyzer: &impl GraphBackend, - ) { - if let Some(idx) = analyzer.range_arena_idx(self) { - if let Ok(mut t) = analyzer.range_arena().ranges[idx].try_borrow_mut() { - match &mut *t { - Elem::Expr(ref mut arenaized) => { - if max { - arenaized.flattened_max = Some(Box::new(elem.clone())); - } else { - arenaized.flattened_min = Some(Box::new(elem.clone())); - } - } - Elem::Reference(ref mut arenaized) => { - if max { - arenaized.flattened_max = Some(Box::new(elem.clone())); - } else { - arenaized.flattened_min = Some(Box::new(elem.clone())); - } - } - Elem::ConcreteDyn(ref mut arenaized) => { - if max { - arenaized.flattened_max = Some(Box::new(elem.clone())); - } else { - arenaized.flattened_min = Some(Box::new(elem.clone())); - } - } - _ => {} - } - } - } - } - - pub fn set_arenaized_cache( - &self, - max: bool, - elem: &Elem, - analyzer: &impl GraphBackend, - ) { - if let Some(idx) = analyzer.range_arena_idx(self) { - if let Ok(mut t) = analyzer.range_arena().ranges[idx].try_borrow_mut() { - match &mut *t { - Elem::Expr(ref mut arenaized) => { - if max { - arenaized.maximized = Some(MinMaxed::Maximized(Box::new(elem.clone()))); - } else { - arenaized.minimized = Some(MinMaxed::Minimized(Box::new(elem.clone()))); - } - } - Elem::Reference(ref mut arenaized) => { - if max { - arenaized.maximized = Some(MinMaxed::Maximized(Box::new(elem.clone()))); - } else { - arenaized.minimized = Some(MinMaxed::Minimized(Box::new(elem.clone()))); - } - } - Elem::ConcreteDyn(ref mut arenaized) => { - if max { - arenaized.maximized = Some(MinMaxed::Maximized(Box::new(elem.clone()))); - } else { - arenaized.minimized = Some(MinMaxed::Minimized(Box::new(elem.clone()))); - } - } - _ => {} - } - } - } - } -} - -impl From for Elem { - fn from(c: Concrete) -> Self { - Elem::Concrete(RangeConcrete { - val: c, - loc: Loc::Implicit, - }) - } -} - -impl From for Elem { - fn from(c: ContextVarNode) -> Self { - Elem::Reference(Reference::new(c.into())) - } -} - -impl std::fmt::Display for Elem { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Elem::Reference(Reference { idx, .. }) => write!(f, "idx_{}", idx.index()), - Elem::ConcreteDyn(d) => { - write!(f, "{{len: {}, values: {{", d.len)?; - d.val - .iter() - .try_for_each(|(key, (val, op))| write!(f, " {key}: ({val}, {op}),"))?; - write!(f, "}}}}") - } - Elem::Concrete(RangeConcrete { val, .. }) => { - write!(f, "{}", val.as_string()) - } - Elem::Expr(RangeExpr { lhs, op, rhs, .. }) => match op { - RangeOp::Min | RangeOp::Max => { - write!(f, "{}{{{}, {}}}", op.to_string(), lhs, rhs) - } - RangeOp::Cast => match &**rhs { - Elem::Concrete(RangeConcrete { val, .. }) => { - write!( - f, - "{}({}, {})", - op.to_string(), - lhs, - val.as_builtin().basic_as_string() - ) - } - _ => write!(f, "{}({}, {})", op.to_string(), lhs, rhs), - }, - RangeOp::BitNot => { - write!(f, "~{}", lhs) - } - _ => write!(f, "({} {} {})", lhs, op.to_string(), rhs), - }, - Elem::Arena(idx) => write!(f, "arena_idx_{idx}"), - Elem::Null => write!(f, ""), - } - } -} - -impl RangeElem for Elem { - type GraphError = GraphError; - - fn arenaize(&mut self, analyzer: &mut impl GraphBackend) -> Result<(), GraphError> { - match self { - Self::Arena(_) => return Ok(()), - Self::Reference(d) => d.arenaize(analyzer)?, - Self::ConcreteDyn(d) => d.arenaize(analyzer)?, - Self::Expr(expr) => { - expr.arenaize(analyzer)?; - } - Self::Concrete(c) => c.arenaize(analyzer)?, - Self::Null => {} - } - - let self_take = std::mem::take(self); - *self = Elem::Arena(analyzer.range_arena_idx_or_upsert(self_take)); - Ok(()) - } - - fn range_eq(&self, other: &Self, analyzer: &impl GraphBackend) -> bool { - match (self, other) { - (Self::Arena(a), Self::Arena(b)) => a == b, - (Self::Concrete(a), Self::Concrete(b)) => a.range_eq(b, analyzer), - (Self::ConcreteDyn(a), Self::ConcreteDyn(b)) => a.range_eq(b, analyzer), - (Self::Reference(a), Self::Reference(b)) => a.idx == b.idx, - _ => false, - } - } - - fn range_ord(&self, other: &Self, analyzer: &impl GraphBackend) -> Option { - match (self, other) { - (Self::Arena(a), Self::Arena(b)) => { - if a == b { - Some(std::cmp::Ordering::Equal) - } else { - self.dearenaize(analyzer) - .borrow() - .range_ord(&other.dearenaize(analyzer).borrow(), analyzer) - } - } - (Self::Concrete(a), Self::Concrete(b)) => a.range_ord(b, analyzer), - (Self::Reference(a), Self::Reference(b)) => a.range_ord(b, analyzer), - (Elem::Null, Elem::Null) => None, - (_a, Elem::Null) => Some(std::cmp::Ordering::Greater), - (Elem::Null, _a) => Some(std::cmp::Ordering::Less), - _ => None, - } - } - - #[instrument(level = "trace", skip_all)] - fn flatten( - &self, - maximize: bool, - analyzer: &impl GraphBackend, - ) -> Result, GraphError> { - match self { - Self::Reference(d) => d.flatten(maximize, analyzer), - Self::Concrete(c) => c.flatten(maximize, analyzer), - Self::Expr(expr) => expr.flatten(maximize, analyzer), - Self::ConcreteDyn(d) => d.flatten(maximize, analyzer), - Self::Null => Ok(Elem::Null), - Self::Arena(_) => { - let de = self.dearenaize(analyzer); - let res = de.borrow().flatten(maximize, analyzer)?; - // match &mut *de.borrow_mut() { - // Self::Reference(ref mut d) => { - // if maximize { - // d.flattened_max = Some(Box::new(res)); - // } else { - // d.flattened_min = Some(Box::new(res)); - // } - // } - // Self::Expr(ref mut expr) => { - // if maximize { - // expr.flattened_max = Some(Box::new(res)); - // } else { - // expr.flattened_min = Some(Box::new(res)); - // } - // } - // Self::ConcreteDyn(ref mut d) => { - // if maximize { - // d.flattened_max = Some(Box::new(res)); - // } else { - // d.flattened_min = Some(Box::new(res)); - // } - // } - // _ => {} - // } - Ok(res) - } - } - } - - #[instrument(level = "trace", skip_all)] - fn cache_flatten(&mut self, analyzer: &mut impl GraphBackend) -> Result<(), GraphError> { - if self.is_flatten_cached(analyzer) { - return Ok(()); - } - - match self { - Self::Reference(d) => d.cache_flatten(analyzer), - Self::Concrete(c) => c.cache_flatten(analyzer), - Self::Expr(expr) => expr.cache_flatten(analyzer), - Self::ConcreteDyn(d) => d.cache_flatten(analyzer), - Self::Null => Ok(()), - Self::Arena(idx) => { - tracing::trace!("flattening for arena idx: {idx}"); - let dearenaized = self.dearenaize(analyzer); - let (min, max) = { - let Ok(t) = dearenaized.try_borrow() else { - return Ok(()); - }; - - let min = t.flatten(false, analyzer)?; - let max = t.flatten(true, analyzer)?; - (min, max) - }; - - match &mut *dearenaized.borrow_mut() { - Self::Reference(ref mut d) => { - d.flattened_min = Some(Box::new(min)); - d.flattened_max = Some(Box::new(max)); - } - Self::Expr(ref mut expr) => { - expr.flattened_min = Some(Box::new(min)); - expr.flattened_max = Some(Box::new(max)); - } - Self::ConcreteDyn(ref mut d) => { - d.flattened_min = Some(Box::new(min)); - d.flattened_max = Some(Box::new(max)); - } - _ => {} - } - // let mut dearenaized = self.dearenaize(analyzer).borrow().clone(); - // dearenaized.cache_flatten(analyzer)?; - // *self.dearenaize(analyzer).borrow_mut() = dearenaized; - Ok(()) - } - } - } - - #[instrument(level = "trace", skip_all)] - fn is_flatten_cached(&self, analyzer: &impl GraphBackend) -> bool { - match self { - Self::Reference(d) => d.is_flatten_cached(analyzer), - Self::Concrete(c) => c.is_flatten_cached(analyzer), - Self::Expr(expr) => expr.is_flatten_cached(analyzer), - Self::ConcreteDyn(d) => d.is_flatten_cached(analyzer), - Self::Null => true, - Self::Arena(_idx) => { - if let Ok(t) = self.dearenaize(analyzer).try_borrow() { - t.is_flatten_cached(analyzer) - } else { - false - } - } - } - } - - fn is_min_max_cached(&self, analyzer: &impl GraphBackend) -> (bool, bool) { - match self { - Self::Reference(d) => d.is_min_max_cached(analyzer), - Self::Concrete(_c) => (true, true), - Self::Expr(expr) => expr.is_min_max_cached(analyzer), - Self::ConcreteDyn(d) => d.is_min_max_cached(analyzer), - Self::Null => (true, true), - Self::Arena(_) => { - if let Ok(t) = self.dearenaize(analyzer).try_borrow() { - t.is_min_max_cached(analyzer) - } else { - (false, false) - } - } - } - } - - fn dependent_on(&self, analyzer: &impl GraphBackend) -> Vec { - match self { - Self::Reference(d) => d.dependent_on(analyzer), - Self::Concrete(_) => vec![], - Self::Expr(expr) => expr.dependent_on(analyzer), - Self::ConcreteDyn(d) => d.dependent_on(analyzer), - Self::Null => vec![], - Self::Arena(_) => { - if let Ok(t) = self.dearenaize(analyzer).try_borrow() { - t.dependent_on(analyzer) - } else { - vec![] - } - } - } - } - - fn recursive_dependent_on( - &self, - analyzer: &impl GraphBackend, - ) -> Result, GraphError> { - match self { - Self::Reference(d) => d.recursive_dependent_on(analyzer), - Self::Concrete(_) => Ok(vec![]), - Self::Expr(expr) => expr.recursive_dependent_on(analyzer), - Self::ConcreteDyn(d) => d.recursive_dependent_on(analyzer), - Self::Null => Ok(vec![]), - Self::Arena(_) => self - .dearenaize(analyzer) - .borrow() - .recursive_dependent_on(analyzer), - } - } - - fn has_cycle( - &self, - seen: &mut Vec, - analyzer: &impl GraphBackend, - ) -> Result { - match self { - Self::Reference(d) => d.has_cycle(seen, analyzer), - Self::Concrete(_) => Ok(false), - Self::Expr(expr) => expr.has_cycle(seen, analyzer), - Self::ConcreteDyn(d) => d.has_cycle(seen, analyzer), - Self::Null => Ok(false), - Self::Arena(_) => self.dearenaize(analyzer).borrow().has_cycle(seen, analyzer), - } - } - - fn depends_on( - &self, - var: ContextVarNode, - seen: &mut Vec, - analyzer: &impl GraphBackend, - ) -> Result { - match self { - Self::Reference(d) => d.depends_on(var, seen, analyzer), - Self::Concrete(_) => Ok(false), - Self::Expr(expr) => expr.depends_on(var, seen, analyzer), - Self::ConcreteDyn(d) => d.depends_on(var, seen, analyzer), - Self::Null => Ok(false), - Self::Arena(_) => self - .dearenaize(analyzer) - .borrow() - .depends_on(var, seen, analyzer), - } - } - - fn filter_recursion( - &mut self, - node_idx: NodeIdx, - new_idx: NodeIdx, - analyzer: &mut impl GraphBackend, - ) { - match self { - Self::Reference(ref mut d) => { - if d.idx == node_idx { - d.idx = new_idx - } - } - Self::Concrete(_) => {} - Self::Expr(expr) => expr.filter_recursion(node_idx, new_idx, analyzer), - Self::ConcreteDyn(d) => d.filter_recursion(node_idx, new_idx, analyzer), - Self::Null => {} - Self::Arena(_idx) => { - let dearenaized = self.dearenaize(analyzer); - dearenaized - .borrow_mut() - .filter_recursion(node_idx, new_idx, analyzer); - } - } - } - - fn maximize(&self, analyzer: &impl GraphBackend) -> Result, GraphError> { - if let Some(idx) = analyzer.range_arena_idx(self) { - let (_min, max) = Elem::Arena(idx).is_min_max_cached(analyzer); - if max { - tracing::trace!("maximize cache hit"); - match &*analyzer.range_arena().ranges[idx].borrow() { - Reference(dy) => return dy.maximize(analyzer), - Concrete(inner) => return inner.maximize(analyzer), - ConcreteDyn(inner) => return inner.maximize(analyzer), - Expr(expr) => return expr.maximize(analyzer), - Null => return Ok(Elem::Null), - _ => {} - } - } - } - - use Elem::*; - let res = match self { - Reference(dy) => dy.maximize(analyzer)?, - Concrete(inner) => inner.maximize(analyzer)?, - ConcreteDyn(inner) => inner.maximize(analyzer)?, - Expr(expr) => expr.maximize(analyzer)?, - Null => Elem::Null, - Arena(_) => { - let dearenaized = self.dearenaize(analyzer); - let res = { - let Ok(t) = dearenaized.try_borrow() else { - return Ok(self.clone()); - }; - t.maximize(analyzer)? - }; - - match &mut *dearenaized.borrow_mut() { - Self::Reference(ref mut d) => { - tracing::trace!("maximize cache MISS: {self}"); - d.maximized = Some(MinMaxed::Maximized(Box::new(res.clone()))); - } - Self::Expr(ref mut expr) => { - tracing::trace!("maximize cache MISS: {self}"); - expr.maximized = Some(MinMaxed::Maximized(Box::new(res.clone()))); - } - Self::ConcreteDyn(ref mut d) => { - tracing::trace!("maximize cache MISS: {self}"); - d.maximized = Some(MinMaxed::Maximized(Box::new(res.clone()))); - } - _ => {} - } - - let (_min, max) = self.is_min_max_cached(analyzer); - assert!(max, "????"); - - res - } - }; - Ok(res) - } - - fn minimize(&self, analyzer: &impl GraphBackend) -> Result, GraphError> { - use Elem::*; - - if let Some(idx) = analyzer.range_arena_idx(self) { - let (min, _max) = Elem::Arena(idx).is_min_max_cached(analyzer); - if min { - tracing::trace!("minimize cache hit"); - match &*analyzer.range_arena().ranges[idx].borrow() { - Reference(dy) => return dy.minimize(analyzer), - Concrete(inner) => return inner.minimize(analyzer), - ConcreteDyn(inner) => return inner.minimize(analyzer), - Expr(expr) => return expr.minimize(analyzer), - Null => return Ok(Elem::Null), - _ => {} - } - } - } - - let res = match self { - Reference(dy) => dy.minimize(analyzer)?, - Concrete(inner) => inner.minimize(analyzer)?, - ConcreteDyn(inner) => inner.minimize(analyzer)?, - Expr(expr) => expr.minimize(analyzer)?, - Null => Elem::Null, - Arena(_) => { - let dearenaized = self.dearenaize(analyzer); - let res = { - let Ok(t) = dearenaized.try_borrow() else { - return Ok(self.clone()); - }; - t.minimize(analyzer)? - }; - - match &mut *dearenaized.borrow_mut() { - Self::Reference(ref mut d) => { - tracing::trace!("minimize cache MISS: {self}"); - d.minimized = Some(MinMaxed::Minimized(Box::new(res.clone()))); - } - Self::Expr(ref mut expr) => { - tracing::trace!("minimize cache MISS: {self}"); - expr.minimized = Some(MinMaxed::Minimized(Box::new(res.clone()))); - } - Self::ConcreteDyn(ref mut d) => { - tracing::trace!("minimize cache MISS: {self}"); - d.minimized = Some(MinMaxed::Minimized(Box::new(res.clone()))); - } - _ => {} - } - - let (min, _max) = self.is_min_max_cached(analyzer); - assert!(min, "????"); - res - } - }; - Ok(res) - } - - fn simplify_maximize( - &self, - analyzer: &impl GraphBackend, - ) -> Result, GraphError> { - use Elem::*; - - if let Some(idx) = analyzer.range_arena_idx(self) { - match &*analyzer.range_arena().ranges[idx].borrow() { - Reference(dy) => { - if let Some(max) = &dy.flattened_max { - return Ok(*max.clone()); - } - } - c @ Concrete(_) => return Ok(c.clone()), - ConcreteDyn(inner) => { - if let Some(max) = &inner.flattened_max { - return Ok(*max.clone()); - } - } - Expr(expr) => { - if let Some(max) = &expr.flattened_max { - return Ok(*max.clone()); - } - } - Null => return Ok(Elem::Null), - _ => {} - } - } - - match self { - Reference(dy) => dy.simplify_maximize(analyzer), - Concrete(inner) => inner.simplify_maximize(analyzer), - ConcreteDyn(inner) => inner.simplify_maximize(analyzer), - Expr(expr) => match collapse(&expr.lhs, expr.op, &expr.rhs, analyzer) { - MaybeCollapsed::Collapsed(collapsed) => { - let res = collapsed.simplify_maximize(analyzer)?; - collapsed.set_arenaized_flattened(true, &res, analyzer); - Ok(res) - } - _ => { - let res = expr.simplify_maximize(analyzer)?; - expr.set_arenaized_flattened(true, res.clone(), analyzer); - Ok(res) - } - }, - Null => Ok(Elem::Null), - Arena(_) => { - let dearenaized = self.dearenaize(analyzer); - let flat = dearenaized.borrow().flatten(true, analyzer)?; - let max = flat.simplify_maximize(analyzer)?; - // let min = flat.simplify_minimize(analyzer)?; - match &mut *dearenaized.borrow_mut() { - Self::Reference(ref mut d) => { - tracing::trace!("simplify maximize cache MISS: {self}"); - d.flattened_max = Some(Box::new(max.clone())); - // d.flattened_min = Some(Box::new(min.clone())); - } - Self::Expr(ref mut expr) => { - tracing::trace!("simplify maximize cache MISS: {self}"); - expr.flattened_max = Some(Box::new(max.clone())); - // expr.flattened_min = Some(Box::new(min.clone())); - } - Self::ConcreteDyn(ref mut d) => { - tracing::trace!("simplify maximize cache MISS: {self}"); - d.flattened_max = Some(Box::new(max.clone())); - // d.flattened_min = Some(Box::new(min.clone())); - } - _ => {} - } - - // assert!(self.is_flatten_cached(analyzer), "????"); - Ok(max) - } - } - } - - fn simplify_minimize( - &self, - analyzer: &impl GraphBackend, - ) -> Result, GraphError> { - use Elem::*; - - if let Some(idx) = analyzer.range_arena_idx(self) { - match &*analyzer.range_arena().ranges[idx].borrow() { - Reference(dy) => { - if let Some(min) = &dy.flattened_min { - return Ok(*min.clone()); - } - } - c @ Concrete(_) => return Ok(c.clone()), - ConcreteDyn(inner) => { - if let Some(min) = &inner.flattened_min { - return Ok(*min.clone()); - } - } - Expr(expr) => { - if let Some(min) = &expr.flattened_min { - return Ok(*min.clone()); - } - } - Null => return Ok(Elem::Null), - _ => {} - } - } - - let res = match self { - Reference(dy) => dy.simplify_minimize(analyzer), - Concrete(inner) => inner.simplify_minimize(analyzer), - ConcreteDyn(inner) => inner.simplify_minimize(analyzer), - Expr(expr) => match collapse(&expr.lhs, expr.op, &expr.rhs, analyzer) { - MaybeCollapsed::Collapsed(collapsed) => { - let res = collapsed.simplify_minimize(analyzer)?; - collapsed.set_arenaized_flattened(false, &res, analyzer); - Ok(res) - } - _ => { - let res = expr.simplify_minimize(analyzer)?; - expr.set_arenaized_flattened(false, res.clone(), analyzer); - Ok(res) - } - }, - Null => Ok(Elem::Null), - Arena(_) => { - let dearenaized = self.dearenaize(analyzer); - let flat = dearenaized.borrow().flatten(false, analyzer)?; - let min = flat.simplify_minimize(analyzer)?; - match &mut *dearenaized.borrow_mut() { - Self::Reference(ref mut d) => { - tracing::trace!("simplify minimize cache MISS: {self}"); - d.flattened_min = Some(Box::new(min.clone())); - } - Self::Expr(ref mut expr) => { - tracing::trace!("simplify minimize cache MISS: {self}"); - expr.flattened_min = Some(Box::new(min.clone())); - } - Self::ConcreteDyn(ref mut d) => { - tracing::trace!("simplify minimize cache MISS: {self}"); - d.flattened_min = Some(Box::new(min.clone())); - } - _ => {} - } - - Ok(min) - } - }?; - - Ok(res) - } - - fn cache_maximize(&mut self, analyzer: &mut impl GraphBackend) -> Result<(), GraphError> { - use Elem::*; - match self { - Reference(dy) => dy.cache_maximize(analyzer), - Concrete(inner) => inner.cache_maximize(analyzer), - ConcreteDyn(inner) => inner.cache_maximize(analyzer), - Expr(expr) => match collapse(&expr.lhs, expr.op, &expr.rhs, analyzer) { - MaybeCollapsed::Collapsed(mut collapsed) => { - collapsed.cache_maximize(analyzer)?; - let max = collapsed.maximize(analyzer)?; - self.set_arenaized_flattened(true, &max, analyzer); - *self = collapsed; - Ok(()) - } - _ => { - expr.cache_maximize(analyzer)?; - let max = expr.maximize(analyzer)?; - self.set_arenaized_flattened(true, &max, analyzer); - Ok(()) - } - }, - Null => Ok(()), - Arena(_idx) => { - let dearenaized = self.dearenaize(analyzer); - if let Ok(mut t) = dearenaized.try_borrow_mut() { - t.cache_maximize(analyzer)?; - } - - Ok(()) - } - } - } - - fn cache_minimize(&mut self, analyzer: &mut impl GraphBackend) -> Result<(), GraphError> { - use Elem::*; - match self { - Reference(dy) => dy.cache_minimize(analyzer), - Concrete(inner) => inner.cache_minimize(analyzer), - ConcreteDyn(inner) => inner.cache_minimize(analyzer), - Expr(expr) => match collapse(&expr.lhs, expr.op, &expr.rhs, analyzer) { - MaybeCollapsed::Collapsed(mut collapsed) => { - collapsed.cache_minimize(analyzer)?; - let min = collapsed.minimize(analyzer)?; - self.set_arenaized_flattened(false, &min, analyzer); - *self = collapsed; - Ok(()) - } - _ => { - expr.cache_minimize(analyzer)?; - let min = expr.minimize(analyzer)?; - self.set_arenaized_flattened(false, &min, analyzer); - Ok(()) - } - }, - Null => Ok(()), - Arena(_idx) => { - let dearenaized = self.dearenaize(analyzer); - if let Ok(mut t) = dearenaized.try_borrow_mut() { - t.cache_minimize(analyzer)?; - } - - Ok(()) - } - } - } - fn uncache(&mut self) { - use Elem::*; - match self { - Reference(dy) => dy.uncache(), - Concrete(inner) => inner.uncache(), - ConcreteDyn(inner) => inner.uncache(), - Expr(expr) => expr.uncache(), - Null => {} - Arena(_idx) => {} - } - } -} - -impl Elem { - pub fn wrapping_add(self, other: Elem) -> Self { - let expr = RangeExpr::new(self, RangeOp::Add(true), other); - Self::Expr(expr) - } - pub fn wrapping_sub(self, other: Elem) -> Self { - let expr = RangeExpr::new(self, RangeOp::Sub(true), other); - Self::Expr(expr) - } - pub fn wrapping_mul(self, other: Elem) -> Self { - let expr = RangeExpr::new(self, RangeOp::Mul(true), other); - Self::Expr(expr) - } - pub fn wrapping_div(self, other: Elem) -> Self { - let expr = RangeExpr::new(self, RangeOp::Div(true), other); - Self::Expr(expr) - } - - /// Creates a logical AND of two range elements - pub fn and(self, other: Self) -> Self { - let expr = RangeExpr::::new(self, RangeOp::And, other); - Self::Expr(expr) - } - - /// Creates a logical OR of two range elements - pub fn or(self, other: Self) -> Self { - let expr = RangeExpr::::new(self, RangeOp::Or, other); - Self::Expr(expr) - } - - pub fn maybe_elem_min(&self) -> Option { - match self { - Elem::Concrete(RangeConcrete { val, .. }) => Some(Elem::from(Concrete::min(val)?)), - _ => None, - } - } - - pub fn maybe_elem_max(&self) -> Option { - match self { - Elem::Concrete(RangeConcrete { val, .. }) => Some(Elem::from(Concrete::max(val)?)), - _ => None, - } - } -} diff --git a/crates/graph/src/range/elem/elem_enum/arena.rs b/crates/graph/src/range/elem/elem_enum/arena.rs new file mode 100644 index 00000000..49582f96 --- /dev/null +++ b/crates/graph/src/range/elem/elem_enum/arena.rs @@ -0,0 +1,216 @@ +use crate::GraphBackend; +use crate::{ + nodes::Concrete, + range::elem::{Elem, RangeElem}, +}; +use shared::RangeArena; + +pub trait RangeArenaLike { + fn debug_str(&self, analyzer: &impl GraphBackend) -> String; + fn ranges(&self) -> &Vec; + fn ranges_mut(&mut self) -> &mut Vec; + fn idx_or_upsert(&mut self, elem: T, analyzer: &impl GraphBackend) -> usize; + fn take_nonnull(&mut self, idx: usize) -> Option; + fn idx(&self, elem: &T) -> Option; + fn to_graph( + &mut self, + analyzer: &impl GraphBackend, + ) -> Result, usize, petgraph::Directed, usize>, crate::GraphError>; +} + +impl RangeArenaLike> for RangeArena> { + fn debug_str(&self, analyzer: &impl GraphBackend) -> String { + self.ranges + .iter() + .enumerate() + .map(|(i, elem)| { + fn fmt(elem: &Elem, analyzer: &impl GraphBackend) -> String { + match elem { + Elem::Reference(reference) => { + format!( + "node_{} -- {}", + reference.idx.index(), + crate::nodes::ContextVarNode::from(reference.idx) + .display_name(analyzer) + .unwrap() + ) + } + Elem::Expr(expr) => { + format!( + "{} {} {}", + fmt(&expr.lhs, analyzer), + expr.op.to_string(), + fmt(&expr.rhs, analyzer) + ) + } + _ => format!("{elem}"), + } + }; + + format!("{i}: {}", fmt(elem, analyzer)) + }) + .collect::>() + .join("\n\t") + } + + fn to_graph( + &mut self, + analyzer: &impl GraphBackend, + ) -> Result, usize, petgraph::Directed, usize>, crate::GraphError> + { + let mut graph = petgraph::Graph::default(); + let mut added = vec![]; + let mut ids = vec![]; + + fn get_children( + elem: &Elem, + analyzer: &impl GraphBackend, + ) -> Result>, crate::GraphError> { + match elem { + Elem::Reference(r) => { + let cvar = crate::nodes::ContextVarNode::from(r.idx); + let range = cvar.ref_range(analyzer)?.unwrap(); + let min = range.min.clone(); + let max = range.max.clone(); + Ok(vec![min, max]) + } + _c @ Elem::Concrete(_) => Ok(vec![]), + Elem::ConcreteDyn(d) => { + let mut v = vec![(*d.len).clone()]; + v.extend(d.val.values().map(|(v, _)| v.clone()).collect::>()); + v.extend(d.val.keys().cloned().collect::>()); + Ok(v) + } + Elem::Expr(expr) => Ok(vec![(*expr.lhs).clone(), (*expr.rhs).clone()]), + Elem::Null => Ok(vec![]), + Elem::Arena(_) => Ok(vec![]), + } + } + + fn add_elem_and_children( + graph: &mut petgraph::Graph, usize, petgraph::Directed, usize>, + added: &mut Vec>, + ids: &mut Vec, + elem: &Elem, + analyzer: &impl GraphBackend, + ) -> Result<(), crate::GraphError> { + assert!(added.len() == ids.len()); + + if !added.contains(elem) { + let new_elems: Vec> = get_children(elem, analyzer)?; + let id = graph.add_node(elem.clone()); + added.push(elem.clone()); + ids.push(id.index()); + + new_elems.into_iter().try_for_each(|elem| { + add_elem_and_children(graph, added, ids, &elem, analyzer)?; + let to_id = added.iter().position(|i| i == &elem).unwrap(); + graph.add_edge(id, to_id.into(), 0); + Ok(()) + })?; + } + + Ok(()) + } + + self.ranges.iter().try_for_each(|elem: &Elem| { + add_elem_and_children(&mut graph, &mut added, &mut ids, elem, analyzer) + })?; + Ok(graph) + } + + fn idx_or_upsert(&mut self, elem: Elem, analyzer: &impl GraphBackend) -> usize { + if self.ranges.is_empty() { + self.ranges.push(Elem::Null); + self.map.insert(Elem::Null, 0); + } + + let nulls = self.ranges.iter().fold(0, |mut acc, e| { + if matches!(e, Elem::Null) { + acc += 1; + } + acc + }); + + // println!( + // "{}\nhad cycle:\n{:?}", + // self.debug_str(analyzer), + // petgraph::dot::Dot::new(&self.to_graph(analyzer).unwrap()) // petgraph::algo::toposort(&self.to_graph(analyzer).unwrap(), None).is_err() + // ); + match elem { + Elem::Arena(idx) => return idx, + Elem::Null => return 0, + _ => {} + } + + if let Some(idx) = self.idx(&elem) { + let Some(existing) = self.take_nonnull(idx) else { + self.ranges_mut()[idx] = elem; + return idx; + }; + + let (min_cached, max_cached) = existing.is_min_max_cached(analyzer, self); + let mut existing_count = 0; + if min_cached { + existing_count += 1; + } + if max_cached { + existing_count += 1; + } + if existing.is_flatten_cached(analyzer, self) { + existing_count += 1; + } + + let (min_cached, max_cached) = elem.is_min_max_cached(analyzer, self); + let mut new_count = 0; + if min_cached { + new_count += 1; + } + if max_cached { + new_count += 1; + } + if elem.is_flatten_cached(analyzer, self) { + new_count += 1; + } + + if new_count >= existing_count { + self.ranges_mut()[idx] = elem; + } else { + self.ranges_mut()[idx] = existing; + } + + idx + } else { + let idx = self.ranges.len(); + self.ranges.push(elem.clone()); + self.map.insert(elem, idx); + idx + } + } + + fn ranges(&self) -> &Vec> { + &self.ranges + } + fn ranges_mut(&mut self) -> &mut Vec> { + &mut self.ranges + } + + fn take_nonnull(&mut self, idx: usize) -> Option> { + if let Some(t) = self.ranges.get_mut(idx) { + match t { + Elem::Null => None, + _ => Some(std::mem::take(t)), + } + } else { + None + } + } + + fn idx(&self, elem: &Elem) -> Option { + if let Elem::Arena(idx) = elem { + Some(*idx) + } else { + self.map.get(elem).copied() + } + } +} diff --git a/crates/graph/src/range/elem/elem_enum/impls.rs b/crates/graph/src/range/elem/elem_enum/impls.rs new file mode 100644 index 00000000..f9aa87d8 --- /dev/null +++ b/crates/graph/src/range/elem/elem_enum/impls.rs @@ -0,0 +1,686 @@ +use crate::elem::{MinMaxed, RangeArenaLike}; +use crate::{ + nodes::Concrete, + range::elem::{Elem, RangeConcrete, RangeDyn, RangeElem, RangeExpr, RangeOp, Reference}, + GraphBackend, GraphError, +}; +use shared::{NodeIdx, RangeArena}; + +use ethers_core::types::I256; + +use std::collections::BTreeMap; + +impl Elem { + pub fn wrapping_add(self, other: Elem) -> Self { + let expr = RangeExpr::new(self, RangeOp::Add(true), other); + Self::Expr(expr) + } + pub fn wrapping_sub(self, other: Elem) -> Self { + let expr = RangeExpr::new(self, RangeOp::Sub(true), other); + Self::Expr(expr) + } + pub fn wrapping_mul(self, other: Elem) -> Self { + let expr = RangeExpr::new(self, RangeOp::Mul(true), other); + Self::Expr(expr) + } + pub fn wrapping_div(self, other: Elem) -> Self { + let expr = RangeExpr::new(self, RangeOp::Div(true), other); + Self::Expr(expr) + } + + /// Creates a logical AND of two range elements + pub fn and(self, other: Self) -> Self { + let expr = RangeExpr::::new(self, RangeOp::And, other); + Self::Expr(expr) + } + + /// Creates a logical OR of two range elements + pub fn or(self, other: Self) -> Self { + let expr = RangeExpr::::new(self, RangeOp::Or, other); + Self::Expr(expr) + } + + pub fn maybe_elem_min(&self) -> Option { + match self { + Elem::Concrete(RangeConcrete { val, .. }) => { + Some(Elem::from(Concrete::min_of_type(val)?)) + } + _ => None, + } + } + + pub fn maybe_elem_max(&self) -> Option { + match self { + Elem::Concrete(RangeConcrete { val, .. }) => { + Some(Elem::from(Concrete::max_of_type(val)?)) + } + _ => None, + } + } +} + +impl Elem { + pub fn node_idx(&self) -> Option { + match self { + Self::Reference(Reference { idx, .. }) => Some(*idx), + _ => None, + } + } + + pub fn concrete(&self) -> Option { + match self { + Self::Concrete(RangeConcrete { val: c, .. }) => Some(c.clone()), + _ => None, + } + } + + pub fn maybe_concrete(&self) -> Option> { + match self { + Elem::Concrete(a) => Some(a.clone()), + _ => None, + } + } + + pub fn maybe_concrete_value(&self) -> Option> { + match self { + Elem::Concrete(a) => Some(a.clone()), + _ => None, + } + } + + pub fn maybe_range_dyn(&self) -> Option> { + match self { + Elem::ConcreteDyn(a) => Some(a.clone()), + _ => None, + } + } + + pub fn is_conc(&self) -> bool { + match self { + Elem::Concrete(_a) => true, + Elem::ConcreteDyn(a) => { + a.len.maybe_concrete().is_some() + && a.val + .iter() + .all(|(key, (val, _))| key.is_conc() && val.is_conc()) + } + Elem::Expr(expr) => expr.lhs.is_conc() && expr.rhs.is_conc(), + _ => false, + } + } +} + +impl Elem { + pub fn assert_nonnull(&self) { + match self { + Elem::Expr(expr) => { + expr.lhs.assert_nonnull(); + expr.rhs.assert_nonnull(); + } + Elem::Null => panic!("was null"), + _ => {} + } + } + + pub fn contains_node(&self, node_idx: NodeIdx) -> bool { + match self { + Self::Reference(d) => d.idx == node_idx, + Self::Concrete(_) => false, + Self::Expr(expr) => expr.contains_node(node_idx), + Self::ConcreteDyn(d) => d.contains_node(node_idx), + Self::Null => false, + Elem::Arena(_) => todo!(), + } + } + + pub fn expect_into_expr(self) -> RangeExpr { + match self { + Self::Expr(expr) => expr, + _ => panic!("Not expression"), + } + } + + pub fn dyn_map(&self) -> Option<&BTreeMap> { + match self { + Self::ConcreteDyn(dyn_range) => Some(&dyn_range.val), + _ => None, + } + } + + pub fn dyn_map_mut(&mut self) -> Option<&mut BTreeMap> { + match self { + Self::ConcreteDyn(ref mut dyn_range) => Some(&mut dyn_range.val), + _ => None, + } + } + + /// Creates a new range element that is a cast from one type to another + pub fn cast(self, other: Self) -> Self { + let expr = RangeExpr::new(self, RangeOp::Cast, other); + Elem::Expr(expr) + } + + pub fn concat(self, other: Self) -> Self { + let expr = RangeExpr::new(self, RangeOp::Concat, other); + Elem::Expr(expr) + } + + /// Creates a new range element that is the minimum of two range elements + pub fn min(self, other: Self) -> Self { + let expr = RangeExpr::new(self, RangeOp::Min, other); + Elem::Expr(expr) + } + + /// Creates a new range element that is the maximum of two range elements + pub fn max(self, other: Self) -> Self { + let expr = RangeExpr::new(self, RangeOp::Max, other); + Elem::Expr(expr) + } + + /// Creates a new range element that is a boolean of equality of two range elements + pub fn eq(self, other: Self) -> Self { + let expr = RangeExpr::new(self, RangeOp::Eq, other); + Elem::Expr(expr) + } + + /// Creates a new range element that is a boolean of inequality of two range elements + pub fn neq(self, other: Self) -> Self { + let expr = RangeExpr::new(self, RangeOp::Neq, other); + Elem::Expr(expr) + } + + /// Creates a new range element that is one range element to the power of another + pub fn pow(self, other: Self) -> Self { + let expr = RangeExpr::new(self, RangeOp::Exp, other); + Elem::Expr(expr) + } + + /// Creates a new range element that is a memcopy of another + pub fn memcopy(self) -> Self { + let expr = RangeExpr::new(self, RangeOp::Memcopy, Elem::Null); + Elem::Expr(expr) + } + + /// Creates a new range element that applies a setting of indices of a memory object + pub fn set_indices(self, other: RangeDyn) -> Self { + let expr = RangeExpr::new(self, RangeOp::SetIndices, Elem::ConcreteDyn(other)); + Elem::Expr(expr) + } + + /// Creates a new range element that sets the length of a memory object + pub fn set_length(self, other: Self) -> Self { + let expr = RangeExpr::new(self, RangeOp::SetLength, 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); + Elem::Expr(expr) + } + + /// Gets the length of a memory object + pub fn get_index(self, other: Self) -> Self { + let expr = RangeExpr::new(self, RangeOp::GetIndex, other); + Elem::Expr(expr) + } +} + +impl Elem { + pub fn replace_dep( + &mut self, + to_replace: NodeIdx, + replacement: Self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena, + ) { + match self { + Elem::Reference(Reference { idx, .. }) => { + if *idx == to_replace { + *self = replacement; + } + } + Elem::Concrete(_) => {} + Elem::Expr(expr) => { + expr.lhs + .replace_dep(to_replace, replacement.clone(), analyzer, arena); + expr.rhs + .replace_dep(to_replace, replacement, analyzer, arena); + expr.maximized = None; + expr.minimized = None; + } + Elem::ConcreteDyn(d) => { + d.len + .replace_dep(to_replace, replacement.clone(), analyzer, arena); + let vals = std::mem::take(&mut d.val); + d.val = vals + .into_iter() + .map(|(mut k, (mut v, op))| { + k.replace_dep(to_replace, replacement.clone(), analyzer, arena); + v.replace_dep(to_replace, replacement.clone(), analyzer, arena); + (k, (v, op)) + }) + .collect(); + } + Elem::Null => {} + Elem::Arena(_) => { + let mut cloned = self.dearenaize_clone(arena); + cloned.replace_dep(to_replace, replacement, analyzer, arena); + cloned.arenaize(analyzer, arena).unwrap(); + *self = cloned; + } + } + } + + pub fn recurse_dearenaize( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena, + ) -> Self { + match self { + Self::Arena(arena_idx) => arena + .ranges + .get(*arena_idx) + .unwrap() + .clone() + .recurse_dearenaize(analyzer, arena), + Self::Expr(expr) => expr.recurse_dearenaize(analyzer, arena), + e => e.clone(), + } + } + + pub fn dearenaize_clone(&self, arena: &mut RangeArena) -> Self { + match self { + Self::Arena(arena_idx) => arena.ranges.get(*arena_idx).cloned().unwrap_or_default(), + _ => unreachable!(), + } + } + + pub fn dearenaize(&self, arena: &mut RangeArena) -> (Self, usize) { + match self { + Self::Arena(arena_idx) => { + ( + arena.take_nonnull(*arena_idx).unwrap_or_default(), + // arena.ranges.get(*arena_idx).cloned().unwrap_or_default(), + *arena_idx, + ) + } + _ => unreachable!(), + } + } + + pub fn rearenaize(&self, elem: Self, idx: usize, arena: &mut RangeArena) { + if !matches!(elem, Elem::Null) { + if let Some(t) = arena.ranges.get_mut(idx) { + *t = elem; + } + } + } + + pub fn arena_eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Arena(a), Self::Arena(b)) => a == b, + (Self::Concrete(a), Self::Concrete(b)) => a == b, + (Self::ConcreteDyn(a), Self::ConcreteDyn(b)) => { + a.len == b.len + && a.val.len() == b.val.len() + && a.val + .iter() + .zip(b.val.iter()) + .all(|((a, op_a), (b, op_b))| a.arena_eq(b) && op_a == op_b) + } + (Self::Reference(a), Self::Reference(b)) => a == b, + (Self::Expr(a), Self::Expr(b)) => { + a.lhs.arena_eq(&b.lhs) && a.rhs.arena_eq(&b.rhs) && a.op == b.op + } + (Elem::Null, Elem::Null) => true, + _ => false, + } + } + pub fn as_bytes( + &self, + analyzer: &impl GraphBackend, + maximize: bool, + arena: &mut RangeArena>, + ) -> Option> { + let evaled = if maximize { + self.maximize(analyzer, arena).ok()? + } else { + self.minimize(analyzer, arena).ok()? + }; + + match evaled { + Elem::Concrete(c) => c.as_bytes(analyzer, maximize, arena), + Elem::ConcreteDyn(c) => c.as_bytes(analyzer, maximize, arena), + _ => None, + } + } + + pub fn overlaps( + &self, + other: &Self, + eval: bool, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { + match (self, other) { + (Elem::Concrete(s), Elem::Concrete(o)) => Ok(Some(o.val == s.val)), + (Elem::Reference(s), Elem::Reference(o)) => { + if s == o { + Ok(Some(true)) + } else if eval { + let lhs_min = s.minimize(analyzer, arena)?; + let rhs_max = o.maximize(analyzer, arena)?; + + match lhs_min.range_ord(&rhs_max, arena) { + Some(std::cmp::Ordering::Less) => { + // we know our min is less than the other max + // check that the max is greater than or eq their min + let lhs_max = s.maximize(analyzer, arena)?; + let rhs_min = o.minimize(analyzer, arena)?; + Ok(Some(matches!( + lhs_max.range_ord(&rhs_min, arena), + Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) + ))) + } + Some(std::cmp::Ordering::Equal) => Ok(Some(true)), + _ => Ok(Some(false)), + } + } else { + Ok(None) + } + } + (Elem::Reference(s), c @ Elem::Concrete(_)) => { + if eval { + let lhs_min = s.minimize(analyzer, arena)?; + + match lhs_min.range_ord(c, arena) { + Some(std::cmp::Ordering::Less) => { + // we know our min is less than the other max + // check that the max is greater than or eq their min + let lhs_max = s.maximize(analyzer, arena)?; + Ok(Some(matches!( + lhs_max.range_ord(c, arena), + Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) + ))) + } + Some(std::cmp::Ordering::Equal) => Ok(Some(true)), + _ => Ok(Some(false)), + } + } else { + Ok(None) + } + } + (Elem::Concrete(_), Elem::Reference(_)) => other.overlaps(self, eval, analyzer, arena), + _ => Ok(None), + } + } + + /// Given an element and a min and max, checks if the element could be equal to the RHS + pub fn overlaps_dual( + &self, + rhs_min: &Self, + rhs_max: &Self, + eval: bool, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { + match self { + Self::Reference(d) => { + if eval { + let lhs_min = d.minimize(analyzer, arena)?; + let rhs_max = rhs_max.maximize(analyzer, arena)?; + + match lhs_min.range_ord(&rhs_max, arena) { + Some(std::cmp::Ordering::Less) => { + // we know our min is less than the other max + // check that the max is greater than or eq their min + let lhs_max = d.maximize(analyzer, arena)?; + let rhs_min = rhs_min.minimize(analyzer, arena)?; + Ok(Some(matches!( + lhs_max.range_ord(&rhs_min, arena), + Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) + ))) + } + Some(std::cmp::Ordering::Equal) => Ok(Some(true)), + _ => Ok(Some(false)), + } + } else if self == rhs_min || self == rhs_max { + Ok(Some(true)) + } else { + Ok(None) + } + } + Self::Concrete(_) => { + let (min, max) = if eval { + ( + rhs_min.minimize(analyzer, arena)?, + rhs_max.maximize(analyzer, arena)?, + ) + } else { + (rhs_min.clone(), rhs_max.clone()) + }; + + match min.range_ord(self, arena) { + Some(std::cmp::Ordering::Less) => Ok(Some(matches!( + max.range_ord(self, arena), + Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) + ))), + Some(std::cmp::Ordering::Equal) => Ok(Some(true)), + _ => Ok(Some(false)), + } + } + _ => Ok(None), + } + } + pub fn is_negative( + &self, + maximize: bool, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result { + let res = match self { + Elem::Concrete(RangeConcrete { + val: Concrete::Int(_, val), + .. + }) if val < &I256::zero() => true, + Elem::Reference(dy) => { + if maximize { + dy.maximize(analyzer, arena)? + .is_negative(maximize, analyzer, arena)? + } else { + dy.minimize(analyzer, arena)? + .is_negative(maximize, analyzer, arena)? + } + } + Elem::Expr(expr) => { + if maximize { + expr.maximize(analyzer, arena)? + .is_negative(maximize, analyzer, arena)? + } else { + expr.minimize(analyzer, arena)? + .is_negative(maximize, analyzer, arena)? + } + } + _ => false, + }; + Ok(res) + } + + pub fn pre_evaled_is_negative(&self) -> bool { + matches!(self, Elem::Concrete(RangeConcrete { val: Concrete::Int(_, val), ..}) if val < &I256::zero()) + } + + pub fn inverse_if_boolean(&self) -> Option { + match self { + Self::Reference(Reference { idx: _, .. }) => Some(Elem::Expr(RangeExpr::new( + self.clone(), + RangeOp::Not, + Elem::Null, + ))), + Self::Concrete(_) => Some(Elem::Expr(RangeExpr::new( + self.clone(), + RangeOp::Not, + Elem::Null, + ))), + Self::Expr(expr) => Some(Elem::Expr(expr.inverse_if_boolean()?)), + Self::ConcreteDyn(_d) => None, + Self::Null => None, + Self::Arena(_) => todo!(), + } + } + + pub fn arenaized_flattened( + &self, + max: bool, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Option>> { + if let Some(idx) = arena.idx(self) { + if let Some(t) = arena.ranges.get(idx) { + match t { + Elem::Expr(ref arenaized) => { + if max { + arenaized.flattened_max.clone() + } else { + arenaized.flattened_min.clone() + } + } + Elem::Reference(ref arenaized) => { + if max { + arenaized.flattened_max.clone() + } else { + arenaized.flattened_min.clone() + } + } + Elem::ConcreteDyn(ref arenaized) => { + if max { + arenaized.flattened_max.clone() + } else { + arenaized.flattened_min.clone() + } + } + c @ Elem::Concrete(_) => Some(Box::new(c.clone())), + c @ Elem::Null => Some(Box::new(c.clone())), + Elem::Arena(idx) => Elem::Arena(*idx).arenaized_flattened(max, analyzer, arena), + } + } else { + None + } + } else { + None + } + } + + pub fn set_arenaized_flattened( + &self, + max: bool, + elem: &Elem, + arena: &mut RangeArena>, + ) { + if let Some(idx) = arena.idx(self) { + if let Some(ref mut t) = arena.ranges.get_mut(idx) { + match &mut *t { + Elem::Expr(ref mut arenaized) => { + if max { + arenaized.flattened_max = Some(Box::new(elem.clone())); + } else { + arenaized.flattened_min = Some(Box::new(elem.clone())); + } + } + Elem::Reference(ref mut arenaized) => { + if max { + arenaized.flattened_max = Some(Box::new(elem.clone())); + } else { + arenaized.flattened_min = Some(Box::new(elem.clone())); + } + } + Elem::ConcreteDyn(ref mut arenaized) => { + if max { + arenaized.flattened_max = Some(Box::new(elem.clone())); + } else { + arenaized.flattened_min = Some(Box::new(elem.clone())); + } + } + _ => {} + } + } + } + } + + pub fn set_arenaized_cache( + &self, + max: bool, + elem: &Elem, + arena: &mut RangeArena>, + ) { + if let Some(idx) = arena.idx(self) { + if let Some(t) = arena.ranges.get_mut(idx) { + match &mut *t { + Elem::Expr(ref mut arenaized) => { + if max { + arenaized.maximized = Some(MinMaxed::Maximized(Box::new(elem.clone()))); + } else { + arenaized.minimized = Some(MinMaxed::Minimized(Box::new(elem.clone()))); + } + } + Elem::Reference(ref mut arenaized) => { + if max { + arenaized.maximized = Some(MinMaxed::Maximized(Box::new(elem.clone()))); + } else { + arenaized.minimized = Some(MinMaxed::Minimized(Box::new(elem.clone()))); + } + } + Elem::ConcreteDyn(ref mut arenaized) => { + if max { + arenaized.maximized = Some(MinMaxed::Maximized(Box::new(elem.clone()))); + } else { + arenaized.minimized = Some(MinMaxed::Minimized(Box::new(elem.clone()))); + } + } + _ => {} + } + } + } + } + + pub fn is_bytes(&self) -> bool { + matches!( + self, + Elem::Concrete(RangeConcrete { + val: Concrete::Bytes(..), + .. + }) + ) + } + + pub fn is_string(&self) -> bool { + matches!( + self, + Elem::Concrete(RangeConcrete { + val: Concrete::String(..), + .. + }) + ) + } + + pub fn is_uint(&self) -> bool { + matches!( + self, + Elem::Concrete(RangeConcrete { + val: Concrete::Uint(..), + .. + }) + ) + } + + pub fn is_int(&self) -> bool { + matches!( + self, + Elem::Concrete(RangeConcrete { + val: Concrete::Int(..), + .. + }) + ) + } +} diff --git a/crates/graph/src/range/elem/elem_enum/mod.rs b/crates/graph/src/range/elem/elem_enum/mod.rs new file mode 100644 index 00000000..4c9bb429 --- /dev/null +++ b/crates/graph/src/range/elem/elem_enum/mod.rs @@ -0,0 +1,28 @@ +mod arena; +mod impls; +mod ops; +mod range_elem; +mod traits; + +use crate::range::elem::{RangeConcrete, RangeDyn, RangeExpr, Reference}; +use shared::RangeArenaIdx; + +pub use arena::RangeArenaLike; + +/// A core range element. +#[derive(Default, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] +pub enum Elem { + /// A range element that is a reference to another node + Reference(Reference), + /// A concrete range element of type `T`. e.g.: some number like `10` + ConcreteDyn(RangeDyn), + /// A concrete range element of type `T`. e.g.: some number like `10` + Concrete(RangeConcrete), + /// A range element that is an expression composed of other range elements + Expr(RangeExpr), + /// A range element that is a pointer to another expression in an arena + Arena(RangeArenaIdx), + /// A null range element useful in range expressions that dont have a rhs + #[default] + Null, +} diff --git a/crates/graph/src/range/elem/elem_enum/ops.rs b/crates/graph/src/range/elem/elem_enum/ops.rs new file mode 100644 index 00000000..52e17fa1 --- /dev/null +++ b/crates/graph/src/range/elem/elem_enum/ops.rs @@ -0,0 +1,93 @@ +use crate::range::elem::{Elem, RangeExpr, RangeOp}; + +use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Rem, Shl, Shr, Sub}; + +impl Add for Elem { + type Output = Self; + + fn add(self, other: Elem) -> Self { + let expr = RangeExpr::new(self, RangeOp::Add(false), other); + Self::Expr(expr) + } +} + +impl Sub for Elem { + type Output = Self; + + fn sub(self, other: Elem) -> Self { + let expr = RangeExpr::new(self, RangeOp::Sub(false), other); + Self::Expr(expr) + } +} + +impl Mul for Elem { + type Output = Self; + + fn mul(self, other: Elem) -> Self { + let expr = RangeExpr::new(self, RangeOp::Mul(false), other); + Self::Expr(expr) + } +} + +impl Div for Elem { + type Output = Self; + + fn div(self, other: Elem) -> Self { + let expr = RangeExpr::new(self, RangeOp::Div(false), other); + Self::Expr(expr) + } +} + +impl Shl for Elem { + type Output = Self; + + fn shl(self, other: Elem) -> Self { + let expr = RangeExpr::new(self, RangeOp::Shl, other); + Self::Expr(expr) + } +} + +impl Shr for Elem { + type Output = Self; + + fn shr(self, other: Elem) -> Self { + let expr = RangeExpr::new(self, RangeOp::Shr, other); + Self::Expr(expr) + } +} + +impl Rem for Elem { + type Output = Self; + + fn rem(self, other: Elem) -> Self { + let expr = RangeExpr::new(self, RangeOp::Mod, other); + Self::Expr(expr) + } +} + +impl BitAnd for Elem { + type Output = Self; + + fn bitand(self, other: Self) -> Self::Output { + let expr = RangeExpr::new(self, RangeOp::BitAnd, other); + Self::Expr(expr) + } +} + +impl BitOr for Elem { + type Output = Self; + + fn bitor(self, other: Self) -> Self::Output { + let expr = RangeExpr::new(self, RangeOp::BitOr, other); + Self::Expr(expr) + } +} + +impl BitXor for Elem { + type Output = Self; + + fn bitxor(self, other: Self) -> Self::Output { + let expr = RangeExpr::new(self, RangeOp::BitXor, other); + Self::Expr(expr) + } +} diff --git a/crates/graph/src/range/elem/elem_enum/range_elem.rs b/crates/graph/src/range/elem/elem_enum/range_elem.rs new file mode 100644 index 00000000..dc9898f3 --- /dev/null +++ b/crates/graph/src/range/elem/elem_enum/range_elem.rs @@ -0,0 +1,617 @@ +use crate::elem::{MinMaxed, RangeArenaLike}; +use crate::{ + nodes::{Concrete, ContextVarNode}, + range::elem::{collapse, Elem, MaybeCollapsed, RangeElem}, + GraphBackend, GraphError, +}; + +use shared::{NodeIdx, RangeArena}; + +impl RangeElem for Elem { + type GraphError = GraphError; + + fn arenaize( + &mut self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { + match self { + Self::Arena(_) => return Ok(()), + Self::Reference(d) => d.arenaize(analyzer, arena)?, + Self::ConcreteDyn(d) => d.arenaize(analyzer, arena)?, + Self::Expr(expr) => { + expr.arenaize(analyzer, arena)?; + } + Self::Concrete(c) => c.arenaize(analyzer, arena)?, + Self::Null => {} + } + + let self_take = std::mem::take(self); + *self = Elem::Arena(arena.idx_or_upsert(self_take, analyzer)); + Ok(()) + } + + fn range_eq(&self, other: &Self, arena: &mut RangeArena>) -> bool { + match (self, other) { + (Self::Arena(a), Self::Arena(b)) => a == b, + (Self::Concrete(a), Self::Concrete(b)) => a.range_eq(b, arena), + (Self::ConcreteDyn(a), Self::ConcreteDyn(b)) => a.range_eq(b, arena), + (Self::Reference(a), Self::Reference(b)) => a.idx == b.idx, + _ => false, + } + } + + fn range_ord( + &self, + other: &Self, + arena: &mut RangeArena>, + ) -> Option { + match (self, other) { + (Self::Arena(a), Self::Arena(b)) => { + if a == b { + Some(std::cmp::Ordering::Equal) + } else { + let (l, a) = self.dearenaize(arena); + let (r, b) = other.dearenaize(arena); + let res = l.range_ord(&r, arena); + self.rearenaize(l, a, arena); + self.rearenaize(r, b, arena); + res + } + } + (Self::Concrete(a), Self::Concrete(b)) => a.range_ord(b, arena), + (c @ Self::Concrete(_), Self::Reference(r)) => { + if let (Some(MinMaxed::Minimized(min)), Some(MinMaxed::Maximized(max))) = + (&r.minimized, &r.maximized) + { + let min_ord = c.range_ord(min, arena)?; + let max_ord = c.range_ord(max, arena)?; + if min_ord == max_ord { + Some(min_ord) + } else { + None + } + } else { + None + } + } + (Self::Reference(r), c @ Self::Concrete(_)) => { + if let (Some(MinMaxed::Minimized(min)), Some(MinMaxed::Maximized(max))) = + (&r.minimized, &r.maximized) + { + let min_ord = min.range_ord(c, arena)?; + let max_ord = max.range_ord(c, arena)?; + if min_ord == max_ord { + Some(min_ord) + } else { + None + } + } else { + None + } + } + (Self::Reference(a), Self::Reference(b)) => a.range_ord(b, arena), + (Elem::Null, Elem::Null) => None, + (_a, Elem::Null) => Some(std::cmp::Ordering::Greater), + (Elem::Null, _a) => Some(std::cmp::Ordering::Less), + _ => None, + } + } + + fn flatten( + &self, + maximize: bool, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { + match self { + Self::Reference(d) => d.flatten(maximize, analyzer, arena), + Self::Concrete(c) => c.flatten(maximize, analyzer, arena), + Self::Expr(expr) => expr.flatten(maximize, analyzer, arena), + Self::ConcreteDyn(d) => d.flatten(maximize, analyzer, arena), + Self::Null => Ok(Elem::Null), + Self::Arena(_) => { + let (de, idx) = self.dearenaize(arena); + let res = de.flatten(maximize, analyzer, arena)?; + self.rearenaize(de, idx, arena); + Ok(res) + } + } + } + + fn cache_flatten( + &mut self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { + if self.is_flatten_cached(analyzer, arena) { + return Ok(()); + } + + match self { + Self::Reference(d) => d.cache_flatten(analyzer, arena), + Self::Concrete(c) => c.cache_flatten(analyzer, arena), + Self::Expr(expr) => expr.cache_flatten(analyzer, arena), + Self::ConcreteDyn(d) => d.cache_flatten(analyzer, arena), + Self::Null => Ok(()), + Self::Arena(idx) => { + tracing::trace!("flattening for arena idx: {idx}"); + let (mut dearenaized, idx) = self.dearenaize(arena); + dearenaized.cache_flatten(analyzer, arena)?; + self.rearenaize(dearenaized, idx, arena); + Ok(()) + } + } + } + + fn is_flatten_cached( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> bool { + match self { + Self::Reference(d) => d.is_flatten_cached(analyzer, arena), + Self::Concrete(c) => c.is_flatten_cached(analyzer, arena), + Self::Expr(expr) => expr.is_flatten_cached(analyzer, arena), + Self::ConcreteDyn(d) => d.is_flatten_cached(analyzer, arena), + Self::Null => true, + Self::Arena(_) => { + let (t, idx) = self.dearenaize(arena); + let res = t.is_flatten_cached(analyzer, arena); + self.rearenaize(t, idx, arena); + res + } + } + } + + fn is_min_max_cached( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> (bool, bool) { + match self { + Self::Reference(d) => d.is_min_max_cached(analyzer, arena), + Self::Concrete(_c) => (true, true), + Self::Expr(expr) => expr.is_min_max_cached(analyzer, arena), + Self::ConcreteDyn(d) => d.is_min_max_cached(analyzer, arena), + Self::Null => (true, true), + Self::Arena(_) => { + let (t, idx) = self.dearenaize(arena); + let res = t.is_min_max_cached(analyzer, arena); + self.rearenaize(t, idx, arena); + res + } + } + } + + fn dependent_on( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Vec { + match self { + Self::Reference(d) => d.dependent_on(analyzer, arena), + Self::Concrete(_) => vec![], + Self::Expr(expr) => expr.dependent_on(analyzer, arena), + Self::ConcreteDyn(d) => d.dependent_on(analyzer, arena), + Self::Null => vec![], + Self::Arena(_) => { + let (t, idx) = self.dearenaize(arena); + let res = t.dependent_on(analyzer, arena); + self.rearenaize(t, idx, arena); + res + } + } + } + + fn recursive_dependent_on( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { + match self { + Self::Reference(d) => d.recursive_dependent_on(analyzer, arena), + Self::Concrete(_) => Ok(vec![]), + Self::Expr(expr) => expr.recursive_dependent_on(analyzer, arena), + Self::ConcreteDyn(d) => d.recursive_dependent_on(analyzer, arena), + Self::Null => Ok(vec![]), + Self::Arena(_) => { + let (dearenaized, idx) = self.dearenaize(arena); + let res = dearenaized.recursive_dependent_on(analyzer, arena); + self.rearenaize(dearenaized, idx, arena); + res + } + } + } + + fn has_cycle( + &self, + seen: &mut Vec, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result { + match self { + Self::Reference(d) => d.has_cycle(seen, analyzer, arena), + Self::Concrete(_) => Ok(false), + Self::Expr(expr) => expr.has_cycle(seen, analyzer, arena), + Self::ConcreteDyn(d) => d.has_cycle(seen, analyzer, arena), + Self::Null => Ok(false), + Self::Arena(_) => { + let (dearenaized, idx) = self.dearenaize(arena); + let res = dearenaized.has_cycle(seen, analyzer, arena); + self.rearenaize(dearenaized, idx, arena); + res + } + } + } + + fn depends_on( + &self, + var: ContextVarNode, + seen: &mut Vec, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result { + match self { + Self::Reference(d) => d.depends_on(var, seen, analyzer, arena), + Self::Concrete(_) => Ok(false), + Self::Expr(expr) => expr.depends_on(var, seen, analyzer, arena), + Self::ConcreteDyn(d) => d.depends_on(var, seen, analyzer, arena), + Self::Null => Ok(false), + Self::Arena(_) => { + let (dearenaized, idx) = self.dearenaize(arena); + let res = dearenaized.depends_on(var, seen, analyzer, arena); + self.rearenaize(dearenaized, idx, arena); + res + } + } + } + + fn filter_recursion( + &mut self, + node_idx: NodeIdx, + new_idx: NodeIdx, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) { + match self { + Self::Reference(ref mut d) => { + if d.idx == node_idx { + d.idx = new_idx + } + } + Self::Concrete(_) => {} + Self::Expr(expr) => expr.filter_recursion(node_idx, new_idx, analyzer, arena), + Self::ConcreteDyn(d) => d.filter_recursion(node_idx, new_idx, analyzer, arena), + Self::Null => {} + Self::Arena(_) => { + let (mut dearenaized, idx) = self.dearenaize(arena); + dearenaized.filter_recursion(node_idx, new_idx, analyzer, arena); + self.rearenaize(dearenaized, idx, arena); + } + } + } + + fn maximize( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { + use Elem::*; + let res = match self { + Reference(dy) => dy.maximize(analyzer, arena)?, + Concrete(inner) => inner.maximize(analyzer, arena)?, + ConcreteDyn(inner) => inner.maximize(analyzer, arena)?, + Expr(expr) => expr.maximize(analyzer, arena)?, + Null => Elem::Null, + Arena(_) => { + let (dearenaized, idx) = self.dearenaize(arena); + let res = dearenaized.maximize(analyzer, arena)?; + self.rearenaize(dearenaized, idx, arena); + + match arena.ranges.get_mut(idx) { + Some(Self::Reference(ref mut d)) => { + if d.maximized.is_none() { + d.maximized = Some(MinMaxed::Maximized(Box::new(res.clone()))); + } + } + Some(Self::Expr(ref mut expr)) => { + if expr.maximized.is_none() { + expr.maximized = Some(MinMaxed::Maximized(Box::new(res.clone()))); + } + } + Some(Self::ConcreteDyn(ref mut d)) => { + if d.maximized.is_none() { + d.maximized = Some(MinMaxed::Maximized(Box::new(res.clone()))); + } + } + _ => {} + } + + let (_min, max) = self.is_min_max_cached(analyzer, arena); + assert!(max, "????"); + + res + } + }; + Ok(res) + } + + fn minimize( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { + use Elem::*; + let res = match self { + Reference(dy) => dy.minimize(analyzer, arena)?, + Concrete(inner) => inner.minimize(analyzer, arena)?, + ConcreteDyn(inner) => inner.minimize(analyzer, arena)?, + Expr(expr) => expr.minimize(analyzer, arena)?, + Null => Elem::Null, + Arena(_) => { + let (dearenaized, idx) = self.dearenaize(arena); + let res = dearenaized.minimize(analyzer, arena)?; + self.rearenaize(dearenaized, idx, arena); + + match arena.ranges.get_mut(idx) { + Some(Self::Reference(ref mut d)) => { + if d.minimized.is_none() { + d.minimized = Some(MinMaxed::Minimized(Box::new(res.clone()))); + } + } + Some(Self::Expr(ref mut expr)) => { + if expr.minimized.is_none() { + expr.minimized = Some(MinMaxed::Minimized(Box::new(res.clone()))); + } + } + Some(Self::ConcreteDyn(ref mut d)) => { + if d.minimized.is_none() { + d.minimized = Some(MinMaxed::Minimized(Box::new(res.clone()))); + } + } + _ => {} + } + + let (min, _max) = self.is_min_max_cached(analyzer, arena); + assert!(min, "????"); + res + } + }; + Ok(res) + } + + fn simplify_maximize( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { + use Elem::*; + + if let Some(idx) = arena.idx(self) { + if let Some(t) = arena.ranges.get(idx) { + match t { + Reference(dy) => { + if let Some(max) = &dy.flattened_max { + return Ok(*max.clone()); + } + } + c @ Concrete(_) => return Ok(c.clone()), + ConcreteDyn(inner) => { + if let Some(max) = &inner.flattened_max { + return Ok(*max.clone()); + } + } + Expr(expr) => { + if let Some(max) = &expr.flattened_max { + return Ok(*max.clone()); + } + } + _ => {} + } + } + } + + match self { + Reference(dy) => dy.simplify_maximize(analyzer, arena), + Concrete(inner) => inner.simplify_maximize(analyzer, arena), + ConcreteDyn(inner) => inner.simplify_maximize(analyzer, arena), + Expr(expr) => match collapse(*expr.lhs.clone(), expr.op, *expr.rhs.clone(), arena) { + MaybeCollapsed::Collapsed(collapsed) => { + let res = collapsed.simplify_maximize(analyzer, arena)?; + collapsed.set_arenaized_flattened(true, &res, arena); + Ok(res) + } + _ => { + let res = expr.simplify_maximize(analyzer, arena)?; + expr.set_arenaized_flattened(true, res.clone(), arena); + Ok(res) + } + }, + Null => Ok(Elem::Null), + Arena(_) => { + let (dearenaized, idx) = self.dearenaize(arena); + let flat = dearenaized.flatten(true, analyzer, arena)?; + let max = flat.simplify_maximize(analyzer, arena)?; + self.rearenaize(dearenaized, idx, arena); + + match arena.ranges.get_mut(idx) { + Some(Self::Reference(ref mut d)) => { + tracing::trace!("simplify maximize cache MISS: {self}"); + d.flattened_max = Some(Box::new(max.clone())); + } + Some(Self::Expr(ref mut expr)) => { + tracing::trace!("simplify maximize cache MISS: {self}"); + expr.flattened_max = Some(Box::new(max.clone())); + } + Some(Self::ConcreteDyn(ref mut d)) => { + tracing::trace!("simplify maximize cache MISS: {self}"); + d.flattened_max = Some(Box::new(max.clone())); + } + _ => {} + } + + Ok(max) + } + } + } + + fn simplify_minimize( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { + use Elem::*; + + if let Some(idx) = arena.idx(self) { + if let Some(t) = arena.ranges.get(idx) { + match t { + Reference(dy) => { + if let Some(min) = &dy.flattened_min { + return Ok(*min.clone()); + } + } + c @ Concrete(_) => return Ok(c.clone()), + ConcreteDyn(inner) => { + if let Some(min) = &inner.flattened_min { + return Ok(*min.clone()); + } + } + Expr(expr) => { + if let Some(min) = &expr.flattened_min { + return Ok(*min.clone()); + } + } + Null => return Ok(Elem::Null), + _ => {} + } + } + } + + let res = match self { + Reference(dy) => dy.simplify_minimize(analyzer, arena), + Concrete(inner) => inner.simplify_minimize(analyzer, arena), + ConcreteDyn(inner) => inner.simplify_minimize(analyzer, arena), + Expr(expr) => match collapse(*expr.lhs.clone(), expr.op, *expr.rhs.clone(), arena) { + MaybeCollapsed::Collapsed(collapsed) => { + let res = collapsed.simplify_minimize(analyzer, arena)?; + collapsed.set_arenaized_flattened(false, &res, arena); + Ok(res) + } + _ => { + let res = expr.simplify_minimize(analyzer, arena)?; + expr.set_arenaized_flattened(false, res.clone(), arena); + Ok(res) + } + }, + Null => Ok(Elem::Null), + Arena(_) => { + let (dearenaized, idx) = self.dearenaize(arena); + let flat = dearenaized.flatten(false, analyzer, arena)?; + let min = flat.simplify_minimize(analyzer, arena)?; + self.rearenaize(dearenaized, idx, arena); + + match arena.ranges.get_mut(idx) { + Some(Self::Reference(ref mut d)) => { + tracing::trace!("simplify minimize cache MISS: {self}"); + d.flattened_min = Some(Box::new(min.clone())); + } + Some(Self::Expr(ref mut expr)) => { + tracing::trace!("simplify minimize cache MISS: {self}"); + expr.flattened_min = Some(Box::new(min.clone())); + } + Some(Self::ConcreteDyn(ref mut d)) => { + tracing::trace!("simplify minimize cache MISS: {self}"); + d.flattened_min = Some(Box::new(min.clone())); + } + _ => {} + } + + Ok(min) + } + }?; + + Ok(res) + } + + fn cache_maximize( + &mut self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { + use Elem::*; + match self { + Reference(dy) => dy.cache_maximize(analyzer, arena), + Concrete(inner) => inner.cache_maximize(analyzer, arena), + ConcreteDyn(inner) => inner.cache_maximize(analyzer, arena), + Expr(expr) => match collapse(*expr.lhs.clone(), expr.op, *expr.rhs.clone(), arena) { + MaybeCollapsed::Collapsed(mut collapsed) => { + collapsed.cache_maximize(analyzer, arena)?; + let max = collapsed.maximize(analyzer, arena)?; + self.set_arenaized_flattened(true, &max, arena); + *self = collapsed; + Ok(()) + } + _ => { + expr.cache_maximize(analyzer, arena)?; + let max = expr.maximize(analyzer, arena)?; + self.set_arenaized_flattened(true, &max, arena); + Ok(()) + } + }, + Null => Ok(()), + Arena(_) => { + let (mut dearenaized, idx) = self.dearenaize(arena); + dearenaized.cache_maximize(analyzer, arena)?; + self.rearenaize(dearenaized, idx, arena); + Ok(()) + } + } + } + + fn cache_minimize( + &mut self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { + use Elem::*; + match self { + Reference(dy) => dy.cache_minimize(analyzer, arena), + Concrete(inner) => inner.cache_minimize(analyzer, arena), + ConcreteDyn(inner) => inner.cache_minimize(analyzer, arena), + Expr(expr) => match collapse(*expr.lhs.clone(), expr.op, *expr.rhs.clone(), arena) { + MaybeCollapsed::Collapsed(mut collapsed) => { + collapsed.cache_minimize(analyzer, arena)?; + let min = collapsed.minimize(analyzer, arena)?; + self.set_arenaized_flattened(false, &min, arena); + *self = collapsed; + Ok(()) + } + _ => { + expr.cache_minimize(analyzer, arena)?; + let min = expr.minimize(analyzer, arena)?; + self.set_arenaized_flattened(false, &min, arena); + Ok(()) + } + }, + Null => Ok(()), + Arena(_) => { + let (mut dearenaized, idx) = self.dearenaize(arena); + dearenaized.cache_minimize(analyzer, arena)?; + self.rearenaize(dearenaized, idx, arena); + Ok(()) + } + } + } + fn uncache(&mut self) { + use Elem::*; + match self { + Reference(dy) => dy.uncache(), + Concrete(inner) => inner.uncache(), + ConcreteDyn(inner) => inner.uncache(), + Expr(expr) => expr.uncache(), + Null => {} + Arena(_idx) => {} + } + } +} diff --git a/crates/graph/src/range/elem/elem_enum/traits.rs b/crates/graph/src/range/elem/elem_enum/traits.rs new file mode 100644 index 00000000..a2faced0 --- /dev/null +++ b/crates/graph/src/range/elem/elem_enum/traits.rs @@ -0,0 +1,114 @@ +use crate::{ + nodes::{Concrete, ContextVarNode}, + range::elem::{Elem, RangeConcrete, RangeExpr, RangeOp, Reference}, +}; +use shared::NodeIdx; + +use solang_parser::pt::Loc; + +use std::{ + borrow::Cow, + hash::{Hash, Hasher}, +}; + +impl Hash for Elem { + fn hash(&self, state: &mut H) { + match self { + Elem::Reference(r) => r.hash(state), + Elem::Concrete(c) => c.hash(state), + Elem::Expr(expr) => expr.hash(state), + Elem::ConcreteDyn(d) => d.hash(state), + Elem::Null => (-1i32).hash(state), + Elem::Arena(idx) => idx.hash(state), + } + } +} + +impl<'a> From<&'a Elem> for Cow<'a, Elem> { + fn from(val: &'a Elem) -> Self { + Cow::Borrowed(val) + } +} + +impl<'a> From> for Cow<'a, Elem> { + fn from(val: Elem) -> Self { + Cow::Owned(val) + } +} + +impl From for Elem { + fn from(c: bool) -> Self { + Elem::Concrete(RangeConcrete::new(Concrete::from(c), Loc::Implicit)) + } +} + +impl From> for Elem { + fn from(dy: Reference) -> Self { + Elem::Reference(dy) + } +} + +impl From> for Elem { + fn from(c: RangeConcrete) -> Self { + Elem::Concrete(c) + } +} + +impl From for Elem { + fn from(idx: NodeIdx) -> Self { + Elem::Reference(Reference::new(idx)) + } +} + +impl From for Elem { + fn from(c: Concrete) -> Self { + Elem::Concrete(RangeConcrete::new(c, Loc::Implicit)) + } +} + +impl From for Elem { + fn from(c: ContextVarNode) -> Self { + Elem::Reference(Reference::new(c.into())) + } +} + +impl std::fmt::Display for Elem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Elem::Reference(Reference { idx, .. }) => write!(f, "idx_{}", idx.index()), + Elem::ConcreteDyn(d) => { + write!(f, "{{len: {}, values: {{", d.len)?; + d.val + .iter() + .try_for_each(|(key, (val, op))| write!(f, " {key}: ({val}, {op}),"))?; + write!(f, "}}}}") + } + Elem::Concrete(RangeConcrete { val, .. }) => { + write!(f, "{}", val.as_string()) + } + Elem::Expr(RangeExpr { lhs, op, rhs, .. }) => match op { + RangeOp::Min | RangeOp::Max => { + write!(f, "{}{{{}, {}}}", op.to_string(), lhs, rhs) + } + RangeOp::Cast => match &**rhs { + Elem::Concrete(RangeConcrete { val, .. }) => { + write!( + f, + "{}({}, {})", + op.to_string(), + lhs, + val.as_builtin().basic_as_string() + ) + } + _ => write!(f, "{}({}, {})", op.to_string(), lhs, rhs), + }, + RangeOp::BitNot => { + write!(f, "~{}", lhs) + } + _ => write!(f, "({} {} {})", lhs, op.to_string(), rhs), + }, + Elem::Arena(idx) => write!(f, "arena_idx_{idx}"), + Elem::Null => write!(f, ""), + } + } +} diff --git a/crates/graph/src/range/elem/elem_trait.rs b/crates/graph/src/range/elem/elem_trait.rs index 7f5c13b3..c4bfcd28 100644 --- a/crates/graph/src/range/elem/elem_trait.rs +++ b/crates/graph/src/range/elem/elem_trait.rs @@ -1,62 +1,99 @@ -use crate::{ - nodes::ContextVarNode, - range::elem::{Elem, RangeExpr, RangeOp}, - GraphBackend, GraphError, -}; +use crate::{nodes::ContextVarNode, range::elem::Elem, GraphBackend, GraphError}; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; +use std::hash::Hash; -pub trait RangeElem { +pub trait RangeElem: Hash { type GraphError; /// Flattens an element into an expression or concrete based purely on inputs, calldata, storage, or environment data variables fn flatten( &self, maximize: bool, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, Self::GraphError>; /// Returns whether `cache_flatten` has been called - fn is_flatten_cached(&self, analyzer: &impl GraphBackend) -> bool; - fn is_min_max_cached(&self, analyzer: &impl GraphBackend) -> (bool, bool); + fn is_flatten_cached( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> bool; + fn is_min_max_cached( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> (bool, bool); /// Flattens an element and caches the result - fn cache_flatten(&mut self, analyzer: &mut impl GraphBackend) -> Result<(), Self::GraphError>; + fn cache_flatten( + &mut self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), Self::GraphError>; /// Tries to evaluate a range element down to a concrete or maximally simplified expression to its maximum value - fn maximize(&self, analyzer: &impl GraphBackend) -> Result, Self::GraphError>; + fn maximize( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, Self::GraphError>; /// Maximizes the element and caches the result for quicker use later - fn cache_maximize(&mut self, analyzer: &mut impl GraphBackend) -> Result<(), Self::GraphError>; + fn cache_maximize( + &mut self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), Self::GraphError>; /// Tries to evaluate a range element down to a concrete or maximally simplified expression to its minimum value - fn minimize(&self, analyzer: &impl GraphBackend) -> Result, Self::GraphError>; + fn minimize( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, Self::GraphError>; /// Minimizes the element and caches the result for quicker use later - fn cache_minimize(&mut self, analyzer: &mut impl GraphBackend) -> Result<(), Self::GraphError>; + fn cache_minimize( + &mut self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), Self::GraphError>; /// Uncaches the minimum and maximum fn uncache(&mut self); /// Tries to simplify to maximum(i.e.: leaves symbolic/dynamic values as they are) - fn simplify_maximize(&self, analyzer: &impl GraphBackend) -> Result, Self::GraphError>; + fn simplify_maximize( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, Self::GraphError>; /// Tries to simplify to minimum (i.e.: leaves symbolic/dynamic values as they are) - fn simplify_minimize(&self, analyzer: &impl GraphBackend) -> Result, Self::GraphError>; + fn simplify_minimize( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, Self::GraphError>; /// Checks if two range elements are equal - fn range_eq(&self, other: &Self, analyzer: &impl GraphBackend) -> bool; + fn range_eq(&self, other: &Self, arena: &mut RangeArena>) -> bool; /// Tries to compare the ordering of two range elements - fn range_ord(&self, other: &Self, analyzer: &impl GraphBackend) -> Option; - /// Constructs a range `Elem::Expr` given a lhs, rhs, and operation ([`RangeOp`]). - fn range_op(lhs: Elem, rhs: Elem, op: RangeOp, _analyzer: &impl GraphBackend) -> Elem - where - Self: Sized, - { - Elem::Expr(RangeExpr::new(lhs, op, rhs)) - } + fn range_ord( + &self, + other: &Self, + arena: &mut RangeArena>, + ) -> Option; /// Traverses the range expression and finds all nodes that are dynamically pointed to /// and returns it in a vector. - fn dependent_on(&self, analyzer: &impl GraphBackend) -> Vec; + fn dependent_on( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Vec; fn recursive_dependent_on( &self, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, Self::GraphError>; fn has_cycle( &self, seen: &mut Vec, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result; fn depends_on( @@ -64,6 +101,7 @@ pub trait RangeElem { var: ContextVarNode, seen: &mut Vec, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result; /// Attempts to replace range elements that form a cyclic dependency by replacing /// it with a new node. Ideally no cyclic dependencies occur in ranges as of now @@ -77,7 +115,12 @@ pub trait RangeElem { node_idx: NodeIdx, new_idx: NodeIdx, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ); - fn arenaize(&mut self, analyzer: &mut impl GraphBackend) -> Result<(), GraphError>; + fn arenaize( + &mut self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError>; } diff --git a/crates/graph/src/range/elem/expr.rs b/crates/graph/src/range/elem/expr.rs deleted file mode 100644 index 797a3ece..00000000 --- a/crates/graph/src/range/elem/expr.rs +++ /dev/null @@ -1,1213 +0,0 @@ -use crate::{ - nodes::{Concrete, ContextVarNode}, - range::{ - elem::{Elem, MinMaxed, RangeElem, RangeOp}, - exec_traits::*, - }, - GraphBackend, GraphError, -}; -use std::hash::Hash; -use std::hash::Hasher; - -use ethers_core::types::U256; -use shared::NodeIdx; - -pub static SINGLETON_EQ_OPS: &[RangeOp] = &[ - RangeOp::Eq, - RangeOp::Neq, - RangeOp::Lt, - RangeOp::Lte, - RangeOp::Gt, - RangeOp::Gte, -]; - -pub static EQ_OPS: &[RangeOp] = &[ - RangeOp::Eq, - RangeOp::Neq, - RangeOp::Lt, - RangeOp::Lte, - RangeOp::Gt, - RangeOp::Gte, - RangeOp::And, - RangeOp::Or, -]; - -pub static FLIP_INEQ_OPS: &[RangeOp] = &[RangeOp::Lt, RangeOp::Lte, RangeOp::Gt, RangeOp::Gte]; - -/// A range expression composed of other range [`Elem`] -#[derive(Clone, Debug, Ord, PartialOrd)] -pub struct RangeExpr { - pub maximized: Option>, - pub minimized: Option>, - pub flattened_min: Option>>, - pub flattened_max: Option>>, - pub lhs: Box>, - pub op: RangeOp, - pub rhs: Box>, -} - -impl PartialEq for RangeExpr { - fn eq(&self, other: &Self) -> bool { - self.lhs == other.lhs && self.rhs == other.rhs && self.op == other.op - } -} -impl Eq for RangeExpr {} - -impl Hash for RangeExpr { - fn hash(&self, state: &mut H) { - (*self.lhs).hash(state); - self.op.hash(state); - (*self.rhs).hash(state); - } -} - -impl RangeExpr { - pub fn is_noop(&self) -> (bool, usize) { - let one = Elem::from(Concrete::from(U256::one())); - let zero = Elem::from(Concrete::from(U256::zero())); - match self.op { - RangeOp::Mul(_) | RangeOp::Div(_) => { - if *self.lhs == one { - (true, 0) - } else if *self.rhs == one { - (true, 1) - } else { - (false, 0) - } - } - RangeOp::Add(_) | RangeOp::Sub(_) => { - if *self.lhs == zero { - (true, 0) - } else if *self.rhs == zero { - (true, 1) - } else { - (false, 0) - } - } - _ => (false, 0), - } - } - - pub fn inverse_if_boolean(&self) -> Option { - if EQ_OPS.contains(&self.op) { - if SINGLETON_EQ_OPS.contains(&self.op) { - let mut new_self = self.clone(); - new_self.uncache(); - new_self.op = new_self.op.logical_inverse()?; - Some(new_self) - } else { - // non-singleton, i.e. AND or OR - let mut new_self = self.clone(); - new_self.uncache(); - new_self.op = new_self.op.inverse()?; - if let Some(new_lhs) = new_self.inverse_if_boolean() { - *new_self.lhs = Elem::Expr(new_lhs); - } - if let Some(new_rhs) = new_self.inverse_if_boolean() { - *new_self.rhs = Elem::Expr(new_rhs); - } - Some(new_self) - } - } else { - None - } - } - - pub fn recurse_dearenaize(&self, analyzer: &impl GraphBackend) -> Elem { - Elem::Expr(Self::new( - self.lhs.recurse_dearenaize(analyzer).clone(), - self.op, - self.rhs.recurse_dearenaize(analyzer).clone(), - )) - } - - pub fn arena_idx(&self, analyzer: &impl GraphBackend) -> Option { - let expr = Elem::Expr(RangeExpr::new( - Elem::Arena(analyzer.range_arena_idx(&self.lhs)?), - self.op, - Elem::Arena(analyzer.range_arena_idx(&self.rhs)?), - )); - analyzer.range_arena_idx(&expr) - } - - pub fn arenaized_cache( - &self, - max: bool, - analyzer: &impl GraphBackend, - ) -> Option> { - if let Some(idx) = self.arena_idx(analyzer) { - let Ok(t) = analyzer.range_arena().ranges[idx].try_borrow() else { - return None; - }; - let Elem::Expr(ref arenaized) = *t else { - return None; - }; - return if max { - arenaized.maximized.clone() - } else { - arenaized.minimized.clone() - }; - } - None - } - - pub fn arenaized_flat_cache( - &self, - max: bool, - analyzer: &impl GraphBackend, - ) -> Option>> { - if let Some(idx) = self.arena_idx(analyzer) { - let Ok(t) = analyzer.range_arena().ranges[idx].try_borrow() else { - return None; - }; - let Elem::Expr(ref arenaized) = *t else { - return None; - }; - return if max { - arenaized.flattened_max.clone() - } else { - arenaized.flattened_min.clone() - }; - } - None - } - - pub fn set_arenaized_flattened( - &self, - max: bool, - elem: Elem, - analyzer: &impl GraphBackend, - ) { - if let Some(idx) = self.arena_idx(analyzer) { - if let Ok(mut t) = analyzer.range_arena().ranges[idx].try_borrow_mut() { - let Elem::Expr(arenaized) = &mut *t else { - return; - }; - - if max { - arenaized.flattened_max = Some(Box::new(elem)); - } else { - arenaized.flattened_min = Some(Box::new(elem)); - } - } - } - } -} - -impl RangeExpr { - /// Creates a new range expression given a left hand side range [`Elem`], a [`RangeOp`], and a a right hand side range [`Elem`]. - pub fn new(lhs: Elem, op: RangeOp, rhs: Elem) -> RangeExpr { - RangeExpr { - maximized: None, - minimized: None, - flattened_max: None, - flattened_min: None, - lhs: Box::new(lhs), - op, - rhs: Box::new(rhs), - } - } - - pub fn contains_node(&self, node_idx: NodeIdx) -> bool { - self.lhs.contains_node(node_idx) || self.rhs.contains_node(node_idx) - } -} - -impl RangeElem for RangeExpr { - type GraphError = GraphError; - - #[tracing::instrument(level = "trace", skip_all)] - fn arenaize(&mut self, analyzer: &mut impl GraphBackend) -> Result<(), GraphError> { - // self.lhs.clone().arenaize(analyzer)?; - // self.rhs.clone().arenaize(analyzer)?; - if self.arena_idx(analyzer).is_none() { - let lhs = std::mem::take(&mut self.lhs); - let rhs = std::mem::take(&mut self.rhs); - self.lhs = Box::new(Elem::Arena(analyzer.range_arena_idx_or_upsert(*lhs))); - self.rhs = Box::new(Elem::Arena(analyzer.range_arena_idx_or_upsert(*rhs))); - let _ = analyzer.range_arena_idx_or_upsert(Elem::Expr(self.clone())); - } - Ok(()) - } - - fn range_eq(&self, _other: &Self, _analyzer: &impl GraphBackend) -> bool { - false - } - - #[tracing::instrument(level = "trace", skip_all)] - fn flatten( - &self, - maximize: bool, - analyzer: &impl GraphBackend, - ) -> Result, GraphError> { - match (maximize, &self.flattened_min, &self.flattened_max) { - (true, _, Some(flat)) | (false, Some(flat), _) => { - return Ok(*flat.clone()); - } - _ => {} - } - - if let Some(arenaized) = self.arenaized_flat_cache(maximize, analyzer) { - return Ok(*arenaized); - } - - Ok(Elem::Expr(RangeExpr::new( - self.lhs.flatten(maximize, analyzer)?, - self.op, - self.rhs.flatten(maximize, analyzer)?, - ))) - } - - fn is_flatten_cached(&self, analyzer: &impl GraphBackend) -> bool { - self.flattened_min.is_some() && self.flattened_max.is_some() || { - if let Some(idx) = self.arena_idx(analyzer) { - if let Ok(t) = analyzer.range_arena().ranges[idx].try_borrow() { - if let Elem::Expr(ref arenaized) = *t { - arenaized.flattened_min.is_some() && arenaized.flattened_max.is_some() - } else { - false - } - } else { - false - } - } else { - false - } - } - } - - fn is_min_max_cached(&self, analyzer: &impl GraphBackend) -> (bool, bool) { - let (arena_cached_min, arena_cached_max) = { - if let Some(idx) = self.arena_idx(analyzer) { - if let Ok(t) = analyzer.range_arena().ranges[idx].try_borrow() { - if let Elem::Expr(ref arenaized) = *t { - (arenaized.minimized.is_some(), arenaized.maximized.is_some()) - } else { - (false, false) - } - } else { - (false, false) - } - } else { - (false, false) - } - }; - ( - self.minimized.is_some() || arena_cached_min, - self.maximized.is_some() || arena_cached_max, - ) - } - - fn range_ord( - &self, - _other: &Self, - _analyzer: &impl GraphBackend, - ) -> Option { - todo!() - } - - fn dependent_on(&self, analyzer: &impl GraphBackend) -> Vec { - let mut deps = self.lhs.dependent_on(analyzer); - deps.extend(self.rhs.dependent_on(analyzer)); - deps - } - - #[tracing::instrument(level = "trace", skip_all)] - fn recursive_dependent_on( - &self, - analyzer: &impl GraphBackend, - ) -> Result, GraphError> { - let mut deps = self.lhs.recursive_dependent_on(analyzer)?; - deps.extend(self.rhs.recursive_dependent_on(analyzer)?); - Ok(deps) - } - - #[tracing::instrument(level = "trace", skip_all)] - fn has_cycle( - &self, - seen: &mut Vec, - analyzer: &impl GraphBackend, - ) -> Result { - let lhs_has_cycle = self.lhs.has_cycle(seen, analyzer)?; - let rhs_has_cycle = self.rhs.has_cycle(seen, analyzer)?; - Ok(lhs_has_cycle || rhs_has_cycle) - } - - fn depends_on( - &self, - var: ContextVarNode, - seen: &mut Vec, - analyzer: &impl GraphBackend, - ) -> Result { - let lhs_deps_on = self.lhs.depends_on(var, seen, analyzer)?; - let rhs_deps_on = self.rhs.depends_on(var, seen, analyzer)?; - Ok(lhs_deps_on || rhs_deps_on) - } - - #[tracing::instrument(level = "trace", skip_all)] - fn filter_recursion( - &mut self, - node_idx: NodeIdx, - new_idx: NodeIdx, - analyzer: &mut impl GraphBackend, - ) { - let _ = self.arenaize(analyzer); - self.lhs.filter_recursion(node_idx, new_idx, analyzer); - self.rhs.filter_recursion(node_idx, new_idx, analyzer); - } - - #[tracing::instrument(level = "trace", skip_all)] - fn maximize(&self, analyzer: &impl GraphBackend) -> Result, GraphError> { - if let Some(MinMaxed::Maximized(cached)) = self.maximized.clone() { - Ok(*cached) - } else if let Some(MinMaxed::Maximized(cached)) = self.arenaized_cache(true, analyzer) { - Ok(*cached) - } else if self.op == RangeOp::SetIndices { - self.simplify_exec_op(true, analyzer) - } else { - self.exec_op(true, analyzer) - } - } - - #[tracing::instrument(level = "trace", skip_all)] - fn minimize(&self, analyzer: &impl GraphBackend) -> Result, GraphError> { - if let Some(MinMaxed::Minimized(cached)) = self.minimized.clone() { - Ok(*cached) - } else if let Some(MinMaxed::Minimized(cached)) = self.arenaized_cache(false, analyzer) { - Ok(*cached) - } else if self.op == RangeOp::SetIndices { - self.simplify_exec_op(false, analyzer) - } else { - self.exec_op(false, analyzer) - } - } - - #[tracing::instrument(level = "trace", skip_all)] - fn simplify_maximize( - &self, - analyzer: &impl GraphBackend, - ) -> Result, GraphError> { - if let Some(simp_max) = &self.flattened_max { - return Ok(*simp_max.clone()); - } - - if let Some(arenaized) = self.arenaized_flat_cache(true, analyzer) { - return Ok(*arenaized); - } - - let l = self.lhs.simplify_maximize(analyzer)?; - let r = self.rhs.simplify_maximize(analyzer)?; - let collapsed = collapse(&l, self.op, &r, analyzer); - let res = match collapsed { - MaybeCollapsed::Concretes(..) => RangeExpr::new(l, self.op, r).exec_op(true, analyzer), - MaybeCollapsed::Collapsed(collapsed) => Ok(collapsed), - MaybeCollapsed::Not(..) => { - // Ok(Elem::Expr(RangeExpr::new(l, self.op, r)))//.simplify_exec_op(false, &mut vec![], analyzer) - let res = RangeExpr::new(l, self.op, r).simplify_exec_op(true, analyzer)?; - match res { - Elem::Expr(expr) => { - match collapse(&expr.lhs, expr.op, &expr.rhs, analyzer) { - MaybeCollapsed::Concretes(..) => return expr.exec_op(true, analyzer), - MaybeCollapsed::Collapsed(collapsed) => return Ok(collapsed), - _ => {} - } - Ok(Elem::Expr(expr)) - } - other => Ok(other), - } - } - }?; - self.set_arenaized_flattened(true, res.clone(), analyzer); - Ok(res) - } - - #[tracing::instrument(level = "trace", skip_all)] - fn simplify_minimize( - &self, - analyzer: &impl GraphBackend, - ) -> Result, GraphError> { - if let Some(simp_min) = &self.flattened_min { - return Ok(*simp_min.clone()); - } - - if let Some(arenaized) = self.arenaized_flat_cache(false, analyzer) { - return Ok(*arenaized); - } - - let l = self.lhs.simplify_minimize(analyzer)?; - self.lhs.set_arenaized_flattened(false, &l, analyzer); - let r = self.rhs.simplify_minimize(analyzer)?; - self.rhs.set_arenaized_flattened(false, &r, analyzer); - - let collapsed = collapse(&l, self.op, &r, analyzer); - let res = match collapsed { - MaybeCollapsed::Concretes(..) => RangeExpr::new(l, self.op, r).exec_op(false, analyzer), - MaybeCollapsed::Collapsed(collapsed) => Ok(collapsed), - MaybeCollapsed::Not(..) => { - let res = RangeExpr::new(l, self.op, r).simplify_exec_op(false, analyzer)?; - match res { - Elem::Expr(expr) => { - match collapse(&expr.lhs, expr.op, &expr.rhs, analyzer) { - MaybeCollapsed::Concretes(..) => return expr.exec_op(false, analyzer), - MaybeCollapsed::Collapsed(collapsed) => return Ok(collapsed), - _ => {} - } - Ok(Elem::Expr(expr)) - } - other => Ok(other), - } - } - }?; - - self.set_arenaized_flattened(false, res.clone(), analyzer); - Ok(res) - } - - #[tracing::instrument(level = "trace", skip_all)] - fn cache_flatten(&mut self, g: &mut impl GraphBackend) -> Result<(), GraphError> { - self.arenaize(g)?; - - fn simplify_minimize( - mut this: Elem, - analyzer: &mut impl GraphBackend, - ) -> Result, GraphError> { - let Elem::Expr(this) = this else { - this.cache_flatten(analyzer)?; - if let Some(t) = this.arenaized_flattened(false, analyzer) { - return Ok(*t); - } else { - return Ok(this.clone()); - } - }; - - if let Some(simp_min) = &this.flattened_min { - return Ok(*simp_min.clone()); - } - - if let Some(arenaized) = this.arenaized_flat_cache(false, analyzer) { - return Ok(*arenaized); - } - - let l = this.lhs.simplify_minimize(analyzer)?; - let r = this.rhs.simplify_minimize(analyzer)?; - let collapsed = collapse(&l, this.op, &r, analyzer); - let res = match collapsed { - MaybeCollapsed::Concretes(..) => { - RangeExpr::new(l, this.op, r).exec_op(false, analyzer) - } - MaybeCollapsed::Collapsed(collapsed) => Ok(collapsed), - MaybeCollapsed::Not(..) => { - let res = RangeExpr::new(l, this.op, r).simplify_exec_op(false, analyzer)?; - - let idx = analyzer.range_arena_idx_or_upsert(res.clone()); - match res { - Elem::Expr(expr) => { - match collapse(&expr.lhs, expr.op, &expr.rhs, analyzer) { - MaybeCollapsed::Concretes(..) => { - let exec_res = expr.exec_op(false, analyzer)?; - Elem::Arena(idx) - .set_arenaized_flattened(false, &exec_res, analyzer); - return Ok(exec_res); - } - MaybeCollapsed::Collapsed(collapsed) => { - Elem::Arena(idx) - .set_arenaized_flattened(false, &collapsed, analyzer); - return Ok(collapsed); - } - _ => {} - } - Ok(Elem::Expr(expr)) - } - other => Ok(other), - } - } - }?; - - this.set_arenaized_flattened(false, res.clone(), analyzer); - Ok(res) - } - - fn simplify_maximize( - mut this: Elem, - analyzer: &mut impl GraphBackend, - ) -> Result, GraphError> { - let Elem::Expr(this) = this else { - this.cache_flatten(analyzer)?; - if let Some(t) = this.arenaized_flattened(true, analyzer) { - return Ok(*t); - } else { - return Ok(this.clone()); - } - }; - - if let Some(simp_min) = &this.flattened_max { - return Ok(*simp_min.clone()); - } - - if let Some(arenaized) = this.arenaized_flat_cache(false, analyzer) { - return Ok(*arenaized); - } - - let l = this.lhs.simplify_maximize(analyzer)?; - let r = this.rhs.simplify_maximize(analyzer)?; - let collapsed = collapse(&l, this.op, &r, analyzer); - let res = match collapsed { - MaybeCollapsed::Concretes(..) => { - RangeExpr::new(l, this.op, r).exec_op(true, analyzer) - } - MaybeCollapsed::Collapsed(collapsed) => Ok(collapsed), - MaybeCollapsed::Not(..) => { - let res = RangeExpr::new(l, this.op, r).simplify_exec_op(true, analyzer)?; - - let idx = analyzer.range_arena_idx_or_upsert(res.clone()); - match res { - Elem::Expr(expr) => { - match collapse(&expr.lhs, expr.op, &expr.rhs, analyzer) { - MaybeCollapsed::Concretes(..) => { - let exec_res = expr.exec_op(true, analyzer)?; - Elem::Arena(idx) - .set_arenaized_flattened(true, &exec_res, analyzer); - return Ok(exec_res); - } - MaybeCollapsed::Collapsed(collapsed) => { - Elem::Arena(idx) - .set_arenaized_flattened(true, &collapsed, analyzer); - return Ok(collapsed); - } - _ => {} - } - Ok(Elem::Expr(expr)) - } - other => Ok(other), - } - } - }?; - - this.set_arenaized_flattened(false, res.clone(), analyzer); - Ok(res) - } - - if self.flattened_max.is_none() { - if let Some(idx) = self.arena_idx(g) { - if let Elem::Expr(ref arenaized) = *g.range_arena().ranges[idx].borrow() { - if arenaized.flattened_max.is_some() { - return Ok(()); - } - }; - } else { - self.arenaize(g)?; - } - - self.lhs.cache_flatten(g)?; - self.rhs.cache_flatten(g)?; - // self.arenaize(g)?; - let flat_max = self.flatten(true, g)?; - let simplified_flat_max = simplify_maximize(flat_max, g)?; - simplified_flat_max.clone().arenaize(g)?; - self.flattened_max = Some(Box::new(simplified_flat_max)); - } - - if self.flattened_min.is_none() { - if let Some(idx) = self.arena_idx(g) { - if let Elem::Expr(ref arenaized) = *g.range_arena().ranges[idx].borrow() { - if arenaized.flattened_min.is_some() { - return Ok(()); - } - }; - } else { - self.arenaize(g)?; - } - - self.lhs.cache_flatten(g)?; - self.rhs.cache_flatten(g)?; - // self.arenaize(g)?; - let flat_min = self.flatten(false, g)?; - let simplified_flat_min = simplify_minimize(flat_min, g)?; - simplified_flat_min.clone().arenaize(g)?; - self.flattened_min = Some(Box::new(simplified_flat_min)); - } - Ok(()) - } - - #[tracing::instrument(level = "trace", skip_all)] - fn cache_maximize(&mut self, g: &mut impl GraphBackend) -> Result<(), GraphError> { - tracing::trace!("cache maximizing: {}", Elem::Expr(self.clone())); - self.arenaize(g)?; - if self.maximized.is_none() { - self.lhs.cache_maximize(g)?; - self.rhs.cache_maximize(g)?; - self.cache_exec_op(true, g)?; - } - Ok(()) - } - - #[tracing::instrument(level = "trace", skip_all)] - fn cache_minimize(&mut self, g: &mut impl GraphBackend) -> Result<(), GraphError> { - tracing::trace!("cache minimizing: {}", Elem::Expr(self.clone())); - self.arenaize(g)?; - if self.minimized.is_none() { - self.lhs.cache_minimize(g)?; - self.rhs.cache_minimize(g)?; - self.cache_exec_op(false, g)?; - } - Ok(()) - } - - fn uncache(&mut self) { - self.uncache_exec(); - } -} - -pub enum MaybeCollapsed<'a, 'b> { - Concretes(&'a Elem, &'b Elem), - Collapsed(Elem), - Not(&'a Elem, &'b Elem), -} - -pub fn collapse<'a, 'b, 'c: 'a + 'b>( - l: &'a Elem, - op: RangeOp, - r: &'b Elem, - analyzer: &'c impl GraphBackend, -) -> MaybeCollapsed<'a, 'b> { - let zero = Elem::from(Concrete::from(U256::zero())); - let one = Elem::from(Concrete::from(U256::one())); - match (l, r) { - (Elem::Arena(_), r) => { - if let Ok(t) = l.dearenaize(analyzer).try_borrow() { - match collapse(&t, op, r, analyzer) { - MaybeCollapsed::Not(..) => MaybeCollapsed::Not(l, r), - MaybeCollapsed::Concretes(..) => MaybeCollapsed::Concretes(l, r), - MaybeCollapsed::Collapsed(e) => MaybeCollapsed::Collapsed(e), - } - } else { - MaybeCollapsed::Not(l, r) - } - } - (l, Elem::Arena(_)) => { - if let Ok(t) = r.dearenaize(analyzer).try_borrow() { - match collapse(l, op, &t, analyzer) { - MaybeCollapsed::Not(..) => MaybeCollapsed::Not(l, r), - MaybeCollapsed::Concretes(..) => MaybeCollapsed::Concretes(l, r), - MaybeCollapsed::Collapsed(e) => MaybeCollapsed::Collapsed(e), - } - } else { - MaybeCollapsed::Not(l, r) - } - } - (Elem::Concrete(_), Elem::Concrete(_)) => MaybeCollapsed::Concretes(l, r), - (Elem::Expr(expr), d @ Elem::Reference(_)) => { - // try to collapse the expression - let x = &*expr.lhs; - let y = &*expr.rhs; - let z = d; - - let x_ord_z = x.range_ord(z, analyzer); - let x_eq_z = matches!(x_ord_z, Some(std::cmp::Ordering::Equal)); - - let y_ord_z = y.range_ord(z, analyzer); - let y_eq_z = matches!(y_ord_z, Some(std::cmp::Ordering::Equal)); - - let y_eq_zero = matches!( - y.range_ord(&zero, analyzer), - Some(std::cmp::Ordering::Equal) | None - ); - let x_eq_zero = matches!( - x.range_ord(&zero, analyzer), - Some(std::cmp::Ordering::Equal) | None - ); - let y_eq_one = matches!( - y.range_ord(&one, analyzer), - Some(std::cmp::Ordering::Equal) | None - ); - let x_eq_one = matches!( - x.range_ord(&one, analyzer), - Some(std::cmp::Ordering::Equal) | None - ); - match (expr.op, op) { - (RangeOp::Sub(_), RangeOp::Eq) | (RangeOp::Div(_), RangeOp::Eq) => { - if x_eq_z && !y_eq_zero { - // (x -|/ k) == x ==> false - MaybeCollapsed::Collapsed(Elem::from(Concrete::from(false))) - } else { - MaybeCollapsed::Not(l, r) - } - } - (RangeOp::Add(_), RangeOp::Eq) => { - if (x_eq_z && !y_eq_zero) || (y_eq_z && !x_eq_zero) { - // (x +|* k) == x ==> false - MaybeCollapsed::Collapsed(Elem::from(Concrete::from(false))) - } else { - MaybeCollapsed::Not(l, r) - } - } - (RangeOp::Mul(_), RangeOp::Eq) => { - if (x_eq_z && !y_eq_one) || (y_eq_z && !x_eq_one) { - // (x +|* k) == x ==> false - MaybeCollapsed::Collapsed(Elem::from(Concrete::from(false))) - } else { - MaybeCollapsed::Not(l, r) - } - } - _ => MaybeCollapsed::Not(l, r), - } - } - // if we have an expression, it fundamentally must have a dynamic in it - (Elem::Expr(expr), c @ Elem::Concrete(_)) => { - // potentially collapsible - let x = &*expr.lhs; - let y = &*expr.rhs; - let z = c; - - match (expr.op, op) { - (RangeOp::Sub(false), RangeOp::Min) => { - // min{x - y, z} - // if x <= z, then x - y will be the minimum if y >= 0 - if matches!( - x.range_ord(z, analyzer), - Some(std::cmp::Ordering::Equal) | Some(std::cmp::Ordering::Less) - ) && matches!( - y.range_ord(&zero, analyzer), - Some(std::cmp::Ordering::Equal) | Some(std::cmp::Ordering::Greater) - ) { - MaybeCollapsed::Collapsed(l.clone()) - } else { - MaybeCollapsed::Not(l, r) - } - } - (RangeOp::Add(false), RangeOp::Max) => { - // max{x + y, z} - // if x >= z, then x + y will be the maximum if y >= 0 - // or if y >= z, then x + y will be the maximum if x >= 0 - if (matches!( - x.range_ord(z, analyzer), - Some(std::cmp::Ordering::Equal) | Some(std::cmp::Ordering::Greater) - ) && matches!( - y.range_ord(&zero, analyzer), - Some(std::cmp::Ordering::Equal) | Some(std::cmp::Ordering::Greater) - )) || (matches!( - y.range_ord(z, analyzer), - Some(std::cmp::Ordering::Equal) | Some(std::cmp::Ordering::Greater) - ) && matches!( - x.range_ord(&zero, analyzer), - Some(std::cmp::Ordering::Equal) | Some(std::cmp::Ordering::Greater) - )) { - MaybeCollapsed::Collapsed(l.clone()) - } else { - MaybeCollapsed::Not(l, r) - } - } - (RangeOp::Eq, RangeOp::Eq) => { - // ((x == y) == z) - // can skip if x and z eq - if let Some(std::cmp::Ordering::Equal) = x.range_ord(z, analyzer) { - MaybeCollapsed::Collapsed(l.clone()) - } else if let Some(std::cmp::Ordering::Equal) = y.range_ord(z, analyzer) { - MaybeCollapsed::Collapsed(l.clone()) - } else if z.range_eq(&Elem::from(Concrete::from(true)), analyzer) { - MaybeCollapsed::Collapsed(l.clone()) - } else { - MaybeCollapsed::Not(l, r) - } - } - (RangeOp::Add(l_op), RangeOp::Add(r_op)) => { - // ((x + y) + z) - let op_fn = if l_op && r_op { - // unchecked - RangeAdd::range_wrapping_add - } else { - // checked - as RangeAdd>::range_add - }; - if let Some(new) = op_fn(x, z) { - MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new(y.clone(), op, new))) - } else if let Some(new) = op_fn(y, z) { - MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new(x.clone(), op, new))) - } else { - MaybeCollapsed::Not(l, r) - } - } - (RangeOp::Add(l_op), RangeOp::Sub(r_op)) => { - // ((x + y) - z) => k - y || x + k - if l_op == r_op { - match y.range_ord(z, analyzer) { - Some(std::cmp::Ordering::Equal) | Some(std::cmp::Ordering::Greater) => { - // y and z are concrete && y >= z ==> x + (y - z) - let op_fn = if l_op { - // unchecked - RangeSub::range_wrapping_sub - } else { - // checked - as RangeSub>::range_sub - }; - if let Some(new) = op_fn(y, z) { - let new_expr = - Elem::Expr(RangeExpr::new(x.clone(), expr.op, new)); - MaybeCollapsed::Collapsed(new_expr) - } else { - MaybeCollapsed::Not(l, r) - } - } - Some(std::cmp::Ordering::Less) => { - // y and z are concrete && y < z ==> x - (z - y) - let op_fn = if l_op { - // unchecked - RangeSub::range_wrapping_sub - } else { - // checked - as RangeSub>::range_sub - }; - if let Some(new) = op_fn(z, y) { - MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( - x.clone(), - RangeOp::Sub(l_op), - new, - ))) - } else { - MaybeCollapsed::Not(l, r) - } - } - None => { - // x and z are concrete, if x >= z, just do (x - z) + y - // else do (y - (z - x)) - match x.range_ord(z, analyzer) { - Some(std::cmp::Ordering::Equal) - | Some(std::cmp::Ordering::Greater) => { - let op_fn = if l_op { - // unchecked - RangeSub::range_wrapping_sub - } else { - // checked - as RangeSub>::range_sub - }; - if let Some(new) = op_fn(y, z) { - MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( - x.clone(), - expr.op, - new, - ))) - } else { - MaybeCollapsed::Not(l, r) - } - } - Some(std::cmp::Ordering::Less) => { - // (y - (z - x)) because z > x, therefore its (-k + y) ==> (y - k) where k = (x - z) - let op_fn = if l_op { - // unchecked - RangeSub::range_wrapping_sub - } else { - // checked - as RangeSub>::range_sub - }; - if let Some(new) = op_fn(z, x) { - MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( - y.clone(), - RangeOp::Sub(l_op), - new, - ))) - } else { - MaybeCollapsed::Not(l, r) - } - } - None => MaybeCollapsed::Not(l, r), - } - } - } - } else { - MaybeCollapsed::Not(l, r) - } - } - (RangeOp::Sub(l_op), RangeOp::Add(r_op)) => { - // ((x - y) + z) => k - y || x + k - if l_op == r_op { - match y.range_ord(z, analyzer) { - Some(std::cmp::Ordering::Equal) | Some(std::cmp::Ordering::Greater) => { - // y and z are concrete && z <= y ==> x - (y - z) - let op_fn = if l_op { - // unchecked - RangeSub::range_wrapping_sub - } else { - // checked - as RangeSub>::range_sub - }; - if let Some(new) = op_fn(y, z) { - MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( - x.clone(), - expr.op, - new, - ))) - } else { - MaybeCollapsed::Not(l, r) - } - } - Some(std::cmp::Ordering::Less) => { - // y and z are concrete && y < z ==> x + (z - y) - let op_fn = if l_op { - // unchecked - RangeSub::range_wrapping_sub - } else { - // checked - as RangeSub>::range_sub - }; - if let Some(new) = op_fn(z, y) { - MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( - x.clone(), - RangeOp::Add(l_op), - new, - ))) - } else { - MaybeCollapsed::Not(l, r) - } - } - None => { - // x and z are concrete, just add them ==> (x + z) - y - let op_fn = if l_op { - // unchecked - RangeAdd::range_wrapping_add - } else { - // checked - as RangeAdd>::range_add - }; - if let Some(new) = op_fn(x, z) { - MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( - new, - expr.op, - y.clone(), - ))) - } else { - MaybeCollapsed::Not(l, r) - } - } - } - } else { - MaybeCollapsed::Not(l, r) - } - } - (RangeOp::Mul(l_op), RangeOp::Mul(r_op)) => { - // ((x * y) * z) - if l_op == r_op { - let op_fn = if l_op { - // unchecked - RangeMul::range_wrapping_mul - } else { - // checked - as RangeMul>::range_mul - }; - if let Some(new) = op_fn(x, z) { - MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( - y.clone(), - op, - new, - ))) - } else if let Some(new) = op_fn(y, z) { - MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( - x.clone(), - op, - new, - ))) - } else { - MaybeCollapsed::Not(l, r) - } - } else { - MaybeCollapsed::Not(l, r) - } - } - (RangeOp::Add(wrapping), op) if EQ_OPS.contains(&op) => { - let const_op = if wrapping { - RangeSub::range_wrapping_sub - } else { - RangeSub::range_sub - }; - // ((x + y) == z) => (x == (z - y)) || (y == (z - x)) - // .. - // ((x + y) != z) => (x != (z - y)) || (y != (z - x)) - if let Some(new) = const_op(z, y) { - MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new(y.clone(), op, new))) - } else if let Some(new) = const_op(z, x) { - MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new(x.clone(), op, new))) - } else { - MaybeCollapsed::Not(l, r) - } - } - (RangeOp::Sub(wrapping), op) if EQ_OPS.contains(&op) => { - let op_y = if wrapping { - as RangeAdd>::range_wrapping_add - } else { - as RangeAdd>::range_add - }; - let op_x = if wrapping { - as RangeSub>::range_wrapping_sub - } else { - as RangeSub>::range_sub - }; - // ((x - y) == z) => (x == (z + y)) || (y == (x - z)) - // ((x - y) != z) => (x != (z + y)) || (y != (x - z)) - if let Some(new) = op_x(x, z) { - MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new(y.clone(), op, new))) - } else if let Some(new) = op_y(y, z) { - MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new(x.clone(), op, new))) - } else { - MaybeCollapsed::Not(l, r) - } - } - (RangeOp::Mul(wrapping), op) if EQ_OPS.contains(&op) => { - let div_op = if wrapping { - RangeDiv::range_wrapping_div - } else { - RangeDiv::range_div - }; - // ((x * y) == z) => (x == (z / y)) || (y == (z / x)) - // ((x * y) != z) => (x != (z / y)) || (y != (z / x)) - if let Some(new) = div_op(z, x) { - let new_op = if matches!( - x.range_ord(&zero, analyzer), - Some(std::cmp::Ordering::Less) - ) && FLIP_INEQ_OPS.contains(&op) - { - op.inverse().unwrap() - } else { - op - }; - MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( - y.clone(), - new_op, - new, - ))) - } else if let Some(new) = div_op(z, y) { - let new_op = if matches!( - y.range_ord(&zero, analyzer), - Some(std::cmp::Ordering::Less) - ) && FLIP_INEQ_OPS.contains(&op) - { - op.inverse().unwrap() - } else { - op - }; - MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( - x.clone(), - new_op, - new, - ))) - } else { - MaybeCollapsed::Not(l, r) - } - } - (RangeOp::Div(wrapping), op) if EQ_OPS.contains(&op) => { - let mul_op = if wrapping { - as RangeMul>::range_wrapping_mul - } else { - as RangeMul>::range_mul - }; - let div_op = if wrapping { - as RangeDiv>::range_wrapping_div - } else { - as RangeDiv>::range_div - }; - - // ((x / y) == z) => (x == (z * y)) || (y == (x / z)) - // .. - // ((x / y) != z) => (x != (z / y)) || (y != (x / z)) - if let Some(new) = mul_op(z, y) { - let new_op = if matches!( - y.range_ord(&zero, analyzer), - Some(std::cmp::Ordering::Less) - ) && FLIP_INEQ_OPS.contains(&op) - { - op.inverse().unwrap() - } else { - op - }; - MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( - x.clone(), - new_op, - new, - ))) - } else if !FLIP_INEQ_OPS.contains(&op) { - if let Some(new) = div_op(x, z) { - // y is the dynamic element - // we cant do flip ops here because we do (x / y) * y >= z * y which is a flip potentially - // but we dont know if y was negative. so we limit to just eq & neq - MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( - y.clone(), - op, - new, - ))) - } else { - MaybeCollapsed::Not(l, r) - } - } else { - MaybeCollapsed::Not(l, r) - } - } - (_, RangeOp::Eq) => { - // (x _ y) == z ==> (x _ y if z == true) - if z.range_eq(&Elem::from(Concrete::from(true)), analyzer) { - MaybeCollapsed::Collapsed(l.clone()) - } else if z.range_eq(&Elem::from(Concrete::from(false)), analyzer) { - // (!x && !y) - match ( - x.inverse_if_boolean(), - y.inverse_if_boolean(), - expr.op.logical_inverse(), - ) { - (Some(new_x), Some(new_y), Some(new_op)) => MaybeCollapsed::Collapsed( - Elem::Expr(RangeExpr::new(new_x, new_op, new_y)), - ), - _ => MaybeCollapsed::Not(l, r), - } - } else { - MaybeCollapsed::Not(l, r) - } - } - (_, RangeOp::Neq) => { - // (x _ y) != z ==> (x _ y if z != false) - if z.range_eq(&Elem::from(Concrete::from(false)), analyzer) { - // != false is == true - MaybeCollapsed::Collapsed(l.clone()) - } else if z.range_eq(&Elem::from(Concrete::from(true)), analyzer) { - // != true is == false, to make it == true, inverse everything - match ( - x.inverse_if_boolean(), - y.inverse_if_boolean(), - expr.op.logical_inverse(), - ) { - (Some(new_x), Some(new_y), Some(new_op)) => MaybeCollapsed::Collapsed( - Elem::Expr(RangeExpr::new(new_x, new_op, new_y)), - ), - _ => MaybeCollapsed::Not(l, r), - } - } else { - MaybeCollapsed::Not(l, r) - } - } - _ => MaybeCollapsed::Not(l, r), - } - } - (Elem::Concrete(_c), Elem::Expr(_expr)) => match collapse(r, op, l, analyzer) { - MaybeCollapsed::Collapsed(inner) => MaybeCollapsed::Collapsed(inner.clone()), - MaybeCollapsed::Not(_, _) => MaybeCollapsed::Not(l, r), - MaybeCollapsed::Concretes(_, _) => unreachable!(), - }, - (le @ Elem::Reference(_), c @ Elem::Concrete(_)) => match op { - RangeOp::Sub(_) | RangeOp::Add(_) => { - if matches!( - c.range_ord(&zero, analyzer), - Some(std::cmp::Ordering::Equal) - ) { - MaybeCollapsed::Collapsed(le.clone()) - } else { - MaybeCollapsed::Not(l, r) - } - } - RangeOp::Mul(_) | RangeOp::Div(_) => { - if matches!(c.range_ord(&one, analyzer), Some(std::cmp::Ordering::Equal)) { - MaybeCollapsed::Collapsed(le.clone()) - } else { - MaybeCollapsed::Not(l, r) - } - } - _ => MaybeCollapsed::Not(l, r), - }, - _ => MaybeCollapsed::Not(l, r), - } -} diff --git a/crates/graph/src/range/elem/expr/collapse.rs b/crates/graph/src/range/elem/expr/collapse.rs new file mode 100644 index 00000000..d043005b --- /dev/null +++ b/crates/graph/src/range/elem/expr/collapse.rs @@ -0,0 +1,633 @@ +use crate::elem::expr::simplify::*; + +use crate::{ + nodes::Concrete, + range::{ + elem::{Elem, RangeConcrete, RangeElem, RangeExpr, RangeOp}, + exec_traits::*, + }, +}; + +use ethers_core::types::U256; +use shared::RangeArena; + +pub static ORD_OPS: &[RangeOp] = &[ + RangeOp::Eq, + RangeOp::Neq, + RangeOp::Lt, + RangeOp::Lte, + RangeOp::Gt, + RangeOp::Gte, + RangeOp::Min, + RangeOp::Max, +]; + +pub static EQ_OPS: &[RangeOp] = &[ + RangeOp::Eq, + RangeOp::Neq, + RangeOp::Lt, + RangeOp::Lte, + RangeOp::Gt, + RangeOp::Gte, + RangeOp::And, + RangeOp::Or, +]; + +pub static SINGLETON_EQ_OPS: &[RangeOp] = &[ + RangeOp::Eq, + RangeOp::Neq, + RangeOp::Lt, + RangeOp::Lte, + RangeOp::Gt, + RangeOp::Gte, +]; + +pub static FLIP_INEQ_OPS: &[RangeOp] = &[RangeOp::Lt, RangeOp::Lte, RangeOp::Gt, RangeOp::Gte]; + +#[derive(Debug)] +pub enum MaybeCollapsed { + Concretes(Elem, Elem), + Collapsed(Elem), + Not(Elem, Elem), +} + +pub fn collapse( + l: Elem, + op: RangeOp, + r: Elem, + arena: &mut RangeArena>, +) -> MaybeCollapsed { + let l = if let Elem::Expr(e) = l { + match collapse(*e.lhs, e.op, *e.rhs, arena) { + MaybeCollapsed::Not(l, r) => Elem::Expr(RangeExpr::new(l, e.op, r)), + MaybeCollapsed::Concretes(l, r) => Elem::Expr(RangeExpr::new(l, e.op, r)), + MaybeCollapsed::Collapsed(e) => e, + } + } else { + l + }; + + let r = if let Elem::Expr(e) = r { + match collapse(*e.lhs, e.op, *e.rhs, arena) { + MaybeCollapsed::Not(l, r) => Elem::Expr(RangeExpr::new(l, e.op, r)), + MaybeCollapsed::Concretes(l, r) => Elem::Expr(RangeExpr::new(l, e.op, r)), + MaybeCollapsed::Collapsed(e) => e, + } + } else { + r + }; + + if let Some(e) = ident_rules(&l, op, &r, arena) { + return MaybeCollapsed::Collapsed(e); + } + + let res = match (l, r) { + (l @ Elem::Arena(_), r) => { + let t = l.dearenaize_clone(arena); + match collapse(t, op, r, arena) { + MaybeCollapsed::Not(l, r) => MaybeCollapsed::Not(l, r), + MaybeCollapsed::Concretes(l, r) => MaybeCollapsed::Not(l, r), + MaybeCollapsed::Collapsed(e) => MaybeCollapsed::Collapsed(e), + } + } + (l, r @ Elem::Arena(_)) => { + let t = r.dearenaize_clone(arena); + match collapse(l, op, t, arena) { + MaybeCollapsed::Not(l, r) => MaybeCollapsed::Not(l, r), + MaybeCollapsed::Concretes(l, r) => MaybeCollapsed::Not(l, r), + MaybeCollapsed::Collapsed(e) => MaybeCollapsed::Collapsed(e), + } + } + (l @ Elem::Concrete(_), r @ Elem::Concrete(_)) => MaybeCollapsed::Concretes(l, r), + (Elem::Expr(expr), d @ Elem::Reference(_)) => { + // try to collapse the expression + let x = &*expr.lhs; + let y = &*expr.rhs; + let z = d; + + let ords = Ords::new(x, y, &z, arena); + + match (expr.op, op) { + (RangeOp::Sub(false), _) if ORD_OPS.contains(&op) => { + if let Some(res) = sub_ord_rules(x, y, op, &z, ords, arena) { + MaybeCollapsed::Collapsed(res) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + (RangeOp::Div(_), RangeOp::Eq) => { + if ords.x_eq_z() && !ords.y_eq_one() { + // (x -|/ y) == x ==> false + MaybeCollapsed::Collapsed(Elem::from(Concrete::from(false))) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + (RangeOp::Add(_), RangeOp::Eq) => { + if (ords.x_eq_z() && !ords.y_eq_zero()) || (ords.y_eq_z() && !ords.x_eq_zero()) + { + // (x +|* k) == x ==> false + MaybeCollapsed::Collapsed(Elem::from(Concrete::from(false))) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + (RangeOp::Mul(_), RangeOp::Eq) => { + if (ords.x_eq_z() && !ords.y_eq_one()) || (ords.y_eq_z() && !ords.x_eq_one()) { + // (x +|* k) == x ==> false + MaybeCollapsed::Collapsed(Elem::from(Concrete::from(false))) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + (RangeOp::Max, RangeOp::Gte) => { + if ords.x_eq_z() || ords.y_eq_z() { + // max{ x, y } >= + MaybeCollapsed::Collapsed(Elem::from(Concrete::from(true))) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + (RangeOp::Min, RangeOp::Lte) => { + if ords.x_eq_z() || ords.y_eq_z() { + // min{ x, y } <= + MaybeCollapsed::Collapsed(Elem::from(Concrete::from(true))) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + _ => MaybeCollapsed::Not(Elem::Expr(expr), z), + } + } + // if we have an expression, it fundamentally must have a dynamic in it + (Elem::Expr(expr), c @ Elem::Concrete(_)) => { + // potentially collapsible + let x = &*expr.lhs; + let y = &*expr.rhs; + let z = c; + + let ords = Ords::new(x, y, &z, arena); + + match (expr.op, op) { + (RangeOp::Sub(false), _) if ORD_OPS.contains(&op) => { + if let Some(res) = sub_ord_rules(x, y, op, &z, ords, arena) { + MaybeCollapsed::Collapsed(res) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + (RangeOp::Add(false), _) if ORD_OPS.contains(&op) => { + if let Some(res) = add_ord_rules(x, y, op, &z, ords, arena) { + MaybeCollapsed::Collapsed(res) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + (RangeOp::Eq, RangeOp::Eq) => { + // ((x == y) == z) + // can skip if x and z eq + if ords.x_eq_z() || ords.y_eq_z() { + MaybeCollapsed::Collapsed(Elem::Expr(expr)) + } else if z.range_eq(&Elem::from(Concrete::from(true)), arena) { + MaybeCollapsed::Collapsed(Elem::Expr(expr)) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + (RangeOp::Add(l_op), RangeOp::Add(r_op)) => { + // ((x + y) + z) + let op_fn = if l_op && r_op { + // unchecked + RangeAdd::range_wrapping_add + } else { + // checked + as RangeAdd>::range_add + }; + if let Some(new) = op_fn(x, &z) { + MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new(y.clone(), op, new))) + } else if let Some(new) = op_fn(y, &z) { + MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new(x.clone(), op, new))) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + (RangeOp::Add(l_op), RangeOp::Sub(r_op)) => { + // ((x + y) - z) => k - y || x + k + if l_op == r_op { + match y.range_ord(&z, arena) { + Some(std::cmp::Ordering::Equal) | Some(std::cmp::Ordering::Greater) => { + // y and z are concrete && y >= z ==> x + (y - z) + let op_fn = if l_op { + // unchecked + RangeSub::range_wrapping_sub + } else { + // checked + as RangeSub>::range_sub + }; + if let Some(new) = op_fn(y, &z) { + let new_expr = + Elem::Expr(RangeExpr::new(x.clone(), expr.op, new)); + MaybeCollapsed::Collapsed(new_expr) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + Some(std::cmp::Ordering::Less) => { + // y and z are concrete && y < z ==> x - (z - y) + let op_fn = if l_op { + // unchecked + RangeSub::range_wrapping_sub + } else { + // checked + as RangeSub>::range_sub + }; + if let Some(new) = op_fn(&z, y) { + MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( + x.clone(), + RangeOp::Sub(l_op), + new, + ))) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + None => { + // x and z are concrete, if x >= z, just do (x - z) + y + // else do (y - (z - x)) + match x.range_ord(&z, arena) { + Some(std::cmp::Ordering::Equal) + | Some(std::cmp::Ordering::Greater) => { + let op_fn = if l_op { + // unchecked + RangeSub::range_wrapping_sub + } else { + // checked + as RangeSub>::range_sub + }; + if let Some(new) = op_fn(y, &z) { + MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( + x.clone(), + expr.op, + new, + ))) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + Some(std::cmp::Ordering::Less) => { + // (y - (z - x)) because z > x, therefore its (-k + y) ==> (y - k) where k = (x - z) + let op_fn = if l_op { + // unchecked + RangeSub::range_wrapping_sub + } else { + // checked + as RangeSub>::range_sub + }; + if let Some(new) = op_fn(&z, x) { + MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( + y.clone(), + RangeOp::Sub(l_op), + new, + ))) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + None => MaybeCollapsed::Not(Elem::Expr(expr), z), + } + } + } + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + (RangeOp::Sub(l_op), RangeOp::Add(r_op)) => { + // ((x - y) + z) => k - y || x + k + if l_op == r_op { + match y.range_ord(&z, arena) { + Some(std::cmp::Ordering::Equal) | Some(std::cmp::Ordering::Greater) => { + // y and z are concrete && z <= y ==> x - (y - z) + let op_fn = if l_op { + // unchecked + RangeSub::range_wrapping_sub + } else { + // checked + as RangeSub>::range_sub + }; + if let Some(new) = op_fn(y, &z) { + MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( + x.clone(), + expr.op, + new, + ))) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + Some(std::cmp::Ordering::Less) => { + // y and z are concrete && y < z ==> x + (z - y) + let op_fn = if l_op { + // unchecked + RangeSub::range_wrapping_sub + } else { + // checked + as RangeSub>::range_sub + }; + if let Some(new) = op_fn(&z, y) { + MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( + x.clone(), + RangeOp::Add(l_op), + new, + ))) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + None => { + // x and z are concrete, just add them ==> (x + z) - y + let op_fn = if l_op { + // unchecked + RangeAdd::range_wrapping_add + } else { + // checked + as RangeAdd>::range_add + }; + if let Some(new) = op_fn(x, &z) { + MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( + new, + expr.op, + y.clone(), + ))) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + } + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + (RangeOp::Mul(l_op), RangeOp::Mul(r_op)) => { + // ((x * y) * z) + if l_op == r_op { + let op_fn = if l_op { + // unchecked + RangeMul::range_wrapping_mul + } else { + // checked + as RangeMul>::range_mul + }; + if let Some(new) = op_fn(x, &z) { + MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( + y.clone(), + op, + new, + ))) + } else if let Some(new) = op_fn(y, &z) { + MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( + x.clone(), + op, + new, + ))) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + (RangeOp::Add(wrapping), op) if EQ_OPS.contains(&op) => { + let const_op = if wrapping { + RangeSub::range_wrapping_sub + } else { + RangeSub::range_sub + }; + // ((x + y) == z) => (x == (z - y)) || (y == (z - x)) + // .. + // ((x + y) != z) => (x != (z - y)) || (y != (z - x)) + if let Some(new) = const_op(&z, y) { + MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new(y.clone(), op, new))) + } else if let Some(new) = const_op(&z, x) { + MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new(x.clone(), op, new))) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + (RangeOp::Sub(wrapping), op) if EQ_OPS.contains(&op) => { + let op_y = if wrapping { + as RangeAdd>::range_wrapping_add + } else { + as RangeAdd>::range_add + }; + let op_x = if wrapping { + as RangeSub>::range_wrapping_sub + } else { + as RangeSub>::range_sub + }; + // ((x - y) == z) => (x == (z + y)) || (y == (x - z)) + // ((x - y) != z) => (x != (z + y)) || (y != (x - z)) + if let Some(new) = op_y(y, &z) { + let new_expr = RangeExpr::new(x.clone(), op, new); + MaybeCollapsed::Collapsed(Elem::Expr(new_expr)) + } else if let Some(new) = op_x(x, &z) { + let new_expr = RangeExpr::new(y.clone(), op, new); + MaybeCollapsed::Collapsed(Elem::Expr(new_expr)) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + (RangeOp::Mul(wrapping), op) if EQ_OPS.contains(&op) => { + let div_op = if wrapping { + RangeDiv::range_wrapping_div + } else { + RangeDiv::range_div + }; + // ((x * y) == z) => (x == (z / y)) || (y == (z / x)) + // ((x * y) != z) => (x != (z / y)) || (y != (z / x)) + if let Some(new) = div_op(&z, x) { + let new_op = if ords.x_lt_zero() && FLIP_INEQ_OPS.contains(&op) { + op.inverse().unwrap() + } else { + op + }; + MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( + y.clone(), + new_op, + new, + ))) + } else if let Some(new) = div_op(&z, y) { + let new_op = if ords.y_lt_zero() && FLIP_INEQ_OPS.contains(&op) { + op.inverse().unwrap() + } else { + op + }; + MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( + x.clone(), + new_op, + new, + ))) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + (RangeOp::Div(wrapping), op) if EQ_OPS.contains(&op) => { + let mul_op = if wrapping { + as RangeMul>::range_wrapping_mul + } else { + as RangeMul>::range_mul + }; + let div_op = if wrapping { + as RangeDiv>::range_wrapping_div + } else { + as RangeDiv>::range_div + }; + + // ((x / y) == z) => (x == (z * y)) || (y == (x / z)) + // .. + // ((x / y) != z) => (x != (z / y)) || (y != (x / z)) + if let Some(new) = mul_op(&z, y) { + let new_op = if ords.y_lt_zero() && FLIP_INEQ_OPS.contains(&op) { + op.inverse().unwrap() + } else { + op + }; + MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( + x.clone(), + new_op, + new, + ))) + } else if !FLIP_INEQ_OPS.contains(&op) { + if let Some(new) = div_op(x, &z) { + // y is the dynamic element + // we cant do flip ops here because we do (x / y) * y >= z * y which is a flip potentially + // but we dont know if y was negative. so we limit to just eq & neq + MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new( + y.clone(), + op, + new, + ))) + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + (_, RangeOp::Eq) => { + // (x _ y) == z ==> (x _ y if z == true) + if z.range_eq(&Elem::from(Concrete::from(true)), arena) { + MaybeCollapsed::Collapsed(Elem::Expr(expr)) + } else if z.range_eq(&Elem::from(Concrete::from(false)), arena) { + // (!x && !y) + match ( + x.inverse_if_boolean(), + y.inverse_if_boolean(), + expr.op.logical_inverse(), + ) { + (Some(new_x), Some(new_y), Some(new_op)) => MaybeCollapsed::Collapsed( + Elem::Expr(RangeExpr::new(new_x, new_op, new_y)), + ), + _ => MaybeCollapsed::Not(Elem::Expr(expr), z), + } + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + (_, RangeOp::Neq) => { + // (x _ y) != z ==> (x _ y if z != false) + if z.range_eq(&Elem::from(Concrete::from(false)), arena) { + // != false is == true + MaybeCollapsed::Collapsed(Elem::Expr(expr)) + } else if z.range_eq(&Elem::from(Concrete::from(true)), arena) { + // != true is == false, to make it == true, inverse everything + match ( + x.inverse_if_boolean(), + y.inverse_if_boolean(), + expr.op.logical_inverse(), + ) { + (Some(new_x), Some(new_y), Some(new_op)) => MaybeCollapsed::Collapsed( + Elem::Expr(RangeExpr::new(new_x, new_op, new_y)), + ), + _ => MaybeCollapsed::Not(Elem::Expr(expr), z), + } + } else { + MaybeCollapsed::Not(Elem::Expr(expr), z) + } + } + _ => MaybeCollapsed::Not(Elem::Expr(expr), z), + } + } + (l @ Elem::Concrete(_), r @ Elem::Expr(_)) => { + if op.commutative() { + match collapse(r, op, l, arena) { + MaybeCollapsed::Collapsed(inner) => MaybeCollapsed::Collapsed(inner.clone()), + MaybeCollapsed::Not(r, l) => MaybeCollapsed::Not(l, r), + MaybeCollapsed::Concretes(r, l) => MaybeCollapsed::Concretes(l, r), + } + } else if let Some(inv) = op.non_commutative_logical_inverse() { + match collapse(r, inv, l, arena) { + MaybeCollapsed::Collapsed(inner) => MaybeCollapsed::Collapsed(inner.clone()), + MaybeCollapsed::Not(r, l) => MaybeCollapsed::Not(l, r), + MaybeCollapsed::Concretes(r, l) => MaybeCollapsed::Concretes(l, r), + } + } else { + MaybeCollapsed::Not(l, r) + } + } + (le @ Elem::Reference(_), c @ Elem::Concrete(_)) => { + let zero = Elem::from(Concrete::from(U256::zero())); + let one = Elem::from(Concrete::from(U256::one())); + match op { + RangeOp::Sub(_) | RangeOp::Add(_) => { + if matches!(c.range_ord(&zero, arena), Some(std::cmp::Ordering::Equal)) { + MaybeCollapsed::Collapsed(le.clone()) + } else { + MaybeCollapsed::Not(le, c) + } + } + RangeOp::Mul(_) | RangeOp::Div(_) => { + if matches!(c.range_ord(&one, arena), Some(std::cmp::Ordering::Equal)) { + MaybeCollapsed::Collapsed(le.clone()) + } else { + MaybeCollapsed::Not(le, c) + } + } + _ => MaybeCollapsed::Not(le, c), + } + } + (Elem::Null, real) => match op { + RangeOp::Max | RangeOp::Min => MaybeCollapsed::Collapsed(real.clone()), + RangeOp::Not => match real { + Elem::Concrete(RangeConcrete { + val: Concrete::Bool(c), + loc, + }) => MaybeCollapsed::Collapsed(Elem::Concrete(RangeConcrete::new( + Concrete::from(!c), + loc, + ))), + _ => MaybeCollapsed::Not(Elem::Null, real), + }, + _ => MaybeCollapsed::Not(Elem::Null, real), + }, + (real, Elem::Null) => match op { + RangeOp::Max | RangeOp::Min => MaybeCollapsed::Collapsed(real.clone()), + RangeOp::Not => match real { + Elem::Concrete(RangeConcrete { + val: Concrete::Bool(c), + loc, + }) => MaybeCollapsed::Collapsed(Elem::Concrete(RangeConcrete::new( + Concrete::from(!c), + loc, + ))), + _ => MaybeCollapsed::Not(real, Elem::Null), + }, + _ => MaybeCollapsed::Not(real, Elem::Null), + }, + (l, r) => return MaybeCollapsed::Not(l, r), + }; + + match res { + MaybeCollapsed::Collapsed(Elem::Expr(e)) => collapse(*e.lhs, e.op, *e.rhs, arena), + other => other, + } +} diff --git a/crates/graph/src/range/elem/expr/mod.rs b/crates/graph/src/range/elem/expr/mod.rs new file mode 100644 index 00000000..f69aaeb3 --- /dev/null +++ b/crates/graph/src/range/elem/expr/mod.rs @@ -0,0 +1,728 @@ +mod collapse; +pub use collapse::*; + +mod simplify; + +use crate::{ + nodes::{Concrete, ContextVarNode}, + range::{ + elem::{Elem, MinMaxed, RangeArenaLike, RangeConcrete, RangeElem, RangeOp}, + exec_traits::*, + }, + GraphBackend, GraphError, +}; +use std::hash::Hash; +use std::hash::Hasher; + +use ethers_core::types::U256; +use shared::{NodeIdx, RangeArena}; + +/// A range expression composed of other range [`Elem`] +#[derive(Clone, Debug, Ord, PartialOrd)] +pub struct RangeExpr { + pub maximized: Option>, + pub minimized: Option>, + pub flattened_min: Option>>, + pub flattened_max: Option>>, + pub lhs: Box>, + pub op: RangeOp, + pub rhs: Box>, +} + +impl std::fmt::Display for RangeExpr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.op { + RangeOp::Min | RangeOp::Max => { + write!(f, "{}{{{}, {}}}", self.op.to_string(), self.lhs, self.rhs) + } + RangeOp::Cast => match &*self.rhs { + Elem::Concrete(RangeConcrete { val, .. }) => { + write!( + f, + "{}({}, {})", + self.op.to_string(), + self.lhs, + val.as_builtin().basic_as_string() + ) + } + _ => write!(f, "{}({}, {})", self.op.to_string(), self.lhs, self.rhs), + }, + RangeOp::BitNot => { + write!(f, "~{}", self.lhs) + } + _ => write!(f, "({} {} {})", self.lhs, self.op.to_string(), self.rhs), + } + } +} + +impl PartialEq for RangeExpr { + fn eq(&self, other: &Self) -> bool { + self.op == other.op && self.lhs == other.lhs && self.rhs == other.rhs + } +} +impl Eq for RangeExpr {} + +impl Hash for RangeExpr { + fn hash(&self, state: &mut H) { + (*self.lhs).hash(state); + self.op.hash(state); + (*self.rhs).hash(state); + } +} + +impl RangeExpr { + pub fn is_noop(&self) -> (bool, usize) { + let one = Elem::from(Concrete::from(U256::one())); + let zero = Elem::from(Concrete::from(U256::zero())); + match self.op { + RangeOp::Mul(_) | RangeOp::Div(_) => { + if *self.lhs == one { + (true, 0) + } else if *self.rhs == one { + (true, 1) + } else { + (false, 0) + } + } + RangeOp::Add(_) | RangeOp::Sub(_) => { + if *self.lhs == zero { + (true, 0) + } else if *self.rhs == zero { + (true, 1) + } else { + (false, 0) + } + } + _ => (false, 0), + } + } + + pub fn inverse_if_boolean(&self) -> Option { + if EQ_OPS.contains(&self.op) { + if SINGLETON_EQ_OPS.contains(&self.op) { + let mut new_self = self.clone(); + new_self.uncache(); + new_self.op = new_self.op.logical_inverse()?; + Some(new_self) + } else { + // non-singleton, i.e. AND or OR + let mut new_self = self.clone(); + new_self.uncache(); + new_self.op = new_self.op.inverse()?; + if let Some(new_lhs) = new_self.inverse_if_boolean() { + *new_self.lhs = Elem::Expr(new_lhs); + } + if let Some(new_rhs) = new_self.inverse_if_boolean() { + *new_self.rhs = Elem::Expr(new_rhs); + } + Some(new_self) + } + } else { + None + } + } + + pub fn recurse_dearenaize( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Elem { + Elem::Expr(Self::new( + self.lhs.recurse_dearenaize(analyzer, arena).clone(), + self.op, + self.rhs.recurse_dearenaize(analyzer, arena).clone(), + )) + } + + pub fn arena_idx(&self, arena: &RangeArena>) -> Option { + let expr = Elem::Expr(RangeExpr::new( + Elem::Arena(arena.idx(&self.lhs)?), + self.op, + Elem::Arena(arena.idx(&self.rhs)?), + )); + arena.idx(&expr) + } + + pub fn arenaized_cache( + &self, + max: bool, + _analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Option> { + if let Some(idx) = self.arena_idx(arena) { + let Some(ref mut t) = arena.ranges.get_mut(idx) else { + return None; + }; + let Elem::Expr(ref mut arenaized) = *t else { + return None; + }; + return if max { + arenaized.maximized.clone() + } else { + arenaized.minimized.clone() + }; + } + None + } + + pub fn arenaized_flat_cache( + &self, + max: bool, + arena: &mut RangeArena>, + ) -> Option>> { + if let Some(idx) = self.arena_idx(arena) { + let Some(ref mut t) = arena.ranges.get_mut(idx) else { + return None; + }; + let Elem::Expr(ref mut arenaized) = *t else { + return None; + }; + return if max { + arenaized.flattened_max.clone() + } else { + arenaized.flattened_min.clone() + }; + } + None + } + + pub fn set_arenaized_flattened( + &self, + max: bool, + elem: Elem, + arena: &mut RangeArena>, + ) { + if let Some(idx) = self.arena_idx(arena) { + if let Some(t) = arena.ranges.get_mut(idx) { + let Elem::Expr(arenaized) = &mut *t else { + return; + }; + + if max { + arenaized.flattened_max = Some(Box::new(elem)); + } else { + arenaized.flattened_min = Some(Box::new(elem)); + } + } + } + } +} + +impl RangeExpr { + /// Creates a new range expression given a left hand side range [`Elem`], a [`RangeOp`], and a a right hand side range [`Elem`]. + pub fn new(lhs: Elem, op: RangeOp, rhs: Elem) -> RangeExpr { + RangeExpr { + maximized: None, + minimized: None, + flattened_max: None, + flattened_min: None, + lhs: Box::new(lhs), + op, + rhs: Box::new(rhs), + } + } + + pub fn contains_node(&self, node_idx: NodeIdx) -> bool { + self.lhs.contains_node(node_idx) || self.rhs.contains_node(node_idx) + } +} + +impl RangeElem for RangeExpr { + type GraphError = GraphError; + + // #[tracing::instrument(level = "trace", skip_all)] + fn arenaize( + &mut self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { + if self.arena_idx(arena).is_none() { + let lhs = std::mem::take(&mut self.lhs); + let rhs = std::mem::take(&mut self.rhs); + self.lhs = Box::new(Elem::Arena(arena.idx_or_upsert(*lhs, analyzer))); + self.rhs = Box::new(Elem::Arena(arena.idx_or_upsert(*rhs, analyzer))); + let _ = arena.idx_or_upsert(Elem::Expr(self.clone()), analyzer); + } + Ok(()) + } + + fn range_eq(&self, _other: &Self, _arena: &mut RangeArena>) -> bool { + false + } + + // #[tracing::instrument(level = "trace", skip_all)] + fn flatten( + &self, + maximize: bool, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { + match (maximize, &self.flattened_min, &self.flattened_max) { + (true, _, Some(flat)) | (false, Some(flat), _) => { + return Ok(*flat.clone()); + } + _ => {} + } + + if let Some(arenaized) = self.arenaized_flat_cache(maximize, arena) { + return Ok(*arenaized); + } + + Ok(Elem::Expr(RangeExpr::new( + self.lhs.flatten(maximize, analyzer, arena)?, + self.op, + self.rhs.flatten(maximize, analyzer, arena)?, + ))) + } + + fn is_flatten_cached( + &self, + _analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> bool { + self.flattened_min.is_some() && self.flattened_max.is_some() || { + if let Some(idx) = self.arena_idx(arena) { + if let Some(t) = arena.ranges.get(idx) { + if let Elem::Expr(ref arenaized) = *t { + arenaized.flattened_min.is_some() && arenaized.flattened_max.is_some() + } else { + false + } + } else { + false + } + } else { + false + } + } + } + + fn is_min_max_cached( + &self, + _analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> (bool, bool) { + let (arena_cached_min, arena_cached_max) = { + if let Some(idx) = self.arena_idx(arena) { + if let Some(t) = arena.ranges.get(idx) { + if let Elem::Expr(ref arenaized) = *t { + (arenaized.minimized.is_some(), arenaized.maximized.is_some()) + } else { + (false, false) + } + } else { + (false, false) + } + } else { + (false, false) + } + }; + ( + self.minimized.is_some() || arena_cached_min, + self.maximized.is_some() || arena_cached_max, + ) + } + + fn range_ord( + &self, + _other: &Self, + _arena: &mut RangeArena>, + ) -> Option { + todo!() + } + + fn dependent_on( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Vec { + let mut deps = self.lhs.dependent_on(analyzer, arena); + deps.extend(self.rhs.dependent_on(analyzer, arena)); + deps + } + + // #[tracing::instrument(level = "trace", skip_all)] + fn recursive_dependent_on( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { + let mut deps = self.lhs.recursive_dependent_on(analyzer, arena)?; + deps.extend(self.rhs.recursive_dependent_on(analyzer, arena)?); + Ok(deps) + } + + // #[tracing::instrument(level = "trace", skip_all)] + fn has_cycle( + &self, + seen: &mut Vec, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result { + let lhs_has_cycle = self.lhs.has_cycle(seen, analyzer, arena)?; + let rhs_has_cycle = self.rhs.has_cycle(seen, analyzer, arena)?; + Ok(lhs_has_cycle || rhs_has_cycle) + } + + fn depends_on( + &self, + var: ContextVarNode, + seen: &mut Vec, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result { + let lhs_deps_on = self.lhs.depends_on(var, seen, analyzer, arena)?; + let rhs_deps_on = self.rhs.depends_on(var, seen, analyzer, arena)?; + Ok(lhs_deps_on || rhs_deps_on) + } + + // #[tracing::instrument(level = "trace", skip_all)] + fn filter_recursion( + &mut self, + node_idx: NodeIdx, + new_idx: NodeIdx, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) { + let _ = self.arenaize(analyzer, arena); + self.lhs + .filter_recursion(node_idx, new_idx, analyzer, arena); + self.rhs + .filter_recursion(node_idx, new_idx, analyzer, arena); + } + + // #[tracing::instrument(level = "trace", skip_all)] + fn maximize( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { + if let Some(MinMaxed::Maximized(cached)) = self.maximized.clone() { + Ok(*cached) + } else if let Some(MinMaxed::Maximized(cached)) = + self.arenaized_cache(true, analyzer, arena) + { + Ok(*cached) + } else if self.op == RangeOp::SetIndices { + self.simplify_exec_op(true, analyzer, arena) + } else { + self.exec_op(true, analyzer, arena) + } + } + + // #[tracing::instrument(level = "trace", skip_all)] + fn minimize( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { + if let Some(MinMaxed::Minimized(cached)) = self.minimized.clone() { + Ok(*cached) + } else if let Some(MinMaxed::Minimized(cached)) = + self.arenaized_cache(false, analyzer, arena) + { + Ok(*cached) + } else if self.op == RangeOp::SetIndices { + self.simplify_exec_op(false, analyzer, arena) + } else { + self.exec_op(false, analyzer, arena) + } + } + + // #[tracing::instrument(level = "trace", skip_all)] + fn simplify_maximize( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { + if let Some(simp_max) = &self.flattened_max { + return Ok(*simp_max.clone()); + } + + if let Some(arenaized) = self.arenaized_flat_cache(true, arena) { + return Ok(*arenaized); + } + + let l = self.lhs.simplify_maximize(analyzer, arena)?; + let r = self.rhs.simplify_maximize(analyzer, arena)?; + let collapsed = collapse(l, self.op, r, arena); + let res = match collapsed { + MaybeCollapsed::Concretes(l, r) => { + RangeExpr::new(l, self.op, r).exec_op(true, analyzer, arena) + } + MaybeCollapsed::Collapsed(collapsed) => Ok(collapsed), + MaybeCollapsed::Not(l, r) => { + let res = RangeExpr::new(l, self.op, r).simplify_exec_op(true, analyzer, arena)?; + match res { + Elem::Expr(expr) => match collapse(*expr.lhs, expr.op, *expr.rhs, arena) { + MaybeCollapsed::Concretes(l, r) => { + RangeExpr::new(l, expr.op, r).exec_op(true, analyzer, arena) + } + MaybeCollapsed::Collapsed(collapsed) => Ok(collapsed), + MaybeCollapsed::Not(l, r) => Ok(Elem::Expr(RangeExpr::new(l, expr.op, r))), + }, + other => Ok(other), + } + } + }?; + self.set_arenaized_flattened(true, res.clone(), arena); + Ok(res) + } + + // #[tracing::instrument(level = "trace", skip_all)] + fn simplify_minimize( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { + if let Some(simp_min) = &self.flattened_min { + return Ok(*simp_min.clone()); + } + + if let Some(arenaized) = self.arenaized_flat_cache(false, arena) { + return Ok(*arenaized); + } + + let l = self.lhs.simplify_minimize(analyzer, arena)?; + self.lhs.set_arenaized_flattened(false, &l, arena); + let r = self.rhs.simplify_minimize(analyzer, arena)?; + self.rhs.set_arenaized_flattened(false, &r, arena); + + let collapsed = collapse(l, self.op, r, arena); + let res = match collapsed { + MaybeCollapsed::Concretes(l, r) => { + RangeExpr::new(l, self.op, r).exec_op(false, analyzer, arena) + } + MaybeCollapsed::Collapsed(collapsed) => Ok(collapsed), + MaybeCollapsed::Not(l, r) => { + let res = RangeExpr::new(l, self.op, r).simplify_exec_op(false, analyzer, arena)?; + match res { + Elem::Expr(expr) => match collapse(*expr.lhs, expr.op, *expr.rhs, arena) { + MaybeCollapsed::Concretes(l, r) => { + return RangeExpr::new(l, self.op, r).exec_op(false, analyzer, arena) + } + MaybeCollapsed::Collapsed(collapsed) => return Ok(collapsed), + MaybeCollapsed::Not(l, r) => Ok(Elem::Expr(RangeExpr::new(l, expr.op, r))), + }, + other => Ok(other), + } + } + }?; + + self.set_arenaized_flattened(false, res.clone(), arena); + Ok(res) + } + + // #[tracing::instrument(level = "trace", skip_all)] + fn cache_flatten( + &mut self, + g: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { + self.arenaize(g, arena)?; + + fn simp_minimize( + this: &mut Elem, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { + let Elem::Expr(this) = this else { + this.cache_flatten(analyzer, arena)?; + if let Some(t) = this.arenaized_flattened(false, analyzer, arena) { + return Ok(*t); + } else { + return Ok(this.clone()); + } + }; + + if let Some(simp_min) = &this.flattened_min { + return Ok(*simp_min.clone()); + } + + if let Some(arenaized) = this.arenaized_flat_cache(false, arena) { + return Ok(*arenaized); + } + + let l = simp_minimize(&mut this.lhs, analyzer, arena)?; + let r = simp_minimize(&mut this.rhs, analyzer, arena)?; + let collapsed = collapse(l, this.op, r, arena); + let res = match collapsed { + MaybeCollapsed::Concretes(l, r) => { + RangeExpr::new(l, this.op, r).exec_op(false, analyzer, arena) + } + MaybeCollapsed::Collapsed(collapsed) => Ok(collapsed), + MaybeCollapsed::Not(l, r) => { + let res = + RangeExpr::new(l, this.op, r).simplify_exec_op(false, analyzer, arena)?; + + let idx = arena.idx_or_upsert(res.clone(), analyzer); + match res { + Elem::Expr(expr) => match collapse(*expr.lhs, expr.op, *expr.rhs, arena) { + MaybeCollapsed::Concretes(l, r) => { + let exec_res = RangeExpr::new(l, expr.op, r) + .exec_op(false, analyzer, arena)?; + Elem::Arena(idx).set_arenaized_flattened(false, &exec_res, arena); + Ok(exec_res) + } + MaybeCollapsed::Collapsed(collapsed) => { + Elem::Arena(idx).set_arenaized_flattened(false, &collapsed, arena); + Ok(collapsed) + } + MaybeCollapsed::Not(l, r) => { + Ok(Elem::Expr(RangeExpr::new(l, expr.op, r))) + } + }, + other => Ok(other), + } + } + }?; + + this.set_arenaized_flattened(false, res.clone(), arena); + Ok(res) + } + + fn simp_maximize( + this: &mut Elem, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { + let Elem::Expr(this) = this else { + this.cache_flatten(analyzer, arena)?; + if let Some(t) = this.arenaized_flattened(true, analyzer, arena) { + return Ok(*t); + } else { + return Ok(this.clone()); + } + }; + + if let Some(simp_min) = &this.flattened_max { + return Ok(*simp_min.clone()); + } + + if let Some(arenaized) = this.arenaized_flat_cache(false, arena) { + return Ok(*arenaized); + } + + let l = simp_maximize(&mut this.lhs, analyzer, arena)?; + let r = simp_maximize(&mut this.rhs, analyzer, arena)?; + let collapsed = collapse(l, this.op, r, arena); + let res = match collapsed { + MaybeCollapsed::Concretes(l, r) => { + RangeExpr::new(l, this.op, r).exec_op(true, analyzer, arena) + } + MaybeCollapsed::Collapsed(collapsed) => Ok(collapsed), + MaybeCollapsed::Not(l, r) => { + let res = + RangeExpr::new(l, this.op, r).simplify_exec_op(true, analyzer, arena)?; + + let idx = arena.idx_or_upsert(res.clone(), analyzer); + match res { + Elem::Expr(expr) => match collapse(*expr.lhs, expr.op, *expr.rhs, arena) { + MaybeCollapsed::Concretes(l, r) => { + let exec_res = + RangeExpr::new(l, expr.op, r).exec_op(true, analyzer, arena)?; + Elem::Arena(idx).set_arenaized_flattened(true, &exec_res, arena); + Ok(exec_res) + } + MaybeCollapsed::Collapsed(collapsed) => { + Elem::Arena(idx).set_arenaized_flattened(true, &collapsed, arena); + Ok(collapsed) + } + MaybeCollapsed::Not(l, r) => { + Ok(Elem::Expr(RangeExpr::new(l, expr.op, r))) + } + }, + other => Ok(other), + } + } + }?; + + this.set_arenaized_flattened(false, res.clone(), arena); + Ok(res) + } + + if self.flattened_max.is_none() { + if let Some(idx) = self.arena_idx(arena) { + if let Some(t) = arena.ranges.get(idx) { + if let Elem::Expr(ref arenaized) = *t { + if arenaized.flattened_max.is_some() { + return Ok(()); + } + } + }; + } else { + self.arenaize(g, arena)?; + } + + self.lhs.cache_flatten(g, arena)?; + self.rhs.cache_flatten(g, arena)?; + + let mut flat_max = self.flatten(true, g, arena)?; + let simplified_flat_max = simp_maximize(&mut flat_max, g, arena)?; + simplified_flat_max.clone().arenaize(g, arena)?; + self.flattened_max = Some(Box::new(simplified_flat_max)); + } + + if self.flattened_min.is_none() { + if let Some(idx) = self.arena_idx(arena) { + if let Some(t) = arena.ranges.get(idx) { + if let Elem::Expr(ref arenaized) = *t { + if arenaized.flattened_min.is_some() { + return Ok(()); + } + } + }; + } else { + self.arenaize(g, arena)?; + } + + self.lhs.cache_flatten(g, arena)?; + self.rhs.cache_flatten(g, arena)?; + + let mut flat_min = self.flatten(false, g, arena)?; + let simplified_flat_min = simp_minimize(&mut flat_min, g, arena)?; + simplified_flat_min.clone().arenaize(g, arena)?; + self.flattened_min = Some(Box::new(simplified_flat_min)); + } + Ok(()) + } + + // #[tracing::instrument(level = "trace", skip_all)] + fn cache_maximize( + &mut self, + g: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { + tracing::trace!("cache maximizing: {}", Elem::Expr(self.clone())); + self.arenaize(g, arena)?; + if self.maximized.is_none() { + self.lhs.cache_maximize(g, arena)?; + self.rhs.cache_maximize(g, arena)?; + self.cache_exec_op(true, g, arena)?; + } + Ok(()) + } + + // #[tracing::instrument(level = "trace", skip_all)] + fn cache_minimize( + &mut self, + g: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { + tracing::trace!("cache minimizing: {}", Elem::Expr(self.clone())); + self.arenaize(g, arena)?; + if self.minimized.is_none() { + tracing::trace!("cache minimize lhs"); + self.lhs.cache_minimize(g, arena)?; + tracing::trace!("cache minimize rhs"); + self.rhs.cache_minimize(g, arena)?; + tracing::trace!("minimizing expr"); + self.cache_exec_op(false, g, arena)?; + } + Ok(()) + } + + fn uncache(&mut self) { + self.uncache_exec(); + } +} diff --git a/crates/graph/src/range/elem/expr/simplify/add.rs b/crates/graph/src/range/elem/expr/simplify/add.rs new file mode 100644 index 00000000..463d0885 --- /dev/null +++ b/crates/graph/src/range/elem/expr/simplify/add.rs @@ -0,0 +1,162 @@ +use crate::{ + nodes::Concrete, + range::{ + elem::expr::simplify::Ords, + elem::{Elem, RangeConcrete, RangeExpr, RangeOp}, + }, +}; + +use shared::RangeArena; + +pub fn add_ord_rules( + x: &Elem, + y: &Elem, + ord_op: RangeOp, + z: &Elem, + ords: Ords, + arena: &mut RangeArena>, +) -> Option> { + match ord_op { + RangeOp::Eq => { + if !ords.x_eq_z() { + return None; + } + // x + y == x + // ==> true iff y == 0, false otherwise + let res = if ords.y_eq_zero() { + Elem::from(true) + } else { + Elem::from(false) + }; + Some(res) + } + RangeOp::Neq => { + if !ords.x_eq_z() { + return None; + } + // x + y != x + // ==> true iff y != 0, false otherwise + let res = if ords.y_eq_zero() { + Elem::from(false) + } else { + Elem::from(true) + }; + Some(res) + } + RangeOp::Lt => { + // x + y < z + // ==> true if: + // x < z && y <= 0 + // ==> false if + // x >= z && y > 0 + if ords.x_lt_z() && ords.y_lte_zero() { + Some(Elem::from(true)) + } else if ords.x_gte_z() && ords.y_gt_zero() { + Some(Elem::from(false)) + } else { + None + } + } + RangeOp::Gt => { + // x + y > z + // ==> true if: + // x >= z && y > 0 || x > z && y >= 0 + let true_lhs = ords.x_gte_z() && ords.y_gt_zero(); + let true_rhs = ords.x_gt_z() && ords.y_gte_zero(); + // ==> false if + // x <= z && y < 0 + let false_cond = ords.x_lte_z() && ords.y_lt_zero(); + + if true_lhs || true_rhs { + Some(Elem::from(true)) + } else if false_cond { + Some(Elem::from(false)) + } else { + None + } + } + RangeOp::Lte => { + // x + y <= z + + // ==> true if: + // x <= z && y <= 0 + let true_cond = ords.x_lte_z() && ords.y_lte_zero(); + + // ==> false if: + // x > z && y >= 0 || x >= z && y > 0 + let false_lhs = ords.x_gt_z() && ords.y_gte_zero(); + let false_rhs = ords.x_gte_z() && ords.y_gt_zero(); + + if true_cond { + Some(Elem::from(true)) + } else if false_lhs || false_rhs { + Some(Elem::from(false)) + } else { + None + } + } + RangeOp::Gte => { + // x + y >= z + + // ==> true if: + // x >= z && y >= 0 + let true_cond = ords.x_gte_z() && ords.y_gte_zero(); + + // ==> false if: + // x < z && y <= 0 || x <= z && y < 0 + let false_lhs = ords.x_lt_z() && ords.y_lte_zero(); + let false_rhs = ords.x_lte_z() && ords.y_lt_zero(); + + if true_cond { + Some(Elem::from(true)) + } else if false_lhs || false_rhs { + Some(Elem::from(false)) + } else { + None + } + } + RangeOp::Max => { + // max{x + y, z} + // same as gt but return lhs or rhs instead + match add_ord_rules(x, y, RangeOp::Gt, z, ords, arena) { + Some(Elem::Concrete(RangeConcrete { + val: Concrete::Bool(b), + .. + })) => { + if b { + Some(Elem::Expr(RangeExpr::new( + x.clone(), + RangeOp::Add(false), + y.clone(), + ))) + } else { + Some(z.clone()) + } + } + _ => None, + } + } + RangeOp::Min => { + // min{x - y, z} + // same as lt but return lhs or rhs instead + match add_ord_rules(x, y, RangeOp::Lt, z, ords, arena) { + Some(Elem::Concrete(RangeConcrete { + val: Concrete::Bool(b), + .. + })) => { + if b { + Some(Elem::Expr(RangeExpr::new( + x.clone(), + RangeOp::Add(false), + y.clone(), + ))) + } else { + Some(z.clone()) + } + } + _ => None, + } + } + _ => None, + } +} diff --git a/crates/graph/src/range/elem/expr/simplify/mod.rs b/crates/graph/src/range/elem/expr/simplify/mod.rs new file mode 100644 index 00000000..efd55aff --- /dev/null +++ b/crates/graph/src/range/elem/expr/simplify/mod.rs @@ -0,0 +1,55 @@ +mod add; +mod ords; +mod sub; + +pub use add::*; +pub use ords::*; +pub use sub::*; + +use crate::{ + nodes::Concrete, + range::elem::{Elem, RangeElem, RangeOp}, +}; + +use ethers_core::types::U256; +use shared::RangeArena; + +pub(crate) fn ident_rules( + l: &Elem, + exec_op: RangeOp, + r: &Elem, + arena: &mut RangeArena>, +) -> Option> { + let zero = Elem::from(Concrete::from(U256::zero())); + let one = Elem::from(Concrete::from(U256::one())); + match exec_op { + RangeOp::Add(_) | RangeOp::Sub(_) => { + let lhs_zero = matches!(l.range_ord(&zero, arena), Some(std::cmp::Ordering::Equal)); + let rhs_zero = matches!(r.range_ord(&zero, arena), Some(std::cmp::Ordering::Equal)); + match (lhs_zero, rhs_zero) { + (true, true) => Some(Elem::from(Concrete::from(U256::zero()))), + (true, false) => Some((*r).clone()), + (false, true) => Some((*l).clone()), + _ => None, + } + } + RangeOp::Mul(_) | RangeOp::Div(_) => { + let lhs_one = matches!(l.range_ord(&one, arena), Some(std::cmp::Ordering::Equal)); + let rhs_one = matches!(r.range_ord(&one, arena), Some(std::cmp::Ordering::Equal)); + match (lhs_one, rhs_one) { + (true, true) => Some(Elem::from(Concrete::from(U256::one()))), + (true, false) => Some((*r).clone()), + (false, true) => Some((*l).clone()), + _ => None, + } + } + RangeOp::Exp => { + if matches!(r.range_ord(&zero, arena), Some(std::cmp::Ordering::Equal)) { + Some(Elem::from(Concrete::from(U256::one()))) + } else { + None + } + } + _ => None, + } +} diff --git a/crates/graph/src/range/elem/expr/simplify/ords.rs b/crates/graph/src/range/elem/expr/simplify/ords.rs new file mode 100644 index 00000000..f140a5db --- /dev/null +++ b/crates/graph/src/range/elem/expr/simplify/ords.rs @@ -0,0 +1,122 @@ +use crate::{ + nodes::Concrete, + range::elem::{Elem, RangeElem}, +}; + +use ethers_core::types::U256; +use shared::RangeArena; + +pub struct Ords { + pub x_ord_z: Option, + pub y_ord_z: Option, + pub y_ord_zero: Option, + pub x_ord_zero: Option, + pub y_ord_one: Option, + pub x_ord_one: Option, +} + +impl Ords { + pub fn new( + x: &Elem, + y: &Elem, + z: &Elem, + arena: &mut RangeArena>, + ) -> Self { + let zero = Elem::from(Concrete::from(U256::zero())); + let one = Elem::from(Concrete::from(U256::one())); + Self { + x_ord_z: x.range_ord(z, arena), + y_ord_z: y.range_ord(z, arena), + y_ord_zero: y.range_ord(&zero, arena), + x_ord_zero: x.range_ord(&zero, arena), + y_ord_one: y.range_ord(&one, arena), + x_ord_one: x.range_ord(&one, arena), + } + } + + pub fn x_gte_z(&self) -> bool { + self.x_gt_z() || self.x_eq_z() + } + pub fn y_gte_z(&self) -> bool { + self.y_gt_z() || self.y_eq_z() + } + + pub fn x_lte_z(&self) -> bool { + self.x_lt_z() || self.x_eq_z() + } + pub fn y_lte_z(&self) -> bool { + self.y_lt_z() || self.y_eq_z() + } + + pub fn x_gt_z(&self) -> bool { + matches!(self.x_ord_z, Some(std::cmp::Ordering::Greater)) + } + + pub fn y_gt_z(&self) -> bool { + matches!(self.x_ord_z, Some(std::cmp::Ordering::Greater)) + } + + pub fn x_lt_z(&self) -> bool { + matches!(self.x_ord_z, Some(std::cmp::Ordering::Less)) + } + + pub fn y_lt_z(&self) -> bool { + matches!(self.x_ord_z, Some(std::cmp::Ordering::Less)) + } + + pub fn x_eq_z(&self) -> bool { + matches!(self.x_ord_z, Some(std::cmp::Ordering::Equal)) + } + + pub fn y_eq_z(&self) -> bool { + matches!(self.y_ord_z, Some(std::cmp::Ordering::Equal)) + } + + pub fn x_lt_zero(&self) -> bool { + matches!(self.x_ord_zero, Some(std::cmp::Ordering::Less)) + } + + pub fn y_lt_zero(&self) -> bool { + matches!(self.y_ord_zero, Some(std::cmp::Ordering::Less)) + } + + pub fn x_eq_zero(&self) -> bool { + matches!(self.x_ord_zero, Some(std::cmp::Ordering::Equal)) + } + + pub fn x_gt_zero(&self) -> bool { + matches!(self.x_ord_zero, Some(std::cmp::Ordering::Greater)) + } + + pub fn y_gt_zero(&self) -> bool { + matches!(self.y_ord_zero, Some(std::cmp::Ordering::Greater)) + } + + pub fn y_eq_zero(&self) -> bool { + matches!(self.y_ord_zero, Some(std::cmp::Ordering::Equal)) + } + + pub fn x_gte_zero(&self) -> bool { + self.x_gt_zero() || self.x_eq_zero() + } + + pub fn y_gte_zero(&self) -> bool { + self.y_gt_zero() || self.y_eq_zero() + } + + pub fn x_lte_zero(&self) -> bool { + self.x_lt_zero() || self.x_eq_zero() + } + + pub fn y_lte_zero(&self) -> bool { + self.y_lt_zero() || self.y_eq_zero() + } + + pub fn x_eq_one(&self) -> bool { + matches!(self.x_ord_one, Some(std::cmp::Ordering::Equal)) + } + + pub fn y_eq_one(&self) -> bool { + matches!(self.y_ord_one, Some(std::cmp::Ordering::Equal)) + } +} diff --git a/crates/graph/src/range/elem/expr/simplify/sub.rs b/crates/graph/src/range/elem/expr/simplify/sub.rs new file mode 100644 index 00000000..d99f2d85 --- /dev/null +++ b/crates/graph/src/range/elem/expr/simplify/sub.rs @@ -0,0 +1,177 @@ +use crate::{ + nodes::Concrete, + range::{ + elem::expr::simplify::Ords, + elem::{Elem, RangeConcrete, RangeExpr, RangeOp}, + }, +}; + +use shared::RangeArena; + +pub fn sub_ord_rules( + x: &Elem, + y: &Elem, + ord_op: RangeOp, + z: &Elem, + ords: Ords, + arena: &mut RangeArena>, +) -> Option> { + match ord_op { + RangeOp::Eq => { + if !ords.x_eq_z() { + return None; + } + // x - y == x + // ==> true iff y == 0, false otherwise + let res = if ords.y_eq_zero() { + Elem::from(true) + } else { + Elem::from(false) + }; + Some(res) + } + RangeOp::Neq => { + if !ords.x_eq_z() { + return None; + } + // x - y != x + // ==> true iff y != 0, false otherwise + let res = if ords.y_eq_zero() { + Elem::from(false) + } else { + Elem::from(true) + }; + Some(res) + } + RangeOp::Lt => { + // x - y < z + // ==> true if: + // x <= z && y > 0 + // ==> false if + // x == z && y < 0 + let x_lte_z = ords.x_eq_z() || ords.x_lt_z(); + if x_lte_z && ords.y_gt_zero() { + Some(Elem::from(true)) + } else if ords.x_eq_z() && ords.y_lt_zero() { + Some(Elem::from(false)) + } else { + None + } + } + RangeOp::Gt => { + // x - y > z + // ==> true if: + // x > z && y <= 0 || x >= z && y < 0 + // ==> false if + // x <= z && y > 0 + let true_lhs = ords.x_gt_z() && (ords.y_lt_zero() || ords.y_eq_zero()); + let true_rhs = (ords.x_gt_z() || ords.x_eq_z()) && ords.y_lt_zero(); + let x_lte_z = ords.x_eq_z() || ords.x_lt_z(); + + if true_lhs || true_rhs { + Some(Elem::from(true)) + } else if x_lte_z && ords.y_gt_zero() { + Some(Elem::from(false)) + } else { + None + } + } + RangeOp::Lte => { + // x - y <= z + + // ==> true if: + // x <= z && y >= 0 + let x_lte_z = ords.x_eq_z() || ords.x_lt_z(); + let y_gte_zero = ords.y_gt_zero() || ords.y_eq_zero(); + + // ==> false if: + // x > z && y <= 0 || x >= z && y < 0 + let x_gt_z = ords.x_gt_z(); + let y_lte_zero = ords.y_lt_zero() || ords.y_eq_zero(); + let lhs = x_gt_z && y_lte_zero; + + let x_gte_z = ords.x_gt_z() || ords.x_eq_z(); + let y_lt_zero = ords.y_lt_zero(); + let rhs = x_gte_z && y_lt_zero; + let false_cond = lhs || rhs; + + if x_lte_z && y_gte_zero { + Some(Elem::from(true)) + } else if false_cond { + Some(Elem::from(false)) + } else { + None + } + } + RangeOp::Gte => { + // x - y >= z + + // ==> true if: + // x >= z && y <= 0 + let x_gte_z = ords.x_eq_z() || ords.x_gt_z(); + let y_lte_zero = ords.y_lt_zero() || ords.y_eq_zero(); + + // ==> false if: + // x < z && y >= 0 || x <= z && y > 0 + let x_lt_z = ords.x_lt_z(); + let y_gte_zero = ords.y_gt_zero() || ords.y_eq_zero(); + let lhs = x_lt_z && y_gte_zero; + + let x_lte_z = ords.x_lt_z() || ords.x_eq_z(); + let y_gt_zero = ords.y_gt_zero(); + let rhs = x_lte_z && y_gt_zero; + let false_cond = lhs || rhs; + + if x_lte_z && y_gte_zero { + Some(Elem::from(true)) + } else if false_cond { + Some(Elem::from(false)) + } else { + None + } + } + RangeOp::Max => { + // max{x - y, z} + // same as gt but return lhs or rhs instead + match sub_ord_rules(x, y, RangeOp::Gt, z, ords, arena) { + Some(Elem::Concrete(RangeConcrete { + val: Concrete::Bool(b), + .. + })) => { + if b { + Some(Elem::Expr(RangeExpr::new( + x.clone(), + RangeOp::Sub(false), + y.clone(), + ))) + } else { + Some(z.clone()) + } + } + _ => None, + } + } + RangeOp::Min => { + // min{x - y, z} + // same as lt but return lhs or rhs instead + match sub_ord_rules(x, y, RangeOp::Lt, z, ords, arena) { + Some(Elem::Concrete(RangeConcrete { + val: Concrete::Bool(b), + .. + })) => { + if b { + Some(Elem::Expr(RangeExpr::new( + x.clone(), + RangeOp::Sub(false), + y.clone(), + ))) + } else { + Some(z.clone()) + } + } + _ => None, + } + } + _ => None, + } +} diff --git a/crates/graph/src/range/elem/map_or_array.rs b/crates/graph/src/range/elem/map_or_array.rs index b0aa3763..2f1f0e08 100644 --- a/crates/graph/src/range/elem/map_or_array.rs +++ b/crates/graph/src/range/elem/map_or_array.rs @@ -1,16 +1,22 @@ use crate::{ - nodes::{Concrete, ContextVarNode}, - range::elem::{Elem, MinMaxed, RangeElem}, + nodes::{Builtin, Concrete, ContextVarNode}, + range::{ + elem::{Elem, MinMaxed, RangeConcrete, RangeElem}, + exec_traits::{RangeCast, RangeMemLen}, + }, GraphBackend, GraphError, }; -use std::hash::Hash; -use std::hash::Hasher; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; +use ethers_core::types::{H256, U256}; use solang_parser::pt::Loc; use std::collections::BTreeMap; +use std::hash::Hash; +use std::hash::Hasher; + +use super::rc_uint256; /// A concrete value for a range element #[derive(Clone, Debug, Ord, PartialOrd)] @@ -40,7 +46,7 @@ impl PartialEq for RangeDyn { } impl Eq for RangeDyn {} -impl Hash for RangeDyn { +impl Hash for RangeDyn { fn hash(&self, state: &mut H) { (*self.len).hash(state); self.val.hash(state); @@ -108,7 +114,7 @@ impl RangeDyn { flattened_max: None, len: Box::new(Elem::Null), val, - op_num, + op_num: op_num - 1, loc, } } @@ -126,17 +132,46 @@ impl RangeDyn { } impl RangeDyn { - pub fn as_bytes(&self, analyzer: &impl GraphBackend, maximize: bool) -> Option> { + pub fn as_sized_bytes(&self) -> Option> { + let len = self.range_get_length()?; + let uint_val = len.maybe_concrete()?.val.uint_val()?; + if uint_val <= 32.into() { + let v = vec![0u8; uint_val.as_usize()]; + let conc = Concrete::from(v) + .cast(Builtin::Bytes(uint_val.as_usize() as u8)) + .unwrap(); + let to_cast = RangeConcrete::new(conc, Loc::Implicit); + self.range_cast(&to_cast) + } else { + None + } + } + pub fn as_bytes( + &self, + analyzer: &impl GraphBackend, + maximize: bool, + arena: &mut RangeArena>, + ) -> Option> { let len = if maximize { - let as_u256 = self.len.maximize(analyzer).ok()?.concrete()?.into_u256()?; + let as_u256 = self + .len + .maximize(analyzer, arena) + .ok()? + .concrete()? + .into_u256()?; if as_u256 > usize::MAX.into() { usize::MAX } else { as_u256.as_usize() } } else { - let mut as_u256 = self.len.minimize(analyzer).ok()?.concrete()?.into_u256()?; - if let Some(max_key) = self.evaled_max_key(analyzer) { + let mut as_u256 = self + .len + .minimize(analyzer, arena) + .ok()? + .concrete()? + .into_u256()?; + if let Some(max_key) = self.evaled_max_key(analyzer, arena) { if let Some(max_key) = max_key.into_u256() { as_u256 = as_u256.max(max_key); } @@ -152,7 +187,7 @@ impl RangeDyn { Some( self.val .values() - .map(|v| v.0.as_bytes(analyzer, maximize)) + .map(|v| v.0.as_bytes(analyzer, maximize, arena)) .collect::>>>()? .into_iter() .flatten() @@ -161,49 +196,117 @@ impl RangeDyn { ) } - pub fn evaled_max_key(&self, analyzer: &impl GraphBackend) -> Option { + pub fn evaled_max_key( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Option { let mut evaled = self .val .keys() - .filter_map(|key| key.maximize(analyzer).ok()) + .filter_map(|key| key.maximize(analyzer, arena).ok()) .collect::>(); - evaled.sort_by(|a, b| a.range_ord(b, analyzer).unwrap_or(std::cmp::Ordering::Less)); + evaled.sort_by(|a, b| a.range_ord(b, arena).unwrap_or(std::cmp::Ordering::Less)); evaled.iter().take(1).next()?.concrete() } + + pub fn from_concrete(concrete: Concrete, loc: Loc) -> Option { + let (vals, len) = match concrete { + Concrete::Bytes(size, b) => ( + Some( + b.0.into_iter() + .take((size).into()) + .enumerate() + .map(|(i, b)| { + let mut h = H256::default(); + h.0[0] = b; + ( + rc_uint256(i as u128).into(), + RangeConcrete::new(Concrete::Bytes(1, h), Loc::Implicit).into(), + ) + }) + .collect::>(), + ), + Concrete::Uint(256, U256::from(size)), + ), + Concrete::DynBytes(b) => ( + Some( + b.iter() + .enumerate() + .map(|(i, by)| { + let mut h = H256::default(); + h.0[0] = *by; + ( + rc_uint256(i as u128).into(), + RangeConcrete::new(Concrete::Bytes(1, h), Loc::Implicit).into(), + ) + }) + .collect::>(), + ), + Concrete::Uint(256, U256::from(b.len())), + ), + Concrete::String(s) => ( + Some( + s.chars() + .enumerate() + .map(|(i, b): (usize, char)| { + let mut h = H256::default(); + h.0[0] = b as u8; + ( + rc_uint256(i as u128).into(), + RangeConcrete::new(Concrete::Bytes(1, h), Loc::Implicit).into(), + ) + }) + .collect::>(), + ), + Concrete::Uint(256, U256::from(s.len())), + ), + _ => (None, Concrete::Uint(256, 0.into())), + }; + + let mut s = Self::new_for_indices(vals?, loc); + s.len = Box::new(RangeConcrete::new(len, loc).into()); + Some(s) + } } impl RangeElem for RangeDyn { type GraphError = GraphError; - fn arenaize(&mut self, analyzer: &mut impl GraphBackend) -> Result<(), GraphError> { - // self.cache_flatten(analyzer)?; - // self.cache_minimize(analyzer)?; - // self.cache_maximize(analyzer)?; - self.len.arenaize(analyzer)?; + fn arenaize( + &mut self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { + self.len.arenaize(analyzer, arena)?; self.val = self .val .iter_mut() .map(|(k, (v, op))| { let mut new_k = k.clone(); let mut new_v = v.clone(); - new_k.arenaize(analyzer); - new_v.arenaize(analyzer); + new_k.arenaize(analyzer, arena); + new_v.arenaize(analyzer, arena); (new_k, (new_v, *op)) }) .collect(); Ok(()) } - fn range_eq(&self, other: &Self, analyzer: &impl GraphBackend) -> bool { + fn range_eq(&self, other: &Self, arena: &mut RangeArena>) -> bool { matches!( - self.range_ord(other, analyzer), + self.range_ord(other, arena), Some(std::cmp::Ordering::Equal) ) } - fn range_ord(&self, other: &Self, analyzer: &impl GraphBackend) -> Option { - match self.len.range_ord(&other.len, analyzer) { + fn range_ord( + &self, + other: &Self, + arena: &mut RangeArena>, + ) -> Option { + match self.len.range_ord(&other.len, arena) { Some(std::cmp::Ordering::Equal) => { let mut eq = 0; let mut self_lt = 0; @@ -211,9 +314,9 @@ impl RangeElem for RangeDyn { self.val.iter().zip(other.val.iter()).for_each( |((self_key, self_val), (other_key, other_val))| { if let Some(std::cmp::Ordering::Equal) = - self_key.clone().range_ord(other_key, analyzer) + self_key.clone().range_ord(other_key, arena) { - match self_val.0.clone().range_ord(&other_val.0, analyzer) { + match self_val.0.clone().range_ord(&other_val.0, arena) { Some(std::cmp::Ordering::Equal) => eq += 1, Some(std::cmp::Ordering::Less) => self_lt += 1, Some(std::cmp::Ordering::Greater) => self_gt += 1, @@ -237,12 +340,16 @@ impl RangeElem for RangeDyn { } } - fn dependent_on(&self, analyzer: &impl GraphBackend) -> Vec { - let mut deps: Vec = self.len.dependent_on(analyzer); + fn dependent_on( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Vec { + let mut deps: Vec = self.len.dependent_on(analyzer, arena); deps.extend( self.val .iter() - .flat_map(|(_, val)| val.0.dependent_on(analyzer)) + .flat_map(|(_, val)| val.0.dependent_on(analyzer, arena)) .collect::>(), ); deps @@ -251,12 +358,13 @@ impl RangeElem for RangeDyn { fn recursive_dependent_on( &self, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, GraphError> { - let mut deps: Vec = self.len.recursive_dependent_on(analyzer)?; + let mut deps: Vec = self.len.recursive_dependent_on(analyzer, arena)?; deps.extend( self.val .values() - .map(|val| val.0.recursive_dependent_on(analyzer)) + .map(|val| val.0.recursive_dependent_on(analyzer, arena)) .collect::>, _>>()? .iter() .flatten() @@ -265,7 +373,7 @@ impl RangeElem for RangeDyn { deps.extend( self.val .keys() - .map(|key| key.recursive_dependent_on(analyzer)) + .map(|key| key.recursive_dependent_on(analyzer, arena)) .collect::>, _>>()? .iter() .flatten() @@ -278,11 +386,12 @@ impl RangeElem for RangeDyn { &self, seen: &mut Vec, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result { let mut has_cycle = false; - has_cycle = has_cycle || self.len.has_cycle(seen, analyzer)?; + has_cycle = has_cycle || self.len.has_cycle(seen, analyzer, arena)?; self.val.iter().try_for_each(|(_, val)| { - has_cycle = has_cycle || val.0.has_cycle(seen, analyzer)?; + has_cycle = has_cycle || val.0.has_cycle(seen, analyzer, arena)?; Ok(()) })?; Ok(has_cycle) @@ -293,11 +402,12 @@ impl RangeElem for RangeDyn { var: ContextVarNode, seen: &mut Vec, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result { let mut deps_on = false; - deps_on |= self.len.depends_on(var, seen, analyzer)?; + deps_on |= self.len.depends_on(var, seen, analyzer, arena)?; self.val.iter().try_for_each(|(_, val)| { - deps_on |= val.0.depends_on(var, seen, analyzer)?; + deps_on |= val.0.depends_on(var, seen, analyzer, arena)?; Ok(()) })?; Ok(deps_on) @@ -307,6 +417,7 @@ impl RangeElem for RangeDyn { &self, maximize: bool, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, GraphError> { match (maximize, &self.flattened_min, &self.flattened_max) { (true, _, Some(flat)) | (false, Some(flat), _) => return Ok(*flat.clone()), @@ -317,13 +428,13 @@ impl RangeElem for RangeDyn { maximized: None, flattened_min: None, flattened_max: None, - len: Box::new(self.len.flatten(maximize, analyzer)?), + len: Box::new(self.len.flatten(maximize, analyzer, arena)?), val: { let mut map = BTreeMap::default(); for (idx, val) in self.val.clone().into_iter() { map.insert( - idx.flatten(maximize, analyzer)?, - (val.0.flatten(maximize, analyzer)?, val.1), + idx.flatten(maximize, analyzer, arena)?, + (val.0.flatten(maximize, analyzer, arena)?, val.1), ); } map @@ -333,45 +444,57 @@ impl RangeElem for RangeDyn { })) } - fn cache_flatten(&mut self, analyzer: &mut impl GraphBackend) -> Result<(), GraphError> { + fn cache_flatten( + &mut self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { if self.flattened_max.is_none() { - self.len.cache_flatten(analyzer)?; + self.len.cache_flatten(analyzer, arena)?; let mapping = std::mem::take(&mut self.val); self.val = mapping .into_iter() .map(|(mut idx, mut val)| { - idx.cache_flatten(analyzer).unwrap(); - val.0.cache_flatten(analyzer).unwrap(); + idx.cache_flatten(analyzer, arena).unwrap(); + val.0.cache_flatten(analyzer, arena).unwrap(); (idx, val) }) .collect(); - let flat_max = self.flatten(true, analyzer)?; - let simplified_flat_max = flat_max.simplify_maximize(analyzer)?; + let flat_max = self.flatten(true, analyzer, arena)?; + let simplified_flat_max = flat_max.simplify_maximize(analyzer, arena)?; self.flattened_max = Some(Box::new(simplified_flat_max)); } if self.flattened_min.is_none() { - self.len.cache_flatten(analyzer)?; + self.len.cache_flatten(analyzer, arena)?; let mapping = std::mem::take(&mut self.val); self.val = mapping .into_iter() .map(|(mut idx, mut val)| { - idx.cache_flatten(analyzer).unwrap(); - val.0.cache_flatten(analyzer).unwrap(); + idx.cache_flatten(analyzer, arena).unwrap(); + val.0.cache_flatten(analyzer, arena).unwrap(); (idx, val) }) .collect(); - let flat_min = self.flatten(false, analyzer)?; - let simplified_flat_min = flat_min.simplify_minimize(analyzer)?; + let flat_min = self.flatten(false, analyzer, arena)?; + let simplified_flat_min = flat_min.simplify_minimize(analyzer, arena)?; self.flattened_min = Some(Box::new(simplified_flat_min)); } Ok(()) } - fn is_flatten_cached(&self, _analyzer: &impl GraphBackend) -> bool { + fn is_flatten_cached( + &self, + _analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, + ) -> bool { self.flattened_min.is_some() && self.flattened_max.is_some() } - fn is_min_max_cached(&self, _analyzer: &impl GraphBackend) -> (bool, bool) { + fn is_min_max_cached( + &self, + _analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, + ) -> (bool, bool) { (self.minimized.is_some(), self.maximized.is_some()) } @@ -380,64 +503,68 @@ impl RangeElem for RangeDyn { node_idx: NodeIdx, new_idx: NodeIdx, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) { - self.len.filter_recursion(node_idx, new_idx, analyzer); + self.len + .filter_recursion(node_idx, new_idx, analyzer, arena); self.val = self .val .clone() .into_iter() .map(|(mut k, mut v)| { - k.filter_recursion(node_idx, new_idx, analyzer); - v.0.filter_recursion(node_idx, new_idx, analyzer); + k.filter_recursion(node_idx, new_idx, analyzer, arena); + v.0.filter_recursion(node_idx, new_idx, analyzer, arena); (k, v) }) .collect(); } - fn maximize(&self, analyzer: &impl GraphBackend) -> Result, GraphError> { + fn maximize( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { if let Some(MinMaxed::Maximized(cached)) = self.maximized.clone() { return Ok(*cached); } Ok(Elem::ConcreteDyn(Self::new_w_op_nums( - self.len.maximize(analyzer)?, + self.len.maximize(analyzer, arena)?, { let mut map: BTreeMap<_, (Elem, usize)> = BTreeMap::default(); for (idx, val) in self.val.clone().into_iter() { // We dont maximize the key so that any subsequent // `get_index` can find potential values - let maximized = val.0.maximize(analyzer)?; - map.insert(idx.simplify_maximize(analyzer)?, (maximized, val.1)); + let maximized = val.0.maximize(analyzer, arena)?; + map.insert(idx.simplify_maximize(analyzer, arena)?, (maximized, val.1)); } - // map.into_iter().filter(|(k, (v, op))| { - // *v != Elem::Null - // }).collect() map }, self.loc, ))) } - fn minimize(&self, analyzer: &impl GraphBackend) -> Result, GraphError> { + fn minimize( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { if let Some(MinMaxed::Minimized(cached)) = self.minimized.clone() { return Ok(*cached); } Ok(Elem::ConcreteDyn(Self::new_w_op_nums( - self.len.minimize(analyzer)?, + self.len.minimize(analyzer, arena)?, { let mut map: BTreeMap<_, (Elem, usize)> = BTreeMap::default(); for (idx, val) in self.val.clone().into_iter() { // We dont minimize the key so that any subsequent // `get_index` can find potential values - let minimized = val.0.minimize(analyzer)?; - map.insert(idx.simplify_minimize(analyzer)?, (minimized, val.1)); + let minimized = val.0.minimize(analyzer, arena)?; + map.insert(idx.simplify_minimize(analyzer, arena)?, (minimized, val.1)); } - // map.into_iter().filter(|(k, (v, op))| { - // *v != Elem::Null - // }).collect() map }, self.loc, @@ -447,18 +574,19 @@ impl RangeElem for RangeDyn { fn simplify_maximize( &self, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, GraphError> { if let Some(max) = &self.flattened_max { return Ok(*max.clone()); } Ok(Elem::ConcreteDyn(Self::new_w_op_nums( - self.len.simplify_maximize(analyzer)?, + self.len.simplify_maximize(analyzer, arena)?, { let mut map = BTreeMap::default(); for (idx, val) in self.val.clone().into_iter() { // We dont minimize the key so that any subsequent // `get_index` can find potential values - let simplified = val.0.simplify_maximize(analyzer)?; + let simplified = val.0.simplify_maximize(analyzer, arena)?; map.insert(idx, (simplified, val.1)); } map @@ -469,19 +597,20 @@ impl RangeElem for RangeDyn { fn simplify_minimize( &self, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, GraphError> { if let Some(min) = &self.flattened_min { return Ok(*min.clone()); } Ok(Elem::ConcreteDyn(Self::new_w_op_nums( - self.len.simplify_minimize(analyzer)?, + self.len.simplify_minimize(analyzer, arena)?, { let mut map = BTreeMap::default(); for (idx, val) in self.val.clone().into_iter() { // We dont minimize the key so that any subsequent // `get_index` can find potential values - let simplified = val.0.simplify_minimize(analyzer)?; + let simplified = val.0.simplify_minimize(analyzer, arena)?; map.insert(idx, (simplified, val.1)); } map @@ -490,36 +619,44 @@ impl RangeElem for RangeDyn { ))) } - fn cache_maximize(&mut self, g: &mut impl GraphBackend) -> Result<(), GraphError> { + fn cache_maximize( + &mut self, + g: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { if self.maximized.is_none() { - self.len.cache_maximize(g)?; + self.len.cache_maximize(g, arena)?; let mapping = std::mem::take(&mut self.val); self.val = mapping .into_iter() .map(|(mut idx, mut val)| { - idx.cache_maximize(g).unwrap(); - val.0.cache_maximize(g).unwrap(); + idx.cache_maximize(g, arena).unwrap(); + val.0.cache_maximize(g, arena).unwrap(); (idx, val) }) .collect(); - self.maximized = Some(MinMaxed::Maximized(Box::new(self.maximize(g)?))); + self.maximized = Some(MinMaxed::Maximized(Box::new(self.maximize(g, arena)?))); } Ok(()) } - fn cache_minimize(&mut self, g: &mut impl GraphBackend) -> Result<(), GraphError> { + fn cache_minimize( + &mut self, + g: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { if self.minimized.is_none() { - self.len.cache_minimize(g)?; + self.len.cache_minimize(g, arena)?; let mapping = std::mem::take(&mut self.val); self.val = mapping .into_iter() .map(|(mut idx, mut val)| { - idx.cache_minimize(g).unwrap(); - val.0.cache_minimize(g).unwrap(); + idx.cache_minimize(g, arena).unwrap(); + val.0.cache_minimize(g, arena).unwrap(); (idx, val) }) .collect(); - self.minimized = Some(MinMaxed::Minimized(Box::new(self.minimize(g)?))); + self.minimized = Some(MinMaxed::Minimized(Box::new(self.minimize(g, arena)?))); } Ok(()) } @@ -527,7 +664,5 @@ impl RangeElem for RangeDyn { fn uncache(&mut self) { self.minimized = None; self.maximized = None; - // self.flattened_min = None; - // self.flattened_max = None; } } diff --git a/crates/graph/src/range/elem/mod.rs b/crates/graph/src/range/elem/mod.rs index cfb54157..dd1c7401 100644 --- a/crates/graph/src/range/elem/mod.rs +++ b/crates/graph/src/range/elem/mod.rs @@ -73,8 +73,6 @@ pub enum RangeOp { And, /// Logical OR Or, - /// Catch-all requirement statement - Where, /// Cast from one type to another Cast, /// Bitwise AND @@ -102,6 +100,57 @@ pub enum RangeOp { } impl RangeOp { + pub fn commutative(&self) -> bool { + use RangeOp::*; + match self { + Add(_i) => true, + Mul(_i) => true, + Sub(_i) => false, + Div(_i) => false, + Mod => false, + Exp => false, + Min => true, + Max => true, + + Eq => true, + Neq => true, + Lt => false, + Lte => false, + Gt => false, + Gte => false, + And => true, + Or => true, + Not => false, + + BitNot => false, + BitAnd => false, + BitXor => false, + BitOr => false, + Shl => false, + Shr => false, + + Cast => false, + + SetLength => false, + Memcopy => false, + GetLength => false, + SetIndices => false, + GetIndex => false, + Concat => false, + } + } + + pub fn non_commutative_logical_inverse(&self) -> Option { + use RangeOp::*; + match self { + Lt => Some(Gt), + Lte => Some(Gte), + Gt => Some(Lt), + Gte => Some(Lte), + _ => None, + } + } + /// Attempts to return the inverse range operation (e.g.: `RangeOp::Add => RangeOp::Sub`) pub fn inverse(self) -> Option { use RangeOp::*; @@ -176,7 +225,6 @@ impl ToString for RangeOp { Not => "!".to_string(), And => "&&".to_string(), Or => "||".to_string(), - Where => "where".to_string(), Cast => "cast".to_string(), BitAnd => "&".to_string(), BitOr => "|".to_string(), diff --git a/crates/graph/src/range/elem/reference.rs b/crates/graph/src/range/elem/reference.rs index 0ac79c12..84af8ef5 100644 --- a/crates/graph/src/range/elem/reference.rs +++ b/crates/graph/src/range/elem/reference.rs @@ -1,7 +1,7 @@ use crate::{ nodes::{Concrete, ContextVarNode}, range::{ - elem::{Elem, MinMaxed, RangeConcrete, RangeElem}, + elem::{Elem, MinMaxed, RangeArenaLike, RangeConcrete, RangeElem}, Range, }, GraphBackend, GraphError, TypeNode, VarType, @@ -9,7 +9,7 @@ use crate::{ use std::hash::Hash; use std::hash::Hasher; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; use solang_parser::pt::Loc; @@ -28,7 +28,7 @@ pub struct Reference { pub flattened_max: Option>>, } -impl Hash for Reference { +impl Hash for Reference { fn hash(&self, state: &mut H) { self.idx.hash(state); } @@ -56,19 +56,27 @@ impl Reference { impl RangeElem for Reference { type GraphError = GraphError; - fn arenaize(&mut self, analyzer: &mut impl GraphBackend) -> Result<(), GraphError> { - let smol = Elem::Reference(Reference::new(self.idx)); - if analyzer.range_arena_idx(&smol).is_none() { - let _ = analyzer.range_arena_idx_or_upsert(Elem::Reference(self.clone())); - } + fn arenaize( + &mut self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { + // let smol = Elem::Reference(Reference::new(self.idx)); + // if analyzer.range_arena_idx(&smol).is_none() { + let _ = arena.idx_or_upsert(Elem::Reference(self.clone()), analyzer); + // } Ok(()) } - fn range_eq(&self, _other: &Self, _analyzer: &impl GraphBackend) -> bool { + fn range_eq(&self, _other: &Self, _arena: &mut RangeArena>) -> bool { false } - fn range_ord(&self, other: &Self, _analyzer: &impl GraphBackend) -> Option { + fn range_ord( + &self, + other: &Self, + _arena: &mut RangeArena>, + ) -> Option { if self.idx == other.idx { Some(std::cmp::Ordering::Equal) } else { @@ -76,13 +84,18 @@ impl RangeElem for Reference { } } - fn dependent_on(&self, _analyzer: &impl GraphBackend) -> Vec { + fn dependent_on( + &self, + _analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, + ) -> Vec { vec![self.idx.into()] } fn recursive_dependent_on( &self, analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, ) -> Result, GraphError> { let mut deps = ContextVarNode(self.idx.index()).dependent_on(analyzer, true)?; deps.push(ContextVarNode(self.idx.index())); @@ -93,6 +106,7 @@ impl RangeElem for Reference { &self, seen: &mut Vec, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result { let cvar = ContextVarNode::from(self.idx); let mut has_cycle = false; @@ -101,8 +115,8 @@ impl RangeElem for Reference { } else { seen.push(cvar); if let Some(range) = cvar.ref_range(analyzer)? { - has_cycle = has_cycle || range.min.has_cycle(seen, analyzer)?; - has_cycle = has_cycle || range.max.has_cycle(seen, analyzer)?; + has_cycle = has_cycle || range.min.has_cycle(seen, analyzer, arena)?; + has_cycle = has_cycle || range.max.has_cycle(seen, analyzer, arena)?; Ok(has_cycle) } else { Ok(false) @@ -115,6 +129,7 @@ impl RangeElem for Reference { var: ContextVarNode, seen: &mut Vec, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result { let cvar = ContextVarNode::from(self.idx); if seen.contains(&cvar) { @@ -125,8 +140,8 @@ impl RangeElem for Reference { Ok(true) } else if let Some(range) = cvar.ref_range(analyzer)? { seen.push(cvar); - let mut deps_on = range.min.depends_on(var, seen, analyzer)?; - deps_on |= range.max.depends_on(var, seen, analyzer)?; + let mut deps_on = range.min.depends_on(var, seen, analyzer, arena)?; + deps_on |= range.max.depends_on(var, seen, analyzer, arena)?; Ok(deps_on) } else { Ok(false) @@ -137,6 +152,7 @@ impl RangeElem for Reference { &self, maximize: bool, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, GraphError> { match (maximize, &self.flattened_min, &self.flattened_max) { (true, _, Some(flat)) | (false, Some(flat), _) => { @@ -154,21 +170,24 @@ impl RangeElem for Reference { if maximize { cvar.range_max(analyzer)? .unwrap_or(Elem::Null) - .flatten(maximize, analyzer) + .flatten(maximize, analyzer, arena) } else { let flattened = cvar .range_min(analyzer)? .unwrap_or(Elem::Null) - .flatten(maximize, analyzer)?; + .flatten(maximize, analyzer, arena)?; Ok(flattened) } } - fn is_flatten_cached(&self, analyzer: &impl GraphBackend) -> bool { + fn is_flatten_cached( + &self, + _analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> bool { self.flattened_min.is_some() && self.flattened_max.is_some() || { - if let Some(idx) = analyzer.range_arena_idx(&Elem::Reference(Reference::new(self.idx))) - { - if let Ok(t) = analyzer.range_arena().ranges[idx].try_borrow() { + if let Some(idx) = arena.idx(&Elem::Reference(Reference::new(self.idx))) { + if let Some(t) = arena.ranges.get(idx) { if let Elem::Reference(ref arenaized) = *t { arenaized.flattened_min.is_some() && arenaized.flattened_max.is_some() } else { @@ -183,11 +202,14 @@ impl RangeElem for Reference { } } - fn is_min_max_cached(&self, analyzer: &impl GraphBackend) -> (bool, bool) { + fn is_min_max_cached( + &self, + _analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> (bool, bool) { let (arena_cached_min, arena_cached_max) = { - if let Some(idx) = analyzer.range_arena_idx(&Elem::Reference(Reference::new(self.idx))) - { - if let Ok(t) = analyzer.range_arena().ranges[idx].try_borrow() { + if let Some(idx) = arena.idx(&Elem::Reference(Reference::new(self.idx))) { + if let Some(t) = arena.ranges.get(idx) { if let Elem::Reference(ref arenaized) = *t { (arenaized.minimized.is_some(), arenaized.maximized.is_some()) } else { @@ -206,12 +228,16 @@ impl RangeElem for Reference { ) } - fn cache_flatten(&mut self, g: &mut impl GraphBackend) -> Result<(), GraphError> { - self.arenaize(g)?; + fn cache_flatten( + &mut self, + g: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { + self.arenaize(g, arena)?; if self.flattened_max.is_none() { - if let Some(idx) = g.range_arena_idx(&Elem::Reference(Reference::new(self.idx))) { - if let Ok(t) = g.range_arena().ranges[idx].try_borrow() { + if let Some(idx) = arena.idx(&Elem::Reference(Reference::new(self.idx))) { + if let Some(t) = arena.ranges.get(idx) { if let Elem::Reference(ref arenaized) = *t { if arenaized.flattened_max.is_some() { tracing::trace!("reference cache flatten hit"); @@ -222,14 +248,14 @@ impl RangeElem for Reference { } let cvar = ContextVarNode::from(self.idx); - cvar.cache_flattened_range(g)?; - let flat_max = self.flatten(true, g)?; - let simplified_flat_max = flat_max.simplify_maximize(g)?; + cvar.cache_flattened_range(g, arena)?; + let flat_max = self.flatten(true, g, arena)?; + let simplified_flat_max = flat_max.simplify_maximize(g, arena)?; self.flattened_max = Some(Box::new(simplified_flat_max)); } if self.flattened_min.is_none() { - if let Some(idx) = g.range_arena_idx(&Elem::Reference(Reference::new(self.idx))) { - if let Ok(t) = g.range_arena().ranges[idx].try_borrow() { + if let Some(idx) = arena.idx(&Elem::Reference(Reference::new(self.idx))) { + if let Some(t) = arena.ranges.get(idx) { if let Elem::Reference(ref arenaized) = *t { if arenaized.flattened_min.is_some() { tracing::trace!("reference cache flatten hit"); @@ -239,23 +265,34 @@ impl RangeElem for Reference { } } let cvar = ContextVarNode::from(self.idx); - cvar.cache_flattened_range(g)?; - let flat_min = self.flatten(false, g)?; - let simplified_flat_min = flat_min.simplify_minimize(g)?; + cvar.cache_flattened_range(g, arena)?; + let flat_min = self.flatten(false, g, arena)?; + let simplified_flat_min = flat_min.simplify_minimize(g, arena)?; self.flattened_min = Some(Box::new(simplified_flat_min)); } Ok(()) } - fn filter_recursion(&mut self, _: NodeIdx, _: NodeIdx, _analyzer: &mut impl GraphBackend) {} + fn filter_recursion( + &mut self, + _: NodeIdx, + _: NodeIdx, + _analyzer: &mut impl GraphBackend, + _arena: &mut RangeArena>, + ) { + } - fn maximize(&self, analyzer: &impl GraphBackend) -> Result, GraphError> { + fn maximize( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { if let Some(MinMaxed::Maximized(cached)) = self.maximized.clone() { return Ok(*cached); } - if let Some(idx) = analyzer.range_arena_idx(&Elem::Reference(Reference::new(self.idx))) { - if let Ok(t) = analyzer.range_arena().ranges[idx].try_borrow() { + if let Some(idx) = arena.idx(&Elem::Reference(Reference::new(self.idx))) { + if let Some(t) = arena.ranges.get(idx) { if let Elem::Reference(ref arenaized) = *t { tracing::trace!("reference maximize cache hit"); if let Some(MinMaxed::Maximized(cached)) = arenaized.maximized.clone() { @@ -272,26 +309,30 @@ impl RangeElem for Reference { | VarType::User(TypeNode::Ty(_), maybe_range) | VarType::BuiltIn(_, maybe_range) => { if let Some(range) = maybe_range { - range.evaled_range_max(analyzer) + range.evaled_range_max(analyzer, arena) } else { Ok(Elem::Reference(self.clone())) } } - VarType::Concrete(concrete_node) => Ok(Elem::Concrete(RangeConcrete { - val: concrete_node.underlying(analyzer)?.clone(), - loc: cvar.loc.unwrap_or(Loc::Implicit), - })), + VarType::Concrete(concrete_node) => Ok(Elem::Concrete(RangeConcrete::new( + concrete_node.underlying(analyzer)?.clone(), + cvar.loc.unwrap_or(Loc::Implicit), + ))), _e => Ok(Elem::Reference(self.clone())), } } - fn minimize(&self, analyzer: &impl GraphBackend) -> Result, GraphError> { + fn minimize( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { if let Some(MinMaxed::Minimized(cached)) = self.minimized.clone() { return Ok(*cached); } - if let Some(idx) = analyzer.range_arena_idx(&Elem::Reference(Reference::new(self.idx))) { - if let Ok(t) = analyzer.range_arena().ranges[idx].try_borrow() { + if let Some(idx) = arena.idx(&Elem::Reference(Reference::new(self.idx))) { + if let Some(t) = arena.ranges.get(idx) { if let Elem::Reference(ref arenaized) = *t { if let Some(MinMaxed::Minimized(cached)) = arenaized.minimized.clone() { tracing::trace!("reference minimize cache hit"); @@ -308,15 +349,15 @@ impl RangeElem for Reference { | VarType::User(TypeNode::Ty(_), maybe_range) | VarType::BuiltIn(_, maybe_range) => { if let Some(range) = maybe_range { - range.evaled_range_min(analyzer) + range.evaled_range_min(analyzer, arena) } else { Ok(Elem::Reference(self.clone())) } } - VarType::Concrete(concrete_node) => Ok(Elem::Concrete(RangeConcrete { - val: concrete_node.underlying(analyzer)?.clone(), - loc: cvar.loc.unwrap_or(Loc::Implicit), - })), + VarType::Concrete(concrete_node) => Ok(Elem::Concrete(RangeConcrete::new( + concrete_node.underlying(analyzer)?.clone(), + cvar.loc.unwrap_or(Loc::Implicit), + ))), _e => Ok(Elem::Reference(self.clone())), } } @@ -324,13 +365,14 @@ impl RangeElem for Reference { fn simplify_maximize( &self, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, GraphError> { if let Some(simp_max) = &self.flattened_max { return Ok(*simp_max.clone()); } - if let Some(idx) = analyzer.range_arena_idx(&Elem::Reference(Reference::new(self.idx))) { - if let Ok(t) = analyzer.range_arena().ranges[idx].try_borrow() { + if let Some(idx) = arena.idx(&Elem::Reference(Reference::new(self.idx))) { + if let Some(t) = arena.ranges.get(idx) { if let Elem::Reference(ref arenaized) = *t { if arenaized.flattened_max.is_some() { tracing::trace!("reference simplify maximize cache hit"); @@ -348,20 +390,23 @@ impl RangeElem for Reference { cvar.global_first_version(analyzer).into(), ))) } else { - self.flatten(true, analyzer)?.simplify_maximize(analyzer) + self.flatten(true, analyzer, arena)? + .simplify_maximize(analyzer, arena) } } fn simplify_minimize( &self, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, GraphError> { + let cvar = ContextVarNode::from(self.idx); if let Some(simp_min) = &self.flattened_min { return Ok(*simp_min.clone()); } - if let Some(idx) = analyzer.range_arena_idx(&Elem::Reference(Reference::new(self.idx))) { - if let Ok(t) = analyzer.range_arena().ranges[idx].try_borrow() { + if let Some(idx) = arena.idx(&Elem::Reference(Reference::new(self.idx))) { + if let Some(t) = arena.ranges.get(idx) { if let Elem::Reference(ref arenaized) = *t { if arenaized.flattened_min.is_some() { tracing::trace!("reference simplify minimize cache hit"); @@ -371,35 +416,43 @@ impl RangeElem for Reference { } } - let cvar = ContextVarNode::from(self.idx); if cvar.is_fundamental(analyzer)? { Ok(Elem::Reference(Reference::new( cvar.global_first_version(analyzer).into(), ))) } else { - self.flatten(false, analyzer)?.simplify_minimize(analyzer) + self.flatten(false, analyzer, arena)? + .simplify_minimize(analyzer, arena) } } - fn cache_maximize(&mut self, g: &mut impl GraphBackend) -> Result<(), GraphError> { - self.arenaize(g)?; + fn cache_maximize( + &mut self, + g: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { + self.arenaize(g, arena)?; if self.maximized.is_none() { let cvar = ContextVarNode::from(self.idx); - cvar.cache_eval_range(g)?; - let max = self.maximize(g)?; - Elem::Reference(Reference::new(self.idx)).set_arenaized_cache(true, &max, g); + cvar.cache_eval_range(g, arena)?; + let max = self.maximize(g, arena)?; + Elem::Reference(Reference::new(self.idx)).set_arenaized_cache(true, &max, arena); self.maximized = Some(MinMaxed::Maximized(Box::new(max))); } Ok(()) } - fn cache_minimize(&mut self, g: &mut impl GraphBackend) -> Result<(), GraphError> { - self.arenaize(g)?; + fn cache_minimize( + &mut self, + g: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { + self.arenaize(g, arena)?; if self.minimized.is_none() { let cvar = ContextVarNode::from(self.idx); - cvar.cache_eval_range(g)?; - let min = self.minimize(g)?; - Elem::Reference(Reference::new(self.idx)).set_arenaized_cache(false, &min, g); + cvar.cache_eval_range(g, arena)?; + let min = self.minimize(g, arena)?; + Elem::Reference(Reference::new(self.idx)).set_arenaized_cache(false, &min, arena); self.minimized = Some(MinMaxed::Minimized(Box::new(min))); } diff --git a/crates/graph/src/range/exec/add.rs b/crates/graph/src/range/exec/add.rs deleted file mode 100644 index 0e1bc7b3..00000000 --- a/crates/graph/src/range/exec/add.rs +++ /dev/null @@ -1,111 +0,0 @@ -use crate::nodes::Concrete; -use crate::range::{elem::*, exec_traits::*}; - -use ethers_core::types::{I256, U256}; - -impl RangeAdd for RangeConcrete { - fn range_add(&self, other: &Self) -> Option> { - match (self.val.into_u256(), other.val.into_u256()) { - (Some(lhs_val), Some(rhs_val)) => { - let max = Concrete::max(&self.val).unwrap(); - let max_uint = max.into_u256().unwrap(); - Some(Elem::Concrete(RangeConcrete { - val: self - .val - .u256_as_original(lhs_val.saturating_add(rhs_val).min(max_uint)), - loc: self.loc, - })) - } - _ => { - match (&self.val, &other.val) { - (Concrete::Uint(lhs_size, val), Concrete::Int(_, neg_v)) - | (Concrete::Int(lhs_size, neg_v), Concrete::Uint(_, val)) => { - // neg_v guaranteed to be negative here - if neg_v.into_raw() > *val { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int( - *lhs_size, - neg_v.saturating_add(I256::from_raw(*val)), - ), - loc: self.loc, - })) - } else { - Some(Elem::Concrete(RangeConcrete { - val: self - .val - .u256_as_original(val.saturating_sub(neg_v.into_raw())), - loc: self.loc, - })) - } - } - (Concrete::Int(lhs_size, l), Concrete::Int(_rhs_size, r)) => { - let max = if *lhs_size == 256 { - I256::MAX - } else { - I256::from_raw(U256::from(1u8) << U256::from(*lhs_size - 1)) - - I256::from(1) - }; - let min = max * I256::from(-1i32) - I256::from(1i32); - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, l.saturating_add(*r).max(min)), - loc: self.loc, - })) - } - _ => None, - } - } - } - } - fn range_wrapping_add(&self, other: &Self) -> Option> { - match (self.val.into_u256(), other.val.into_u256()) { - (Some(lhs_val), Some(rhs_val)) => Some(Elem::Concrete(RangeConcrete { - val: self - .val - .u256_as_original(lhs_val.overflowing_add(rhs_val).0), - loc: self.loc, - })), - _ => match (&self.val, &other.val) { - (Concrete::Uint(lhs_size, val), Concrete::Int(_, neg_v)) - | (Concrete::Int(lhs_size, neg_v), Concrete::Uint(_, val)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int( - *lhs_size, - I256::from_raw(neg_v.into_raw().overflowing_add(*val).0), - ), - loc: self.loc, - })) - } - (Concrete::Int(lhs_size, l), Concrete::Int(_rhs_size, r)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, l.overflowing_add(*r).0), - loc: self.loc, - })) - } - _ => None, - }, - } - } -} - -impl RangeAdd for Elem { - fn range_add(&self, other: &Self) -> Option> { - match (self, other) { - (Elem::Concrete(a), _) if a.val.into_u256() == Some(U256::zero()) => { - Some(other.clone()) - } - (_, Elem::Concrete(b)) if b.val.into_u256() == Some(U256::zero()) => Some(self.clone()), - (Elem::Concrete(a), Elem::Concrete(b)) => a.range_add(b), - _ => None, - } - } - fn range_wrapping_add(&self, other: &Self) -> Option> { - match (self, other) { - (Elem::Concrete(a), _) if a.val.into_u256() == Some(U256::zero()) => { - Some(other.clone()) - } - (_, Elem::Concrete(b)) if b.val.into_u256() == Some(U256::zero()) => Some(self.clone()), - (Elem::Concrete(a), Elem::Concrete(b)) => a.range_wrapping_add(b), - _ => None, - } - } -} diff --git a/crates/graph/src/range/exec/bitwise.rs b/crates/graph/src/range/exec/bitwise.rs index a980fb7b..087f8165 100644 --- a/crates/graph/src/range/exec/bitwise.rs +++ b/crates/graph/src/range/exec/bitwise.rs @@ -1,101 +1,162 @@ -use crate::nodes::Concrete; +use crate::nodes::{Builtin, Concrete}; use crate::range::{elem::*, exec_traits::*}; -use ethers_core::types::{H256, U256}; +use crate::GraphBackend; + +use shared::RangeArena; + +use ethers_core::types::{H256, I256, U256}; impl RangeBitwise for RangeConcrete { fn range_bit_and(&self, other: &Self) -> Option> { match (&self.val, &other.val) { (Concrete::Uint(s, a), Concrete::Uint(s2, b)) => { + let op_res = *a & *b; let size = if s > s2 { s } else { s2 }; - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Uint(*size, *a & *b), - loc: self.loc, - })) + let val = Concrete::Uint(*size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) } (Concrete::Int(s, a), Concrete::Int(s2, b)) => { + let op_res = *a & *b; let size = if s > s2 { s } else { s2 }; - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*size, *a & *b), - loc: self.loc, - })) + let val = Concrete::Int(*size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) } (Concrete::Uint(s, u), Concrete::Int(s2, i)) | (Concrete::Int(s, i), Concrete::Uint(s2, u)) => { + let op_res = *u & i.into_raw(); let size = if s > s2 { s } else { s2 }; - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Uint(*size, *u & i.into_raw()), - loc: self.loc, - })) + let val = Concrete::Uint(*size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) } (Concrete::Bytes(s, a), Concrete::Bytes(s2, b)) => { + let op_res = a & b; let size = if s > s2 { s } else { s2 }; - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bytes(*size, a & b), - loc: self.loc, - })) + let val = Concrete::Bytes(*size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + (Concrete::DynBytes(v), _) if v.len() <= 32 => RangeConcrete::new( + Concrete::DynBytes(v.clone()).cast(Builtin::Bytes(v.len() as u8))?, + self.loc, + ) + .range_bit_and(other), + (_, Concrete::DynBytes(v)) if v.len() <= 32 => self.range_bit_and(&RangeConcrete::new( + Concrete::DynBytes(v.clone()).cast(Builtin::Bytes(v.len() as u8))?, + self.loc, + )), + _ => { + if let (Some(l), Some(r)) = (self.val.into_u256(), other.val.into_u256()) { + let op_res = l & r; + let val = self.val.u256_as_original(op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } else { + None + } } - _ => None, } } fn range_bit_or(&self, other: &Self) -> Option> { match (&self.val, &other.val) { (Concrete::Uint(s, a), Concrete::Uint(s2, b)) => { + let op_res = *a | *b; let size = if s > s2 { s } else { s2 }; - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Uint(*size, *a | *b), - loc: self.loc, - })) + let val = Concrete::Uint(*size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) } (Concrete::Int(s, a), Concrete::Int(s2, b)) => { + let op_res = *a | *b; let size = if s > s2 { s } else { s2 }; - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*size, *a | *b), - loc: self.loc, - })) + let val = Concrete::Int(*size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) } (Concrete::Bytes(s, a), Concrete::Bytes(s2, b)) => { + let op_res = a | b; let size = if s > s2 { s } else { s2 }; - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bytes(*size, a | b), - loc: self.loc, - })) + let val = Concrete::Bytes(*size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + (Concrete::DynBytes(v), _) if v.len() <= 32 => RangeConcrete::new( + Concrete::DynBytes(v.clone()).cast(Builtin::Bytes(v.len() as u8))?, + self.loc, + ) + .range_bit_or(other), + (_, Concrete::DynBytes(v)) if v.len() <= 32 => self.range_bit_or(&RangeConcrete::new( + Concrete::DynBytes(v.clone()).cast(Builtin::Bytes(v.len() as u8))?, + self.loc, + )), + _ => { + if let (Some(l), Some(r)) = (self.val.into_u256(), other.val.into_u256()) { + let op_res = l | r; + let val = self.val.u256_as_original(op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } else { + None + } } - _ => None, } } fn range_bit_xor(&self, other: &Self) -> Option> { match (&self.val, &other.val) { (Concrete::Uint(s, a), Concrete::Uint(s2, b)) => { + let op_res = *a ^ *b; let size = if s > s2 { s } else { s2 }; - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Uint(*size, *a ^ *b), - loc: self.loc, - })) + let val = Concrete::Uint(*size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) } (Concrete::Int(s, a), Concrete::Int(s2, b)) => { + let op_res = *a ^ *b; let size = if s > s2 { s } else { s2 }; - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*size, *a ^ *b), - loc: self.loc, - })) + let val = Concrete::Int(*size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) } (Concrete::Bytes(s, a), Concrete::Bytes(s2, b)) => { + let op_res = a ^ b; let size = if s > s2 { s } else { s2 }; - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bytes(*size, a ^ b), - loc: self.loc, - })) + let val = Concrete::Bytes(*size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + (Concrete::DynBytes(v), _) if v.len() <= 32 => RangeConcrete::new( + Concrete::DynBytes(v.clone()).cast(Builtin::Bytes(v.len() as u8))?, + self.loc, + ) + .range_bit_xor(other), + (_, Concrete::DynBytes(v)) if v.len() <= 32 => self.range_bit_xor(&RangeConcrete::new( + Concrete::DynBytes(v.clone()).cast(Builtin::Bytes(v.len() as u8))?, + self.loc, + )), + _ => { + if let (Some(l), Some(r)) = (self.val.into_u256(), other.val.into_u256()) { + let op_res = l ^ r; + let val = self.val.u256_as_original(op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } else { + None + } } - _ => None, } } fn range_bit_not(&self) -> Option> { match &self.val { Concrete::Uint(size, a) => { - let max = Concrete::max(&self.val).unwrap().uint_val().unwrap(); + let max = Concrete::max_of_type(&self.val) + .unwrap() + .uint_val() + .unwrap(); let val = U256( a.0.into_iter() .map(|i| !i) @@ -103,29 +164,29 @@ impl RangeBitwise for RangeConcrete { .try_into() .unwrap(), ); - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Uint(*size, val & max), - loc: self.loc, - })) + let op_res = val & max; + let rc = RangeConcrete::new(Concrete::Uint(*size, op_res), self.loc); + Some(rc.into()) } Concrete::Int(size, a) => { - let (val, _) = a.overflowing_neg(); - let (val, _) = val.overflowing_sub(1.into()); - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*size, val), - loc: self.loc, - })) + let (op_res, _) = a.overflowing_neg(); + let (op_res, _) = op_res.overflowing_sub(1.into()); + let rc = RangeConcrete::new(Concrete::Int(*size, op_res), self.loc); + Some(rc.into()) } Concrete::Bytes(s, a) => { - let mut h = H256::default(); + let mut op_res = H256::default(); (0..*s).for_each(|i| { - h.0[i as usize] = !a.0[i as usize]; + op_res.0[i as usize] = !a.0[i as usize]; }); - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bytes(*s, h), - loc: self.loc, - })) + let rc = RangeConcrete::new(Concrete::Bytes(*s, op_res), self.loc); + Some(rc.into()) } + Concrete::DynBytes(v) if v.len() <= 32 => RangeConcrete::new( + Concrete::DynBytes(v.clone()).cast(Builtin::Bytes(v.len() as u8))?, + self.loc, + ) + .range_bit_not(), _ => None, } } @@ -158,3 +219,598 @@ impl RangeBitwise for Elem { } } } + +/// Executes a bitwise `and` given the minimum and maximum of each element. It returns either the _minimum_ bound or _maximum_ bound +/// of the operation. +/// +/// ### Note +/// Signed integers use 2's complement representation so the maximum is 2size - 1 - 1, while unsigned integers are 2size - 1 +/// +/// +/// ### Truth Tables +/// Truth table for `checked div` operation: +/// +/// `todo!()` +/// +/// Truth table for `wrapping div` operation: +/// +/// `todo!()` +/// +pub fn exec_bit_and( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + match (lhs_min, lhs_max, rhs_min, rhs_max) { + (Elem::ConcreteDyn(d), _, _, _) => { + return exec_bit_and( + &d.as_sized_bytes()?, + lhs_max, + rhs_min, + rhs_max, + maximize, + analyzer, + arena, + ); + } + (_, Elem::ConcreteDyn(d), _, _) => { + return exec_bit_and( + lhs_min, + &d.as_sized_bytes()?, + rhs_min, + rhs_max, + maximize, + analyzer, + arena, + ); + } + (_, _, Elem::ConcreteDyn(d), _) => { + return exec_bit_and( + lhs_min, + lhs_max, + &d.as_sized_bytes()?, + rhs_max, + maximize, + analyzer, + arena, + ); + } + (_, _, _, Elem::ConcreteDyn(d)) => { + return exec_bit_and( + lhs_min, + lhs_max, + rhs_min, + &d.as_sized_bytes()?, + maximize, + analyzer, + arena, + ); + } + _ => {} + } + + let mut candidates = vec![]; + let bit_and = |lhs: &Elem<_>, rhs: &Elem<_>, candidates: &mut Vec>| { + if let Some(c) = lhs.range_bit_and(rhs) { + candidates.push(c); + } + }; + + // the max is the min of the maxes + match lhs_max.range_ord(rhs_max, arena) { + Some(std::cmp::Ordering::Less) => { + candidates.push(lhs_max.clone()); + } + Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) => { + candidates.push(rhs_max.clone()); + } + _ => {} + } + + bit_and(lhs_min, rhs_min, &mut candidates); + bit_and(lhs_min, rhs_max, &mut candidates); + bit_and(lhs_max, rhs_min, &mut candidates); + bit_and(lhs_max, rhs_max, &mut candidates); + + let zero = Elem::from(Concrete::from(U256::from(0))); + let negative_one = Elem::from(Concrete::from(I256::from(-1i32))); + + let min_contains = matches!( + rhs_min.range_ord(&zero, arena), + Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) + ); + + let max_contains = matches!( + rhs_max.range_ord(&zero, arena), + Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) + ); + + if min_contains && max_contains { + candidates.push(zero); + } + + let min_contains = matches!( + rhs_min.range_ord(&negative_one, arena), + Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) + ); + + let max_contains = matches!( + rhs_max.range_ord(&negative_one, arena), + Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) + ); + + if min_contains && max_contains { + candidates.push(lhs_min.clone()); + candidates.push(lhs_max.clone()); + } + + // Sort the candidates + candidates.sort_by(|a, b| match a.range_ord(b, arena) { + Some(r) => r, + _ => std::cmp::Ordering::Less, + }); + + if candidates.is_empty() { + return None; + } + + if maximize { + Some(candidates.remove(candidates.len() - 1)) + } else { + Some(candidates.remove(0)) + } +} + +/// Executes a bitwise `or` given the minimum and maximum of each element. It returns either the _minimum_ bound or _maximum_ bound +/// of the operation. +/// +/// ### Note +/// Signed integers use 2's complement representation so the maximum is 2size - 1 - 1, while unsigned integers are 2size - 1 +/// +/// +/// ### Truth Tables +/// Truth table for `checked div` operation: +/// +/// `todo!()` +/// +/// Truth table for `wrapping div` operation: +/// +/// `todo!()` +/// +pub fn exec_bit_or( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + match (lhs_min, lhs_max, rhs_min, rhs_max) { + (Elem::ConcreteDyn(d), _, _, _) => { + return exec_bit_or( + &d.as_sized_bytes()?, + lhs_max, + rhs_min, + rhs_max, + maximize, + analyzer, + arena, + ); + } + (_, Elem::ConcreteDyn(d), _, _) => { + return exec_bit_or( + lhs_min, + &d.as_sized_bytes()?, + rhs_min, + rhs_max, + maximize, + analyzer, + arena, + ); + } + (_, _, Elem::ConcreteDyn(d), _) => { + return exec_bit_or( + lhs_min, + lhs_max, + &d.as_sized_bytes()?, + rhs_max, + maximize, + analyzer, + arena, + ); + } + (_, _, _, Elem::ConcreteDyn(d)) => { + return exec_bit_or( + lhs_min, + lhs_max, + rhs_min, + &d.as_sized_bytes()?, + maximize, + analyzer, + arena, + ); + } + _ => {} + } + + let mut candidates = vec![]; + let bit_or = |lhs: &Elem<_>, rhs: &Elem<_>, candidates: &mut Vec>| { + if let Some(c) = lhs.range_bit_or(rhs) { + candidates.push(c); + } + }; + + bit_or(lhs_min, rhs_min, &mut candidates); + bit_or(lhs_min, rhs_max, &mut candidates); + bit_or(lhs_max, rhs_min, &mut candidates); + bit_or(lhs_max, rhs_max, &mut candidates); + + let negative_one = Elem::from(Concrete::from(I256::from(-1i32))); + + let min_contains = matches!( + rhs_min.range_ord(&negative_one, arena), + Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) + ); + + let max_contains = matches!( + rhs_max.range_ord(&negative_one, arena), + Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) + ); + + if min_contains && max_contains { + candidates.push(negative_one.clone()); + candidates.push(negative_one.clone()); + } + + // Sort the candidates + candidates.sort_by(|a, b| match a.range_ord(b, arena) { + Some(r) => r, + _ => std::cmp::Ordering::Less, + }); + + if candidates.is_empty() { + return None; + } + + if maximize { + Some(candidates.remove(candidates.len() - 1)) + } else { + Some(candidates.remove(0)) + } +} + +/// Executes a bitwise `xor` given the minimum and maximum of each element. It returns either the _minimum_ bound or _maximum_ bound +/// of the operation. +/// +/// ### Note +/// Signed integers use 2's complement representation so the maximum is 2size - 1 - 1, while unsigned integers are 2size - 1 +/// +/// +/// ### Truth Tables +/// Truth table for `checked div` operation: +/// +/// `todo!()` +/// +/// Truth table for `wrapping div` operation: +/// +/// `todo!()` +/// +pub fn exec_bit_xor( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + match (lhs_min, lhs_max, rhs_min, rhs_max) { + (Elem::ConcreteDyn(d), _, _, _) => { + return exec_bit_xor( + &d.as_sized_bytes()?, + lhs_max, + rhs_min, + rhs_max, + maximize, + analyzer, + arena, + ); + } + (_, Elem::ConcreteDyn(d), _, _) => { + return exec_bit_xor( + lhs_min, + &d.as_sized_bytes()?, + rhs_min, + rhs_max, + maximize, + analyzer, + arena, + ); + } + (_, _, Elem::ConcreteDyn(d), _) => { + return exec_bit_xor( + lhs_min, + lhs_max, + &d.as_sized_bytes()?, + rhs_max, + maximize, + analyzer, + arena, + ); + } + (_, _, _, Elem::ConcreteDyn(d)) => { + return exec_bit_xor( + lhs_min, + lhs_max, + rhs_min, + &d.as_sized_bytes()?, + maximize, + analyzer, + arena, + ); + } + _ => {} + } + + let mut candidates = vec![ + lhs_min.range_bit_xor(rhs_min), + lhs_min.range_bit_xor(rhs_max), + lhs_max.range_bit_xor(rhs_min), + lhs_max.range_bit_xor(rhs_max), + ]; + + let zero = Elem::from(Concrete::from(U256::from(0))); + let negative_one = Elem::from(Concrete::from(I256::from(-1i32))); + + let min_contains = matches!( + rhs_min.range_ord(&zero, arena), + Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) + ); + + let max_contains = matches!( + rhs_max.range_ord(&zero, arena), + Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) + ); + + if min_contains && max_contains { + // if the rhs contains zero, in xor, thats just itself + candidates.push(lhs_max.range_bit_xor(&zero)); + } + + let min_contains = matches!( + rhs_min.range_ord(&negative_one, arena), + Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) + ); + + let max_contains = matches!( + rhs_max.range_ord(&negative_one, arena), + Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) + ); + + if min_contains && max_contains { + candidates.push(lhs_min.range_bit_xor(&negative_one)); + candidates.push(lhs_max.range_bit_xor(&negative_one)); + } + + let mut candidates = candidates.into_iter().flatten().collect::>(); + candidates.sort_by(|a, b| match a.range_ord(b, arena) { + Some(r) => r, + _ => std::cmp::Ordering::Less, + }); + + if candidates.is_empty() { + return None; + } + + if maximize { + Some(candidates.remove(candidates.len() - 1)) + } else { + Some(candidates.remove(0)) + } +} + +/// Executes a bitwise `not` given the minimum and maximum of each element. It returns either the _minimum_ bound or _maximum_ bound +/// of the operation. +/// +/// ### Note +/// Signed integers use 2's complement representation so the maximum is 2size - 1 - 1, while unsigned integers are 2size - 1 +/// +/// +/// ### Truth Tables +/// Truth table for `checked div` operation: +/// +/// `todo!()` +/// +/// Truth table for `wrapping div` operation: +/// +/// `todo!()` +/// +pub fn exec_bit_not( + lhs_min: &Elem, + lhs_max: &Elem, + maximize: bool, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + match (lhs_min, lhs_max) { + (Elem::ConcreteDyn(d), _) => { + return exec_bit_not(&d.as_sized_bytes()?, lhs_max, maximize, analyzer, arena); + } + (_, Elem::ConcreteDyn(d)) => { + return exec_bit_not(lhs_min, &d.as_sized_bytes()?, maximize, analyzer, arena); + } + _ => {} + } + let mut candidates = vec![lhs_min.range_bit_not(), lhs_max.range_bit_not()]; + + let zero = Elem::from(Concrete::from(U256::from(0))); + + let min_contains = matches!( + lhs_min.range_ord(&zero, arena), + Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) + ); + + let max_contains = matches!( + lhs_max.range_ord(&zero, arena), + Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) + ); + + if min_contains && max_contains { + match lhs_min { + Elem::Concrete( + ref r @ RangeConcrete { + val: Concrete::Uint(..), + .. + }, + ) => candidates.push(Some(Concrete::max_of_type(&r.val).unwrap().into())), + Elem::Concrete( + ref r @ RangeConcrete { + val: Concrete::Int(..), + .. + }, + ) => candidates.push(Some(Concrete::min_of_type(&r.val).unwrap().into())), + _ => {} + } + } + + let mut candidates = candidates.into_iter().flatten().collect::>(); + candidates.sort_by(|a, b| match a.range_ord(b, arena) { + Some(r) => r, + _ => std::cmp::Ordering::Less, + }); + + if candidates.is_empty() { + return None; + } + + if maximize { + Some(candidates.remove(candidates.len() - 1)) + } else { + Some(candidates.remove(0)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ethers_core::types::{I256, U256}; + use solang_parser::pt::Loc; + + #[test] + fn and_uint_uint() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(15)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(256, U256::from(5)), Loc::Implicit); + let result = x.range_bit_and(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::from(5))); + } + + #[test] + fn and_int_int() { + let x = RangeConcrete::new(Concrete::Int(256, I256::from(-15i32)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(-5i32)), Loc::Implicit); + let result = x.range_bit_and(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::from(-15))); + } + + #[test] + fn and_bytes_bytes() { + let mut h: [u8; 32] = [0; 32]; + h[0..4].copy_from_slice(&[1, 1, 1, 1][..]); + let mut h2: [u8; 32] = [0; 32]; + h2[0..4].copy_from_slice(&[0, 1, 0, 1][..]); + let x = RangeConcrete::new(Concrete::from(h), Loc::Implicit); + let y = RangeConcrete::new(Concrete::from(h2), Loc::Implicit); + let result = x.range_bit_and(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::from(h2)); + } + + #[test] + fn or_uint_uint() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(15)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(256, U256::from(5)), Loc::Implicit); + let result = x.range_bit_or(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::from(15))); + } + + #[test] + fn or_int_int() { + let x = RangeConcrete::new(Concrete::Int(256, I256::from(-15i32)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(-5i32)), Loc::Implicit); + let result = x.range_bit_or(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::from(-5))); + } + + #[test] + fn or_bytes_bytes() { + let mut h: [u8; 32] = [0; 32]; + h[0..4].copy_from_slice(&[1, 1, 1, 1][..]); + let mut h2: [u8; 32] = [0; 32]; + h2[0..4].copy_from_slice(&[0, 1, 0, 1][..]); + let x = RangeConcrete::new(Concrete::from(h), Loc::Implicit); + let y = RangeConcrete::new(Concrete::from(h2), Loc::Implicit); + let result = x.range_bit_or(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::from(h)); + } + + #[test] + fn xor_uint_uint() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(15)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(256, U256::from(5)), Loc::Implicit); + let result = x.range_bit_xor(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::from(10))); + } + + #[test] + fn xor_int_int() { + let x = RangeConcrete::new(Concrete::Int(256, I256::from(-15i32)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(-5i32)), Loc::Implicit); + let result = x.range_bit_xor(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::from(10))); + } + + #[test] + fn xor_bytes_bytes() { + let mut h: [u8; 32] = [0; 32]; + h[0..4].copy_from_slice(&[1, 1, 1, 1][..]); + let mut h2: [u8; 32] = [0; 32]; + h2[0..4].copy_from_slice(&[0, 1, 0, 1][..]); + let x = RangeConcrete::new(Concrete::from(h), Loc::Implicit); + let y = RangeConcrete::new(Concrete::from(h2), Loc::Implicit); + let result = x.range_bit_xor(&y).unwrap().maybe_concrete_value().unwrap(); + + let mut expected: [u8; 32] = [0; 32]; + expected[0..3].copy_from_slice(&[1, 0, 1][..]); + assert_eq!(result.val, Concrete::from(expected)); + } + + #[test] + fn not_uint() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(15)), Loc::Implicit); + let result = x.range_bit_not().unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::MAX << 4)); + } + + #[test] + fn not_int() { + let x = RangeConcrete::new(Concrete::Int(256, I256::from(-15i32)), Loc::Implicit); + let result = x.range_bit_not().unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::from(14))); + } + + #[test] + fn not_bytes() { + let mut h: [u8; 32] = [0; 32]; + h[0..4].copy_from_slice(&[1; 4][..]); + let x = RangeConcrete::new(Concrete::from(h), Loc::Implicit); + let result = x.range_bit_not().unwrap().maybe_concrete_value().unwrap(); + + let mut expected: [u8; 32] = [255; 32]; + expected[0..4].copy_from_slice(&[254, 254, 254, 254][..]); + assert_eq!(result.val, Concrete::from(expected)); + } +} diff --git a/crates/graph/src/range/exec/cast.rs b/crates/graph/src/range/exec/cast.rs index cf3033cb..0c02d5a9 100644 --- a/crates/graph/src/range/exec/cast.rs +++ b/crates/graph/src/range/exec/cast.rs @@ -1,36 +1,35 @@ -use crate::nodes::Concrete; +use crate::nodes::{Builtin, Concrete}; use crate::range::{elem::*, exec_traits::*}; +use crate::GraphBackend; + +use shared::RangeArena; use ethers_core::types::{H256, U256}; use std::collections::BTreeMap; impl RangeCast for RangeConcrete { fn range_cast(&self, other: &Self) -> Option> { - Some(Elem::Concrete(RangeConcrete { - val: self.val.clone().cast_from(&other.val)?, - loc: self.loc, - })) + Some(Elem::Concrete(RangeConcrete::new( + self.val.clone().cast_from(&other.val)?, + self.loc, + ))) } } impl RangeCast> for RangeConcrete { fn range_cast(&self, other: &RangeDyn) -> Option> { - match (self.val.clone(), other.val.iter().take(1).next()) { - ( - Concrete::Bytes(size, val), - Some(( - _, - ( - Elem::Concrete(Self { - val: Concrete::Bytes(..), - .. - }), - _, - ), - )), - ) - | (Concrete::Bytes(size, val), None) => { - // let mut existing = other.val.clone(); + match ( + self.val.clone(), + other.val.values().take(1).next().map(|(a, _)| a), + ) { + (Concrete::Uint(size, val), o) if o.is_none() || o.unwrap().is_bytes() => { + RangeConcrete::new( + Concrete::Uint(size, val).cast(Builtin::Bytes((size / 8) as u8))?, + self.loc, + ) + .range_cast(other) + } + (Concrete::Bytes(size, val), o) if o.is_none() || o.unwrap().is_bytes() => { let new = val .0 .iter() @@ -43,28 +42,13 @@ impl RangeCast> for RangeConcrete { (idx, v) }) .collect::>(); - // existing.extend(new); Some(Elem::ConcreteDyn(RangeDyn::new( Elem::from(Concrete::from(U256::from(size))), new, other.loc, ))) } - ( - Concrete::DynBytes(val), - Some(( - _, - ( - Elem::Concrete(Self { - val: Concrete::Bytes(..), - .. - }), - _, - ), - )), - ) - | (Concrete::DynBytes(val), None) => { - // let mut existing = other.val.clone(); + (Concrete::DynBytes(val), o) if o.is_none() || o.unwrap().is_bytes() => { let new = val .iter() .enumerate() @@ -76,28 +60,13 @@ impl RangeCast> for RangeConcrete { (idx, v) }) .collect::>(); - // existing.extend(new); Some(Elem::ConcreteDyn(RangeDyn::new( Elem::from(Concrete::from(U256::from(val.len()))), new, other.loc, ))) } - ( - Concrete::String(val), - Some(( - _, - ( - Elem::Concrete(Self { - val: Concrete::String(..), - .. - }), - _, - ), - )), - ) - | (Concrete::String(val), None) => { - // let mut existing = other.val.clone(); + (Concrete::String(val), o) if o.is_none() || o.unwrap().is_string() => { let new = val .chars() .enumerate() @@ -109,7 +78,6 @@ impl RangeCast> for RangeConcrete { (idx, v) }) .collect::>(); - // existing.extend(new); Some(Elem::ConcreteDyn(RangeDyn::new( Elem::from(Concrete::from(U256::from(val.len()))), new, @@ -123,125 +91,31 @@ impl RangeCast> for RangeConcrete { impl RangeCast> for RangeDyn { fn range_cast(&self, other: &Self) -> Option> { - let val: Option<(_, &(Elem, usize))> = self.val.iter().take(1).next(); - let o_val: Option<(_, &(Elem, usize))> = other.val.iter().take(1).next(); + let val: Option<&Elem> = self.val.values().take(1).next().map(|(a, _)| a); + let o_val: Option<&Elem> = other.val.values().take(1).next().map(|(a, _)| a); + match (val, o_val) { - ( - Some(( - _, - &( - Elem::Concrete(RangeConcrete { - val: Concrete::Bytes(..), - .. - }), - _, - ), - )), - Some(( - _, - &( - Elem::Concrete(RangeConcrete { - val: Concrete::Bytes(..), - .. - }), - _, - ), - )), - ) - | ( - Some(( - _, - &( - Elem::Concrete(RangeConcrete { - val: Concrete::Bytes(..), - .. - }), - _, - ), - )), - None, - ) => Some(Elem::ConcreteDyn(self.clone())), - ( - Some(( - _, - ( - Elem::Concrete(RangeConcrete { - val: Concrete::Uint(..), - .. - }), - _, - ), - )), - Some(( - _, - ( - Elem::Concrete(RangeConcrete { - val: Concrete::Uint(..), - .. - }), - _, - ), - )), - ) - | ( - Some(( - _, - ( - Elem::Concrete(RangeConcrete { - val: Concrete::Uint(..), - .. - }), - _, - ), - )), - None, - ) => Some(Elem::ConcreteDyn(self.clone())), - ( - Some(( - _, - ( - Elem::Concrete(RangeConcrete { - val: Concrete::Int(..), - .. - }), - _, - ), - )), - Some(( - _, - ( - Elem::Concrete(RangeConcrete { - val: Concrete::Int(..), - .. - }), - _, - ), - )), - ) - | ( - Some(( - _, - ( - Elem::Concrete(RangeConcrete { - val: Concrete::Int(..), - .. - }), - _, - ), - )), - None, - ) => Some(Elem::ConcreteDyn(self.clone())), - (Some((_, (l @ Elem::Reference(_), _))), None) => Some(l.clone()), - (None, Some((_, (r @ Elem::Reference(_), _)))) => Some(r.clone()), + (Some(elem), Some(o_elem)) + if elem.is_bytes() && o_elem.is_bytes() + || elem.is_uint() && o_elem.is_uint() + || elem.is_int() && o_elem.is_int() => + { + Some(Elem::ConcreteDyn(self.clone())) + } + (Some(elem), None) if elem.is_bytes() || elem.is_uint() || elem.is_int() => { + Some(Elem::ConcreteDyn(self.clone())) + } + (Some(Elem::Reference(_)), None) => Some(Elem::ConcreteDyn(self.clone())), + (None, Some(Elem::Reference(_))) => Some(Elem::ConcreteDyn(self.clone())), (None, None) => Some(Elem::ConcreteDyn(self.clone())), - _e => None, + _ => None, } } } impl RangeCast> for RangeDyn { fn range_cast(&self, other: &RangeConcrete) -> Option> { - let (_k, (val, _op)): (_, &(Elem, _)) = self.val.iter().take(1).next()?; + let val: &Elem<_> = self.val.values().take(1).next().map(|(a, _)| a)?; let o_val = &other.val; match (val, o_val) { ( @@ -252,7 +126,7 @@ impl RangeCast> for RangeDyn { Concrete::Bytes(size, _), ) => { let mut h = H256::default(); - for (i, (_, val)) in self.val.iter().take(*size as usize).enumerate() { + for (i, val) in self.val.values().take(*size as usize).enumerate() { match val { ( Elem::Concrete(RangeConcrete { @@ -288,3 +162,101 @@ impl RangeCast for Elem { } } } + +pub fn exec_cast( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, + _analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + // the weird thing about cast is that we really dont know until after the cast due to sizing things + // so we should just try them all then compare + let candidates = vec![ + lhs_min.range_cast(rhs_min), + lhs_min.range_cast(rhs_max), + lhs_max.range_cast(rhs_min), + lhs_max.range_cast(rhs_max), + ]; + let mut candidates = candidates.into_iter().flatten().collect::>(); + candidates.sort_by(|a, b| match a.range_ord(b, arena) { + Some(r) => r, + _ => std::cmp::Ordering::Less, + }); + + if candidates.is_empty() { + return None; + } + + if maximize { + Some(candidates.remove(candidates.len() - 1)) + } else { + Some(candidates.remove(0)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ethers_core::types::I256; + use solang_parser::pt::Loc; + + #[test] + fn int_downcast() { + let x = RangeConcrete::new(Concrete::Int(256, I256::from(-1500)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(8, I256::from(0)), Loc::Implicit); + let result = x.range_cast(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(8, I256::from(36))); + } + + #[test] + fn uint_downcast() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(1500)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(8, U256::from(0)), Loc::Implicit); + let result = x.range_cast(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(8, U256::from(220))); + } + + #[test] + fn int_weirdness() { + // type(int64).max + let v = Concrete::max_of_type(&Concrete::Int(64, I256::from(0i32))) + .unwrap() + .int_val() + .unwrap(); + // int128(type(int64).max) + let x = RangeConcrete::new(Concrete::Int(128, v), Loc::Implicit); + // int128(type(int64).max) + 1 + let x = x + .range_add(&RangeConcrete::new( + Concrete::Int(256, I256::from(1)), + Loc::Implicit, + )) + .unwrap() + .maybe_concrete_value() + .unwrap(); + let expected = x.val.int_val().unwrap() * I256::from(-1i32); + let y = RangeConcrete::new(Concrete::Int(64, I256::from(0)), Loc::Implicit); + // int64(int128(type(int64).max) + 1) + let result = x.range_cast(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(64, expected)); + } + + #[test] + fn int_upcast() { + let x = rc_int_sized(-101); + let y = rc_int256(-101); + let result = x.range_cast(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::from(-101))); + } + + #[test] + fn bytes_upcast() { + let x = RangeConcrete::new(Concrete::from(vec![19, 55]), Loc::Implicit); + let y = RangeConcrete::new(Concrete::from(vec![0, 0, 0, 0]), Loc::Implicit); + let result = x.range_cast(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::from(vec![19, 55, 0, 0])); + } +} diff --git a/crates/graph/src/range/exec/div.rs b/crates/graph/src/range/exec/div.rs deleted file mode 100644 index 62476206..00000000 --- a/crates/graph/src/range/exec/div.rs +++ /dev/null @@ -1,146 +0,0 @@ -use crate::nodes::Concrete; -use crate::range::{elem::*, exec_traits::*}; - -use ethers_core::types::{I256, U256}; - -impl RangeDiv for RangeConcrete { - fn range_div(&self, other: &Self) -> Option> { - match (self.val.into_u256(), other.val.into_u256()) { - (Some(lhs_val), Some(rhs_val)) => { - if rhs_val == 0.into() { - None - } else { - Some(Elem::Concrete(RangeConcrete { - val: self.val.u256_as_original(lhs_val / rhs_val), - loc: self.loc, - })) - } - } - _ => match (&self.val, &other.val) { - (Concrete::Uint(lhs_size, val), Concrete::Int(_, neg_v)) => { - if neg_v == &I256::from(0) { - None - } else { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int( - *lhs_size, - I256::from_raw(val / neg_v.into_raw()) * I256::from(-1i32), - ), - loc: self.loc, - })) - } - } - (Concrete::Int(lhs_size, neg_v), Concrete::Uint(_, val)) => { - if val == &U256::from(0) { - None - } else { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, *neg_v / I256::from_raw(*val)), - loc: self.loc, - })) - } - } - (Concrete::Int(lhs_size, l), Concrete::Int(_rhs_size, r)) => { - if r == &I256::from(0) { - None - } else { - let (val, overflow) = l.overflowing_div(*r); - if overflow { - None - } else { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, val), - loc: self.loc, - })) - } - } - } - _ => None, - }, - } - } - - fn range_wrapping_div(&self, other: &Self) -> Option> { - match (self.val.into_u256(), other.val.into_u256()) { - (Some(lhs_val), Some(rhs_val)) => { - if rhs_val == 0.into() { - Some(Elem::Concrete(RangeConcrete { - val: self.val.u256_as_original(U256::zero()), - loc: self.loc, - })) - } else { - Some(Elem::Concrete(RangeConcrete { - val: self.val.u256_as_original(lhs_val / rhs_val), - loc: self.loc, - })) - } - } - _ => match (&self.val, &other.val) { - (Concrete::Uint(lhs_size, val), Concrete::Int(_, neg_v)) => { - if neg_v == &I256::from(0) { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, I256::from(0i32)), - loc: self.loc, - })) - } else { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int( - *lhs_size, - I256::from_raw(val / neg_v.into_raw()) * I256::from(-1i32), - ), - loc: self.loc, - })) - } - } - (Concrete::Int(lhs_size, neg_v), Concrete::Uint(_, val)) => { - if val == &U256::from(0) { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, I256::from(0i32)), - loc: self.loc, - })) - } else { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, *neg_v / I256::from_raw(*val)), - loc: self.loc, - })) - } - } - (Concrete::Int(lhs_size, l), Concrete::Int(_rhs_size, r)) => { - if r == &I256::from(0) { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, I256::from(0i32)), - loc: self.loc, - })) - } else { - let (val, overflow) = l.overflowing_div(*r); - if overflow { - None - } else { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, val), - loc: self.loc, - })) - } - } - } - _ => None, - }, - } - } -} - -impl RangeDiv for Elem { - fn range_div(&self, other: &Self) -> Option> { - match (self, other) { - (Elem::Concrete(a), Elem::Concrete(b)) => a.range_div(b), - _ => None, - } - } - - fn range_wrapping_div(&self, other: &Self) -> Option> { - match (self, other) { - (Elem::Concrete(a), Elem::Concrete(b)) => a.range_div(b), - _ => None, - } - } -} diff --git a/crates/graph/src/range/exec/exec_op.rs b/crates/graph/src/range/exec/exec_op.rs index 8de2986d..8f774fbe 100644 --- a/crates/graph/src/range/exec/exec_op.rs +++ b/crates/graph/src/range/exec/exec_op.rs @@ -1,11 +1,9 @@ use crate::{ nodes::Concrete, - range::{elem::*, exec_traits::*}, + range::{elem::*, exec::*, exec_traits::*}, GraphBackend, GraphError, }; - -use ethers_core::types::{I256, U256}; -use solang_parser::pt::Loc; +use shared::RangeArena; impl ExecOp for RangeExpr { type GraphError = GraphError; @@ -15,11 +13,13 @@ impl ExecOp for RangeExpr { &self, maximize: bool, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, Self::GraphError> { - let idx = self.arena_idx(analyzer); + let idx = self.arena_idx(arena); if let Some(idx) = idx { - if let Ok(t) = analyzer.range_arena().ranges[idx].try_borrow() { - if let Elem::Expr(expr) = &*t { + if let Some(t) = arena.ranges.get(idx) { + if let Elem::Expr(expr) = t { + tracing::trace!("hitting cache"); if maximize { if let Some(MinMaxed::Maximized(max)) = &expr.maximized { return Ok(*max.clone()); @@ -31,11 +31,12 @@ impl ExecOp for RangeExpr { } } - let res = self.exec(self.spread(analyzer)?, maximize, analyzer)?; + let res = self.exec(self.spread(analyzer, arena)?, maximize, analyzer, arena)?; if let Some(idx) = idx { - if let Ok(mut t) = analyzer.range_arena().ranges[idx].try_borrow_mut() { + if let Some(t) = arena.ranges.get_mut(idx) { if let Elem::Expr(expr) = &mut *t { + tracing::trace!("setting cache"); if maximize { expr.maximized = Some(MinMaxed::Maximized(Box::new(res.clone()))); } else { @@ -53,20 +54,28 @@ impl ExecOp for RangeExpr { &mut self, maximize: bool, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) -> Result<(), GraphError> { - self.lhs.cache_minimize(analyzer)?; - self.lhs.cache_maximize(analyzer)?; - self.rhs.cache_minimize(analyzer)?; - self.rhs.cache_maximize(analyzer)?; - let res = self.exec_op(maximize, analyzer)?; + tracing::trace!("minimize lhs"); + self.lhs.cache_minimize(analyzer, arena)?; + tracing::trace!("maximize lhs"); + self.lhs.cache_maximize(analyzer, arena)?; + tracing::trace!("minimize rhs"); + self.rhs.cache_minimize(analyzer, arena)?; + tracing::trace!("maximize rhs"); + self.rhs.cache_maximize(analyzer, arena)?; + tracing::trace!("exec"); + + let res = self.exec_op(maximize, analyzer, arena)?; + if maximize { self.maximized = Some(MinMaxed::Maximized(Box::new(res))); } else { self.minimized = Some(MinMaxed::Minimized(Box::new(res))); } - if let Some(idx) = self.arena_idx(analyzer) { - if let Ok(mut t) = analyzer.range_arena().ranges[idx].try_borrow_mut() { + if let Some(idx) = self.arena_idx(arena) { + if let Some(t) = arena.ranges.get_mut(idx) { if let Elem::Expr(expr) = &mut *t { if maximize { expr.maximized.clone_from(&self.maximized); @@ -90,6 +99,7 @@ impl ExecOp for RangeExpr { &self, maximize: bool, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, GraphError> { if maximize { if let Some(v) = &self.flattened_max { @@ -99,11 +109,11 @@ impl ExecOp for RangeExpr { return Ok(*v.clone()); } - if let Some(v) = self.arenaized_flat_cache(maximize, analyzer) { + if let Some(v) = self.arenaized_flat_cache(maximize, arena) { return Ok(*v); } - let (lhs_min, lhs_max, rhs_min, rhs_max) = self.simplify_spread(analyzer)?; + let (lhs_min, lhs_max, rhs_min, rhs_max) = self.simplify_spread(analyzer, arena)?; tracing::trace!( "simplifying op: {} {} {}, lhs_min: {}, lhs_max: {}, rhs_min: {}, rhs_max: {}", self.lhs, @@ -117,144 +127,144 @@ impl ExecOp for RangeExpr { let lhs_is_conc = lhs_min.is_conc() && lhs_max.is_conc(); let rhs_is_conc = rhs_min.is_conc() && rhs_max.is_conc(); - let mut finished = false; + let finished = false; let mut ret = Ok(Elem::Null); - if self.op == RangeOp::Cast { - // for a cast we can *actually* evaluate dynamic elem if lhs side is concrete - if lhs_is_conc { - ret = self.exec_op(maximize, analyzer); - finished = true; - } else { - // we can drop the cast if the max of the dynamic lhs is less than the cast - let concretized_lhs = self.lhs.maximize(analyzer)?; - if matches!( - concretized_lhs.range_ord(&self.rhs, analyzer), - Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) - ) { - ret = Ok(*self.lhs.clone()); - finished = true; - } - } - } else if matches!(self.op, RangeOp::Concat | RangeOp::Memcopy) { - // we can always execute a concat or memcopy - ret = self.exec_op(maximize, analyzer); - finished = true; - } else if matches!( - self.op, - RangeOp::SetIndices | RangeOp::SetLength | RangeOp::GetLength | RangeOp::GetIndex - ) { - match self.op { - RangeOp::GetLength => { - ret = if maximize { - Ok(lhs_max - .range_get_length() - .unwrap_or_else(|| Elem::Expr(self.clone()))) - } else { - Ok(lhs_min - .range_get_length() - .unwrap_or_else(|| Elem::Expr(self.clone()))) - }; - finished = true; - } - RangeOp::SetLength => { - ret = if maximize { - Ok(lhs_max - .range_set_length(&rhs_max) - .unwrap_or_else(|| Elem::Expr(self.clone()))) - } else { - Ok(lhs_min - .range_set_length(&rhs_min) - .unwrap_or_else(|| Elem::Expr(self.clone()))) - }; - finished = true; - } - RangeOp::GetIndex => { - if maximize { - let res = match lhs_max { - Elem::ConcreteDyn(RangeDyn { ref val, .. }) => val - .iter() - .try_fold( - None, - |mut acc: Option>, (key, (val, _))| { - if matches!( - key.overlaps_dual(&rhs_min, &rhs_max, true, analyzer)?, - Some(true) - ) { - if acc.is_none() - || matches!( - acc.clone().unwrap().range_ord(val, analyzer), - Some(std::cmp::Ordering::Greater) - ) - { - acc = Some(val.clone()); - Ok(acc) - } else { - Ok(acc) - } - } else { - Ok(acc) - } - }, - )? - .unwrap_or_else(|| Elem::Null), - _ => Elem::Expr(self.clone()), - }; - ret = Ok(res); - finished = true; - } else { - let res = match lhs_max { - Elem::ConcreteDyn(RangeDyn { ref val, .. }) => val - .iter() - .try_fold( - None, - |mut acc: Option>, (key, (val, _))| { - if matches!( - key.overlaps_dual(&rhs_min, &rhs_max, true, analyzer)?, - Some(true) - ) { - if acc.is_none() - || matches!( - acc.clone().unwrap().range_ord(val, analyzer), - Some(std::cmp::Ordering::Less) - ) - { - acc = Some(val.clone()); - Ok(acc) - } else { - Ok(acc) - } - } else { - Ok(acc) - } - }, - )? - .unwrap_or_else(|| Elem::Null), - _ => Elem::Expr(self.clone()), - }; - ret = Ok(res); - finished = true; - } - } - RangeOp::SetIndices => { - ret = if maximize { - Ok(lhs_max - .range_set_indices(&rhs_max) - .unwrap_or_else(|| Elem::Expr(self.clone()))) - } else { - Ok(lhs_min - .range_set_indices(&rhs_min) - .unwrap_or_else(|| Elem::Expr(self.clone()))) - }; - finished = true; - } - _ => unreachable!(), - } - } + // if self.op == RangeOp::Cast { + // // for a cast we can *actually* evaluate dynamic elem if lhs side is concrete + // if lhs_is_conc { + // ret = self.exec_op(maximize, analyzer); + // finished = true; + // } else { + // // we can drop the cast if the max of the dynamic lhs is less than the cast + // let concretized_lhs = self.lhs.maximize(analyzer, arena)?; + // if matches!( + // concretized_lhs.range_ord(&self.rhs, analyzer), + // Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) + // ) { + // ret = Ok(*self.lhs.clone()); + // finished = true; + // } + // } + // } else if matches!(self.op, RangeOp::Concat | RangeOp::Memcopy) { + // // we can always execute a concat or memcopy + // ret = self.exec_op(maximize, analyzer); + // finished = true; + // } else if matches!( + // self.op, + // RangeOp::SetIndices | RangeOp::SetLength | RangeOp::GetLength | RangeOp::GetIndex + // ) { + // match self.op { + // RangeOp::GetLength => { + // ret = if maximize { + // Ok(lhs_max + // .range_get_length() + // .unwrap_or_else(|| Elem::Expr(self.clone()))) + // } else { + // Ok(lhs_min + // .range_get_length() + // .unwrap_or_else(|| Elem::Expr(self.clone()))) + // }; + // finished = true; + // } + // RangeOp::SetLength => { + // ret = if maximize { + // Ok(lhs_max + // .range_set_length(&rhs_max) + // .unwrap_or_else(|| Elem::Expr(self.clone()))) + // } else { + // Ok(lhs_min + // .range_set_length(&rhs_min) + // .unwrap_or_else(|| Elem::Expr(self.clone()))) + // }; + // finished = true; + // } + // RangeOp::GetIndex => { + // if maximize { + // let res = match lhs_max { + // Elem::ConcreteDyn(RangeDyn { ref val, .. }) => val + // .iter() + // .try_fold( + // None, + // |mut acc: Option>, (key, (val, _))| { + // if matches!( + // key.overlaps_dual(&rhs_min, &rhs_max, true, analyzer)?, + // Some(true) + // ) { + // if acc.is_none() + // || matches!( + // acc.clone().unwrap().range_ord(val, arena), + // Some(std::cmp::Ordering::Greater) + // ) + // { + // acc = Some(val.clone()); + // Ok(acc) + // } else { + // Ok(acc) + // } + // } else { + // Ok(acc) + // } + // }, + // )? + // .unwrap_or_else(|| Elem::Null), + // _ => Elem::Expr(self.clone()), + // }; + // ret = Ok(res); + // finished = true; + // } else { + // let res = match lhs_max { + // Elem::ConcreteDyn(RangeDyn { ref val, .. }) => val + // .iter() + // .try_fold( + // None, + // |mut acc: Option>, (key, (val, _))| { + // if matches!( + // key.overlaps_dual(&rhs_min, &rhs_max, true, analyzer)?, + // Some(true) + // ) { + // if acc.is_none() + // || matches!( + // acc.clone().unwrap().range_ord(val, arena), + // Some(std::cmp::Ordering::Less) + // ) + // { + // acc = Some(val.clone()); + // Ok(acc) + // } else { + // Ok(acc) + // } + // } else { + // Ok(acc) + // } + // }, + // )? + // .unwrap_or_else(|| Elem::Null), + // _ => Elem::Expr(self.clone()), + // }; + // ret = Ok(res); + // finished = true; + // } + // } + // RangeOp::SetIndices => { + // ret = if maximize { + // Ok(lhs_max + // .range_set_indices(&rhs_max) + // .unwrap_or_else(|| Elem::Expr(self.clone()))) + // } else { + // Ok(lhs_min + // .range_set_indices(&rhs_min) + // .unwrap_or_else(|| Elem::Expr(self.clone()))) + // }; + // finished = true; + // } + // _ => unreachable!(), + // } + // } let parts = (lhs_min, lhs_max, rhs_min, rhs_max); match (lhs_is_conc, rhs_is_conc, finished) { (true, true, false) => { - ret = self.exec(parts, maximize, analyzer); + ret = self.exec(parts, maximize, analyzer, arena); } (_, _, false) => { ret = Ok(Elem::Expr(self.clone())); @@ -262,8 +272,8 @@ impl ExecOp for RangeExpr { _ => {} } - if let Some(_idx) = self.arena_idx(analyzer) { - self.set_arenaized_flattened(maximize, ret.clone()?, analyzer); + if let Some(_idx) = self.arena_idx(arena) { + self.set_arenaized_flattened(maximize, ret.clone()?, arena); } ret } @@ -271,6 +281,7 @@ impl ExecOp for RangeExpr { fn spread( &self, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result< ( Elem, @@ -280,17 +291,17 @@ impl ExecOp for RangeExpr { ), GraphError, > { - let lhs_min = self.lhs.minimize(analyzer)?; - self.lhs.set_arenaized_cache(false, &lhs_min, analyzer); + let lhs_min = self.lhs.minimize(analyzer, arena)?; + self.lhs.set_arenaized_cache(false, &lhs_min, arena); - let lhs_max = self.lhs.maximize(analyzer)?; - self.lhs.set_arenaized_cache(true, &lhs_max, analyzer); + let lhs_max = self.lhs.maximize(analyzer, arena)?; + self.lhs.set_arenaized_cache(true, &lhs_max, arena); - let rhs_min = self.rhs.minimize(analyzer)?; - self.rhs.set_arenaized_cache(false, &rhs_min, analyzer); + let rhs_min = self.rhs.minimize(analyzer, arena)?; + self.rhs.set_arenaized_cache(false, &rhs_min, arena); - let rhs_max = self.rhs.maximize(analyzer)?; - self.rhs.set_arenaized_cache(true, &rhs_max, analyzer); + let rhs_max = self.rhs.maximize(analyzer, arena)?; + self.rhs.set_arenaized_cache(true, &rhs_max, arena); Ok((lhs_min, lhs_max, rhs_min, rhs_max)) } @@ -298,6 +309,7 @@ impl ExecOp for RangeExpr { fn simplify_spread( &self, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result< ( Elem, @@ -307,17 +319,17 @@ impl ExecOp for RangeExpr { ), GraphError, > { - let lhs_min = self.lhs.simplify_minimize(analyzer)?; - self.lhs.set_arenaized_flattened(false, &lhs_min, analyzer); + let lhs_min = self.lhs.simplify_minimize(analyzer, arena)?; + self.lhs.set_arenaized_flattened(false, &lhs_min, arena); - let lhs_max = self.lhs.simplify_maximize(analyzer)?; - self.lhs.set_arenaized_flattened(true, &lhs_max, analyzer); + let lhs_max = self.lhs.simplify_maximize(analyzer, arena)?; + self.lhs.set_arenaized_flattened(true, &lhs_max, arena); - let rhs_min = self.rhs.simplify_minimize(analyzer)?; - self.rhs.set_arenaized_flattened(false, &rhs_min, analyzer); + let rhs_min = self.rhs.simplify_minimize(analyzer, arena)?; + self.rhs.set_arenaized_flattened(false, &rhs_min, arena); - let rhs_max = self.rhs.simplify_maximize(analyzer)?; - self.rhs.set_arenaized_flattened(true, &rhs_max, analyzer); + let rhs_max = self.rhs.simplify_maximize(analyzer, arena)?; + self.rhs.set_arenaized_flattened(true, &rhs_max, arena); Ok((lhs_min, lhs_max, rhs_min, rhs_max)) } @@ -333,16 +345,17 @@ impl ExecOp for RangeExpr { ), maximize: bool, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, GraphError> { if maximize { - if let Some(MinMaxed::Maximized(v)) = self.arenaized_cache(maximize, analyzer) { + if let Some(MinMaxed::Maximized(v)) = self.arenaized_cache(maximize, analyzer, arena) { tracing::trace!("avoided execing via cache"); return Ok(*v); } } if !maximize { - if let Some(MinMaxed::Minimized(v)) = self.arenaized_cache(maximize, analyzer) { + if let Some(MinMaxed::Minimized(v)) = self.arenaized_cache(maximize, analyzer, arena) { tracing::trace!("avoided execing via cache"); return Ok(*v); } @@ -360,1449 +373,82 @@ impl ExecOp for RangeExpr { rhs_max ); - let lhs_min_neg = lhs_min.pre_evaled_is_negative(); - let lhs_max_neg = lhs_max.pre_evaled_is_negative(); - let rhs_min_neg = rhs_min.pre_evaled_is_negative(); - let rhs_max_neg = rhs_max.pre_evaled_is_negative(); - - let consts = ( - matches!( - lhs_min.range_ord(&lhs_max, analyzer), - Some(std::cmp::Ordering::Equal) + let res = match self.op { + 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), + RangeOp::SetIndices => exec_set_indices( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, &self.rhs, maximize, analyzer, arena, ), - matches!( - rhs_min.range_ord(&rhs_max, analyzer), - Some(std::cmp::Ordering::Equal) + RangeOp::Memcopy => exec_memcopy(&lhs_min, &lhs_max, maximize), + RangeOp::Concat => exec_concat( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, + ), + RangeOp::Add(unchecked) => exec_add( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, unchecked, analyzer, arena, + ), + RangeOp::Sub(unchecked) => exec_sub( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, unchecked, analyzer, arena, + ), + RangeOp::Mul(unchecked) => exec_mul( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, unchecked, analyzer, arena, + ), + RangeOp::Div(unchecked) => exec_div( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, unchecked, analyzer, arena, + ), + RangeOp::Mod => exec_mod( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, + ), + RangeOp::Exp => exec_exp( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, + ), + RangeOp::Min => exec_min( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, + ), + RangeOp::Max => exec_max( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, + ), + RangeOp::Gt => exec_gt(&lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize), + RangeOp::Lt => exec_lt(&lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize), + RangeOp::Gte => exec_gte(&lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize), + RangeOp::Lte => exec_lte(&lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize), + RangeOp::Eq => exec_eq_neq( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, true, analyzer, arena, + ), + RangeOp::Neq => exec_eq_neq( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, false, analyzer, arena, + ), + RangeOp::And => exec_and( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, + ), + RangeOp::Or => exec_or( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, + ), + RangeOp::Not => exec_not( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, + ), + RangeOp::BitAnd => exec_bit_and( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, + ), + RangeOp::BitOr => exec_bit_or( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, + ), + RangeOp::BitXor => exec_bit_xor( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, + ), + RangeOp::BitNot => exec_bit_not(&lhs_min, &lhs_max, maximize, analyzer, arena), + RangeOp::Shl => exec_shl( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, + ), + RangeOp::Shr => exec_shr( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, + ), + RangeOp::Cast => exec_cast( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, ), - ); - - fn fallback( - this: &RangeExpr, - lhs: Elem, - rhs: Elem, - consts: (bool, bool), - ) -> Elem { - match consts { - (true, true) => Elem::Expr(RangeExpr::new(lhs, this.op, rhs)), - (false, true) => Elem::Expr(RangeExpr::new(*this.lhs.clone(), this.op, rhs)), - (true, false) => Elem::Expr(RangeExpr::new(lhs, this.op, *this.rhs.clone())), - (false, false) => Elem::Expr(this.clone()), - } } - - let res = match self.op { - RangeOp::GetLength => { - if maximize { - let new = lhs_max.clone(); - let new_max = new.simplify_maximize(analyzer)?; - let res = new_max.range_get_length(); - res.unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } else { - let new_min = lhs_min.simplify_minimize(analyzer)?; - let res = new_min.range_get_length(); - res.unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } - } - RangeOp::SetLength => { - if maximize { - lhs_max - .range_set_length(&rhs_max) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } else { - lhs_min - .range_set_length(&rhs_min) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } - } - RangeOp::GetIndex => { - if maximize { - fn match_ty_max( - lhs_max: Elem, - rhs_min: Elem, - rhs_max: Elem, - analyzer: &impl GraphBackend, - ) -> Result, GraphError> { - match lhs_max { - Elem::ConcreteDyn(RangeDyn { val, .. }) => Ok(val - .iter() - .try_fold( - None, - |mut acc: Option>, (key, (val, _))| { - if matches!( - key.overlaps_dual(&rhs_min, &rhs_max, true, analyzer)?, - Some(true) - ) { - if acc.is_none() - || matches!( - acc.clone().unwrap().range_ord(val, analyzer), - Some(std::cmp::Ordering::Greater) - ) - { - acc = Some(val.clone()); - Ok(acc) - } else { - Ok(acc) - } - } else { - Ok(acc) - } - }, - )? - .unwrap_or_else(|| Elem::Null)), - Elem::Reference(_) => { - let new_max = lhs_max.simplify_maximize(analyzer)?; - if new_max == lhs_max { - Ok(Elem::Null) - } else { - match_ty_max(new_max, rhs_min, rhs_max, analyzer) - } - } - _ => Ok(Elem::Null), - } - } - match_ty_max(lhs_max.clone(), rhs_min, rhs_max.clone(), analyzer) - .unwrap_or_else(|_| fallback(self, lhs_max, rhs_max, consts)) - } else { - fn match_ty_min( - lhs_min: Elem, - rhs_min: Elem, - rhs_max: Elem, - analyzer: &impl GraphBackend, - ) -> Result, GraphError> { - match lhs_min { - Elem::ConcreteDyn(RangeDyn { val, .. }) => Ok(val - .iter() - .try_fold( - None, - |mut acc: Option>, (key, (val, _))| { - if matches!( - key.overlaps_dual(&rhs_min, &rhs_max, true, analyzer)?, - Some(true) - ) { - if acc.is_none() - || matches!( - acc.clone().unwrap().range_ord(val, analyzer), - Some(std::cmp::Ordering::Less) - ) - { - acc = Some(val.clone()); - Ok(acc) - } else { - Ok(acc) - } - } else { - Ok(acc) - } - }, - )? - .unwrap_or_else(|| Elem::Null)), - Elem::Reference(ref _r) => { - let new_min = lhs_min.simplify_minimize(analyzer)?; - if new_min == lhs_min { - Ok(Elem::Null) - } else { - match_ty_min(new_min, rhs_min, rhs_max, analyzer) - } - } - _ => Ok(Elem::Null), - } - } - match_ty_min(lhs_min.clone(), rhs_min.clone(), rhs_max, analyzer) - .unwrap_or_else(|_| fallback(self, lhs_min, rhs_min, consts)) - } - } - RangeOp::SetIndices => { - if maximize { - let max = self.rhs.simplify_maximize(analyzer)?; - - lhs_max.range_set_indices(&rhs_max).unwrap_or_else(|| { - lhs_max - .range_set_indices(&max) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - }) - } else { - let min = self.rhs.simplify_minimize(analyzer)?; - lhs_min.range_set_indices(&rhs_min).unwrap_or_else(|| { - lhs_min - .range_set_indices(&min) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - }) - } - } - RangeOp::Memcopy => { - if maximize { - lhs_max.clone() - } else { - lhs_min.clone() - } - } - RangeOp::Add(unchecked) => { - if unchecked { - let mut candidates = vec![ - lhs_min.range_wrapping_add(&rhs_min), - lhs_min.range_wrapping_add(&rhs_max), - lhs_max.range_wrapping_add(&rhs_min), - lhs_max.range_wrapping_add(&rhs_max), - ]; - - // if they arent constants, we can test a normal add - if !matches!(consts, (true, true)) { - candidates.push(lhs_max.range_add(&rhs_max)); - candidates.push(lhs_min.range_add(&rhs_min)); - } - - let mut candidates = candidates.into_iter().flatten().collect::>(); - candidates.sort_by(|a, b| match a.range_ord(b, analyzer) { - Some(r) => r, - _ => std::cmp::Ordering::Less, - }); - - if candidates.is_empty() { - return Ok(fallback(self, lhs_min, rhs_min, consts)); - } - - if maximize { - candidates[candidates.len() - 1].clone() - } else { - candidates[0].clone() - } - } else if maximize { - // if we are maximizing, the largest value will always just be the the largest value + the largest value - lhs_max - .range_add(&rhs_max) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } else { - lhs_min - .range_add(&rhs_min) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } - } - RangeOp::Sub(unchecked) => { - // quick check if rhs is const and zero, if so return min or max - if unchecked { - let mut candidates = vec![]; - // check if rhs contains zero, if so add lhs_min and max as candidates - - let one = Elem::from(Concrete::from(U256::from(1))); - let zero = Elem::from(Concrete::from(U256::from(0))); - let rhs_min_contains_zero = matches!( - rhs_min.range_ord(&zero, analyzer), - Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) - ); - - let rhs_max_contains_zero = matches!( - rhs_max.range_ord(&zero, analyzer), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) - ); - - if rhs_min_contains_zero && rhs_max_contains_zero { - candidates.push(Some(lhs_min.clone())); - candidates.push(Some(lhs_max.clone())); - } - - // If we have the below case, where the lhs - // contains the rhs, we can add zero. Futher more, if - // the lhs contains rhs - 1, we can add max as it - // would overflow to uint256.max - // zero min max uint256.max - // lhs: | - - |----------------------------| - - | - // rhs: | - - |--| - - - - - - - - - - - - - - - | - match lhs_max.range_ord(&rhs_min, analyzer) { - Some(std::cmp::Ordering::Less) => { - // We are going to overflow, zero not possible - } - Some(std::cmp::Ordering::Equal) => { - // We are going to at least be zero, - // we may overflow. check if rhs is const, otherwise - // add uint256.max as a candidate - candidates.push(Some(zero.clone())); - if !consts.1 { - candidates.push(zero.range_wrapping_sub(&one)); - } - } - Some(std::cmp::Ordering::Greater) => { - // No guarantees on overflow, check lhs_min - match lhs_min.range_ord(&rhs_min, analyzer) { - Some(std::cmp::Ordering::Less) => { - // fully contained, add zero and max - candidates.push(Some(zero.clone())); - candidates.push(zero.range_wrapping_sub(&one)); - } - Some(std::cmp::Ordering::Equal) => { - // We are going to at least be zero, - // we may overflow. check if rhs is const, otherwise - // add uint256.max as a candidate - candidates.push(Some(zero.clone())); - if !consts.1 { - candidates.push(zero.range_wrapping_sub(&one)); - } - } - Some(std::cmp::Ordering::Greater) => { - // current info: - // zero min max uint256.max - // lhs: | - - |----------------------------| - - | - // rhs: | - |----? - - - - - - - - - - - - - - - | - // figure out where rhs max is - match lhs_min.range_ord(&rhs_max, analyzer) { - Some(std::cmp::Ordering::Less) => { - // zero min - // lhs: | - - |---? - // rhs: | - |----| - // min max - // Add both - candidates.push(Some(zero.clone())); - candidates.push(zero.range_wrapping_sub(&one)); - } - Some(std::cmp::Ordering::Equal) => { - // zero min - // lhs: | - - |---? - // rhs: | |---| - // min max - // Add zero - candidates.push(Some(zero.clone())); - } - Some(std::cmp::Ordering::Greater) => { - // zero min - // lhs: | - - |---? - // rhs: |-----| - // min max - // Add nothing - } - _ => {} - } - } - _ => {} - } - } - _ => {} - } - - candidates.extend(vec![ - lhs_min.range_wrapping_sub(&rhs_min), - lhs_min.range_wrapping_sub(&rhs_max), - lhs_max.range_wrapping_sub(&rhs_min), - lhs_max.range_wrapping_sub(&rhs_max), - ]); - let mut candidates = candidates.into_iter().flatten().collect::>(); - candidates.sort_by(|a, b| match a.range_ord(b, analyzer) { - Some(r) => r, - _ => std::cmp::Ordering::Less, - }); - - if candidates.is_empty() { - return Ok(fallback(self, lhs_min, rhs_min, consts)); - } - - if maximize { - candidates[candidates.len() - 1].clone() - } else { - candidates[0].clone() - } - } else if maximize { - // if we are maximizing, the largest value will always just be the the largest value - the smallest value - lhs_max - .range_sub(&rhs_min) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } else { - // if we are minimizing, the smallest value will always be smallest value - largest value - lhs_min - .range_sub(&rhs_max) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } - } - RangeOp::Mul(unchecked) => { - if unchecked { - let mut candidates: Vec>> = vec![]; - let zero = Elem::from(Concrete::from(U256::from(0))); - let one = Elem::from(Concrete::from(U256::from(1))); - let negative_one = Elem::from(Concrete::from(I256::from(-1i32))); - - // check if rhs contains 1 - if let ( - Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal), - ) = ( - rhs_min.range_ord(&one, analyzer), - rhs_max.range_ord(&one, analyzer), - ) { - candidates.push(Some(lhs_max.clone())); - candidates.push(Some(lhs_min.clone())); - } - - // check if lhs contains 1 - if let ( - Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal), - ) = ( - lhs_min.range_ord(&one, analyzer), - lhs_max.range_ord(&one, analyzer), - ) { - candidates.push(Some(rhs_max.clone())); - candidates.push(Some(rhs_min.clone())); - } - - // check if rhs contains -1 - if let ( - Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal), - ) = ( - rhs_min.range_ord(&negative_one, analyzer), - rhs_max.range_ord(&negative_one, analyzer), - ) { - candidates.push(lhs_max.range_mul(&negative_one)); - candidates.push(lhs_min.range_mul(&negative_one)); - } - - // check if lhs contains -1 - if let ( - Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal), - ) = ( - lhs_min.range_ord(&negative_one, analyzer), - lhs_max.range_ord(&negative_one, analyzer), - ) { - candidates.push(rhs_max.range_mul(&negative_one)); - candidates.push(rhs_min.range_mul(&negative_one)); - } - - // check if rhs contains zero - if let ( - Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal), - ) = ( - rhs_min.range_ord(&zero, analyzer), - rhs_max.range_ord(&zero, analyzer), - ) { - candidates.push(Some(zero.clone())); - } - // check if lhs contains zero - if let ( - Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal), - ) = ( - lhs_min.range_ord(&zero, analyzer), - lhs_max.range_ord(&zero, analyzer), - ) { - candidates.push(Some(zero.clone())); - } - candidates.extend(vec![ - lhs_min.range_wrapping_mul(&rhs_min), - lhs_min.range_wrapping_mul(&rhs_max), - lhs_max.range_wrapping_mul(&rhs_min), - lhs_max.range_wrapping_mul(&rhs_max), - ]); - let mut candidates = candidates.into_iter().flatten().collect::>(); - - candidates.sort_by(|a, b| match a.range_ord(b, analyzer) { - Some(r) => r, - _ => std::cmp::Ordering::Less, - }); - - if candidates.is_empty() { - return Ok(fallback(self, lhs_min, rhs_min, consts)); - } - - if maximize { - candidates[candidates.len() - 1].clone() - } else { - candidates[0].clone() - } - } else if maximize { - // if we are maximizing, and both mins are negative and both maxes are positive, - // we dont know which will be larger of the two (i.e. -1*2**255 * -1*2**255 > 100*100) - match (lhs_min_neg, lhs_max_neg, rhs_min_neg, rhs_max_neg) { - (true, true, true, true) => { - // all negative, will be min * min because those are furthest from 0 resulting in the - // largest positive value - lhs_min - .range_mul(&rhs_min) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } - (true, false, true, false) => { - // we dont know if lhs_max * rhs_min is larger or lhs_min * rhs_max is smaller - match (lhs_min.range_mul(&rhs_min), lhs_max.range_mul(&rhs_max)) { - (Some(min_expr), Some(max_expr)) => { - match min_expr.range_ord(&max_expr, analyzer) { - Some(std::cmp::Ordering::Less) => max_expr, - Some(std::cmp::Ordering::Greater) => min_expr, - _ => max_expr, - } - } - (None, Some(max_expr)) => max_expr, - (Some(min_expr), None) => min_expr, - (None, None) => fallback(self, lhs_min, rhs_min, consts), - } - } - (_, false, _, false) => { - // rhs_max is positive, lhs_max is positive, guaranteed to be largest max value - lhs_max - .range_mul(&rhs_max) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } - (false, false, true, true) => { - // since we are forced to go negative here, values closest to 0 will ensure we get the maximum - lhs_min - .range_mul(&rhs_max) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } - (true, true, false, false) => { - // since we are forced to go negative here, values closest to 0 will ensure we get the maximum - lhs_max - .range_mul(&rhs_min) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } - (true, _, true, _) => lhs_min - .range_mul(&rhs_min) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)), - (false, true, _, _) | (_, _, false, true) => { - fallback(self, lhs_min, rhs_min, consts) - } - } - } else { - match (lhs_min_neg, lhs_max_neg, rhs_min_neg, rhs_max_neg) { - (false, false, false, false) => { - // rhs_min is positive, lhs_min is positive, guaranteed to be smallest max value - lhs_min - .range_mul(&rhs_min) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } - (true, true, true, true) => { - // all negative, will be max * max because those are closest to 0 resulting in the - // smallest positive value - lhs_max - .range_mul(&rhs_max) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } - (true, false, true, false) => { - // we dont know if lhs_max * rhs_min is smaller or lhs_min * rhs_max is smaller - match (lhs_max.range_mul(&rhs_min), lhs_min.range_mul(&rhs_max)) { - (Some(min_expr), Some(max_expr)) => { - match min_expr.range_ord(&max_expr, analyzer) { - Some(std::cmp::Ordering::Less) => min_expr, - Some(std::cmp::Ordering::Greater) => max_expr, - _ => min_expr, - } - } - (None, Some(max_expr)) => max_expr, - (Some(min_expr), None) => min_expr, - (None, None) => fallback(self, lhs_min, rhs_min, consts), - } - } - (true, _, _, false) => { - // rhs_max is positive, lhs_min is negative, guaranteed to be largest min value - lhs_min - .range_mul(&rhs_max) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } - (_, false, _, true) => { - // just lhs has a positive value, most negative will be lhs_max, rhs_max - lhs_max - .range_mul(&rhs_max) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } - (false, false, true, false) => lhs_max - .range_mul(&rhs_min) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)), - (false, true, _, _) | (_, _, false, true) => { - fallback(self, lhs_min, rhs_min, consts) - } - } - } - } - RangeOp::Div(_unchecked) => { - let mut candidates = vec![ - lhs_min.range_div(&rhs_min), - lhs_min.range_div(&rhs_max), - lhs_max.range_div(&rhs_min), - lhs_max.range_div(&rhs_max), - ]; - - let one = Elem::from(Concrete::from(U256::from(1))); - let negative_one = Elem::from(Concrete::from(I256::from(-1i32))); - - let min_contains = matches!( - rhs_min.range_ord(&one, analyzer), - Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) - ); - - let max_contains = matches!( - rhs_max.range_ord(&one, analyzer), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) - ); - - if min_contains && max_contains { - candidates.push(lhs_min.range_div(&one)); - candidates.push(lhs_max.range_div(&one)); - } - - let min_contains = matches!( - rhs_min.range_ord(&negative_one, analyzer), - Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) - ); - - let max_contains = matches!( - rhs_max.range_ord(&negative_one, analyzer), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) - ); - - if min_contains && max_contains { - candidates.push(lhs_min.range_div(&negative_one)); - candidates.push(lhs_max.range_div(&negative_one)); - } - - let mut candidates = candidates.into_iter().flatten().collect::>(); - candidates.sort_by(|a, b| match a.range_ord(b, analyzer) { - Some(r) => r, - _ => std::cmp::Ordering::Less, - }); - - if candidates.is_empty() { - return Ok(fallback(self, lhs_min, rhs_min, consts)); - } - - if maximize { - candidates[candidates.len() - 1].clone() - } else { - candidates[0].clone() - } - // if maximize { - // match (lhs_min_neg, lhs_max_neg, rhs_min_neg, rhs_max_neg) { - // (true, false, true, false) => { - // // we dont know if lhs_min / rhs_min is larger or lhs_max / rhs_max is larger - // match (lhs_min.range_div(&rhs_min), lhs_max.range_div(&rhs_max)) { - // (Some(min_expr), Some(max_expr)) => { - // match min_expr.range_ord(&max_expr) { - // Some(std::cmp::Ordering::Less) => { - // max_expr - // } - // Some(std::cmp::Ordering::Greater) => { - // min_expr - // } - // _ => { - // max_expr - // } - // } - // } - // (None, Some(max_expr)) => { - // max_expr - // } - // (Some(min_expr), None) => { - // min_expr - // } - // (None, None) => Elem::Expr(self.clone()) - // } - // } - // (false, false, true, true) => { - // // since we are forced to go negative here, values closest to 0 will ensure we get the maximum - // lhs_min.range_div(&rhs_max).unwrap_or(Elem::Expr(self.clone())) - // } - // (true, true, false, false) => { - // // since we are forced to go negative here, values closest to 0 will ensure we get the maximum - // lhs_max.range_div(&rhs_min).unwrap_or(Elem::Expr(self.clone())) - // } - // (_, false, false, _) => { - // // lhs is positive, rhs min is positive, guaranteed to give largest - // lhs_max.range_div(&rhs_min).unwrap_or(Elem::Expr(self.clone())) - // } - // (_, false, true, false) => { - // // lhs_max is positive and rhs_max is positive, guaranteed to be lhs_max and rhs_max - // lhs_max.range_div(&rhs_max).unwrap_or(Elem::Expr(self.clone())) - // } - // (true, _, true, _) => { - // // at this point, its either all trues, or a single false - // // given that, to maximize, the only way to get a positive value is to use the most negative values - // lhs_min.range_div(&rhs_min).unwrap_or(Elem::Expr(self.clone())) - // } - // (false, true, _, _) | (_, _, false, true)=> { - // panic!("unsatisfiable range") - // } - // } - // } else { - // match (lhs_min_neg, lhs_max_neg, rhs_min_neg, rhs_max_neg) { - // (false, false, false, false) => { - // // smallest number will be lhs_min / rhs_min since both are positive - // lhs_min.range_div(&rhs_max).unwrap_or(Elem::Expr(self.clone())) - // } - // (true, true, true, true) => { - // // smallest number will be lhs_max / rhs_min since both are negative - // lhs_max.range_div(&rhs_max).unwrap_or(Elem::Expr(self.clone())) - // } - // (true, true, true, false) => { - // // The way to maintain most negative value is lhs_min / rhs_max, all others would go - // // positive or guaranteed to be closer to 0 - // lhs_min.range_div(&rhs_max).unwrap_or(Elem::Expr(self.clone())) - // } - // (true, false, true, false) => { - // // we dont know if lhs_min / rhs_max is larger or lhs_max / rhs_min is larger - // match (lhs_min.range_div(&rhs_max), lhs_max.range_div(&rhs_min)) { - // (Some(min_expr), Some(max_expr)) => { - // match min_expr.range_ord(&max_expr) { - // Some(std::cmp::Ordering::Less) => { - // min_expr - // } - // Some(std::cmp::Ordering::Greater) => { - // max_expr - // } - // _ => { - // min_expr - // } - // } - // } - // (None, Some(max_expr)) => { - // max_expr - // } - // (Some(min_expr), None) => { - // min_expr - // } - // (None, None) => Elem::Expr(self.clone()) - // } - // } - // (_, false, true, _) => { - // // We are going negative here, so it will be most positive / least negative - // lhs_max.range_div(&rhs_min).unwrap_or(Elem::Expr(self.clone())) - // } - // (true, _, false, _) => { - // // We are going negative here, so it will be most negative / least positive - // lhs_min.range_div(&rhs_min).unwrap_or(Elem::Expr(self.clone())) - // } - // (false, true, _, _) | (_, _, false, true)=> { - // panic!("unsatisfiable range") - // } - // } - // } - } - // RangeOp::Mod => { - // lhs.range_mod(&rhs).unwrap_or(Elem::Expr(self.clone())) - // } - RangeOp::Min => { - let candidates = vec![ - lhs_min.range_min(&rhs_min), - lhs_min.range_min(&rhs_max), - lhs_max.range_min(&rhs_min), - lhs_max.range_min(&rhs_max), - ]; - let mut candidates = candidates.into_iter().flatten().collect::>(); - candidates.sort_by(|a, b| match a.range_ord(b, analyzer) { - Some(r) => r, - _ => std::cmp::Ordering::Less, - }); - - if candidates.is_empty() { - return Ok(fallback(self, lhs_min, rhs_min, consts)); - } - - if maximize { - candidates[candidates.len() - 1].clone() - } else { - candidates[0].clone() - } - // if maximize { - // match (lhs_min_neg, lhs_max_neg, rhs_min_neg, rhs_max_neg) { - // (true, _, true, _) | (false, _, false, _) => { - // // counter-intuitively, we want the maximum value from a call to minimum - // // this is due to the symbolic nature of the evaluation. We are still - // // using the minimum values but getting the larger of the minimum - // lhs_min.range_max(&rhs_min).unwrap_or(Elem::Expr(self.clone())) - // } - // (true, _, false, false) => { - // rhs_min //.range_min(&rhs_min).unwrap_or(Elem::Expr(self.clone())) - // } - // (false, false, true, _) => { - // lhs_min //lhs_min.range_min(&rhs_min).unwrap_or(Elem::Expr(self.clone())) - // } - // (false, true, _, _) | (_, _, false, true)=> { - // panic!("unsatisfiable range") - // } - // } - // } else { - // match (lhs_min_neg, lhs_max_neg, rhs_min_neg, rhs_max_neg) { - // (true, _, true, _) | (false, _, false, _) => { - // lhs_min.range_min(&rhs_min).unwrap_or(Elem::Expr(self.clone())) - // } - // (true, _, false, false) => { - // lhs_min //.range_min(&rhs_min).unwrap_or(Elem::Expr(self.clone())) - // } - // (false, false, true, _) => { - // rhs_min //lhs_min.range_min(&rhs_min).unwrap_or(Elem::Expr(self.clone())) - // } - // (false, true, _, _) | (_, _, false, true)=> { - // panic!("unsatisfiable range") - // } - // } - // } - } - RangeOp::Max => { - let candidates = vec![ - lhs_min.range_max(&rhs_min), - lhs_min.range_max(&rhs_max), - lhs_max.range_max(&rhs_min), - lhs_max.range_max(&rhs_max), - ]; - let mut candidates = candidates.into_iter().flatten().collect::>(); - candidates.sort_by(|a, b| match a.range_ord(b, analyzer) { - Some(r) => r, - _ => std::cmp::Ordering::Less, - }); - - if candidates.is_empty() { - return Ok(fallback(self, lhs_min, rhs_min, consts)); - } - - if maximize { - candidates[candidates.len() - 1].clone() - } else { - candidates[0].clone() - } - // if maximize { - // match (lhs_min_neg, lhs_max_neg, rhs_min_neg, rhs_max_neg) { - // (true, _, true, _) | (false, _, false, _) => { - // lhs_max.range_max(&rhs_max).unwrap_or(Elem::Expr(self.clone())) - // } - // (true, _, false, false) => { - // rhs_max //.range_min(&rhs_min).unwrap_or(Elem::Expr(self.clone())) - // } - // (false, false, true, _) => { - // lhs_max //lhs_min.range_min(&rhs_min).unwrap_or(Elem::Expr(self.clone())) - // } - // (false, true, _, _) | (_, _, false, true)=> { - // panic!("unsatisfiable range") - // } - // } - // } else { - // match (lhs_min_neg, lhs_max_neg, rhs_min_neg, rhs_max_neg) { - // (_, true, _, true) | (_, false, _, false) => { - // // counter-intuitively, we want the minimum value from a call to maximum - // // this is due to the symbolic nature of the evaluation. We are still - // // using the maximum values but getting the smaller of the maximum - // lhs_max.range_min(&rhs_max).unwrap_or(Elem::Expr(self.clone())) - // } - // (_, false, true, true) => { - // lhs_max //.range_min(&rhs_min).unwrap_or(Elem::Expr(self.clone())) - // } - // (true, true, _, false) => { - // rhs_max //lhs_min.range_min(&rhs_min).unwrap_or(Elem::Expr(self.clone())) - // } - // (false, true, _, _) | (_, _, false, true)=> { - // panic!("unsatisfiable range") - // } - // } - // } - } - RangeOp::Gt => { - if maximize { - lhs_max - .range_gt(&rhs_min) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } else { - lhs_min - .range_gt(&rhs_max) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } - } - RangeOp::Lt => { - if maximize { - lhs_min - .range_lt(&rhs_max) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } else { - lhs_max - .range_lt(&rhs_min) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } - } - RangeOp::Gte => { - if maximize { - lhs_max - .range_gte(&rhs_min) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } else { - lhs_min - .range_gte(&rhs_max) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } - } - RangeOp::Lte => { - if maximize { - lhs_min - .range_lte(&rhs_max) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } else { - lhs_max - .range_lte(&rhs_min) - .unwrap_or_else(|| fallback(self, lhs_min, rhs_min, consts)) - } - } - RangeOp::Eq => { - // prevent trying to eval when we have dependents - if !lhs_min.dependent_on(analyzer).is_empty() - || !lhs_max.dependent_on(analyzer).is_empty() - || !rhs_min.dependent_on(analyzer).is_empty() - || !rhs_max.dependent_on(analyzer).is_empty() - { - return Ok(fallback(self, lhs_min, rhs_min, consts)); - } - - let loc = if let Some(c) = lhs_min.maybe_concrete() { - c.loc - } else if let Some(c) = lhs_max.maybe_concrete() { - c.loc - } else if let Some(c) = rhs_min.maybe_concrete() { - c.loc - } else if let Some(c) = rhs_max.maybe_concrete() { - c.loc - } else { - Loc::Implicit - }; - - if maximize { - // check for any overlap - let lhs_max_rhs_min_ord = lhs_max.range_ord(&rhs_min, analyzer); - let lhs_min_rhs_max_ord = lhs_min.range_ord(&rhs_max, analyzer); - - // if lhs max is less than the rhs min, it has to be false - if matches!(lhs_max_rhs_min_ord, Some(std::cmp::Ordering::Less)) { - return Ok(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(false), - loc, - })); - } - - // if lhs min is greater than the rhs max, it has to be false - if matches!(lhs_min_rhs_max_ord, Some(std::cmp::Ordering::Greater)) { - return Ok(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(false), - loc, - })); - } - - // lhs_max >= rhs_min - // lhs_min <= rhs_max - // therefore its possible to set some value to true here - if lhs_max_rhs_min_ord.is_some() && lhs_min_rhs_max_ord.is_some() { - Elem::Concrete(RangeConcrete { - val: Concrete::Bool(true), - loc, - }) - } else { - fallback(self, lhs_min, rhs_min, consts) - } - } else { - // check if either lhs element is *not* contained by rhs - match ( - // check if lhs is constant - lhs_min.range_ord(&lhs_max, analyzer), - // check if rhs is constant - rhs_min.range_ord(&rhs_max, analyzer), - // check if lhs is equal to rhs - lhs_min.range_ord(&rhs_min, analyzer), - ) { - ( - Some(std::cmp::Ordering::Equal), - Some(std::cmp::Ordering::Equal), - Some(std::cmp::Ordering::Equal), - ) => Elem::Concrete(RangeConcrete { - val: Concrete::Bool(true), - loc, - }), - // if any of those are not equal, we can construct - // an element that is true - _ => Elem::Concrete(RangeConcrete { - val: Concrete::Bool(false), - loc, - }), - } - } - } - RangeOp::Neq => { - // prevent trying to eval when we have dependents - if !lhs_min.dependent_on(analyzer).is_empty() - || !lhs_max.dependent_on(analyzer).is_empty() - || !rhs_min.dependent_on(analyzer).is_empty() - || !rhs_max.dependent_on(analyzer).is_empty() - { - return Ok(fallback(self, lhs_min, rhs_min, consts)); - } - let loc = if let Some(c) = lhs_min.maybe_concrete() { - c.loc - } else if let Some(c) = lhs_max.maybe_concrete() { - c.loc - } else if let Some(c) = rhs_min.maybe_concrete() { - c.loc - } else if let Some(c) = rhs_max.maybe_concrete() { - c.loc - } else { - Loc::Implicit - }; - - if maximize { - // the only case here in which we can't assert that - // true is the maximum is when they are both consts and equal - if matches!(consts, (true, true)) { - // both are consts, check if they are equal - if matches!( - lhs_min.range_ord(&rhs_min, analyzer), - Some(std::cmp::Ordering::Equal) - ) { - return Ok(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(false), - loc, - })); - } - } - - Elem::Concrete(RangeConcrete { - val: Concrete::Bool(true), - loc, - }) - } else { - // we *want* to produce false - if matches!(consts, (true, true)) { - // both are consts, check if we are forced to return true - if matches!( - lhs_min.range_ord(&rhs_min, analyzer), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Less) - ) { - return Ok(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(true), - loc, - })); - } - } - - // check for any overlap - let lhs_max_rhs_min_ord = lhs_max.range_ord(&rhs_min, analyzer); - let lhs_min_rhs_max_ord = lhs_min.range_ord(&rhs_max, analyzer); - - // if lhs max is less than the rhs min, it has to be != (true) - if matches!(lhs_max_rhs_min_ord, Some(std::cmp::Ordering::Less)) { - return Ok(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(true), - loc, - })); - } - - // if lhs min is greater than the rhs max, it has to be != (true) - if matches!(lhs_min_rhs_max_ord, Some(std::cmp::Ordering::Greater)) { - return Ok(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(true), - loc, - })); - } - - // we can force an equal value if needed - Elem::Concrete(RangeConcrete { - val: Concrete::Bool(false), - loc, - }) - // fallback(self, lhs_min, rhs_min, consts) - } - } - RangeOp::Shl => { - let candidates = vec![ - lhs_min.range_shl(&rhs_min), - lhs_min.range_shl(&rhs_max), - lhs_max.range_shl(&rhs_min), - lhs_max.range_shl(&rhs_max), - ]; - let mut candidates = candidates.into_iter().flatten().collect::>(); - candidates.sort_by(|a, b| match a.range_ord(b, analyzer) { - Some(r) => r, - _ => std::cmp::Ordering::Less, - }); - - if candidates.is_empty() { - return Ok(fallback(self, lhs_min, rhs_min, consts)); - } - - if maximize { - candidates[candidates.len() - 1].clone() - } else { - candidates[0].clone() - } - } - RangeOp::Shr => { - let candidates = vec![ - lhs_min.range_shr(&rhs_min), - lhs_min.range_shr(&rhs_max), - lhs_max.range_shr(&rhs_min), - lhs_max.range_shr(&rhs_max), - ]; - let mut candidates = candidates.into_iter().flatten().collect::>(); - candidates.sort_by(|a, b| match a.range_ord(b, analyzer) { - Some(r) => r, - _ => std::cmp::Ordering::Less, - }); - - if candidates.is_empty() { - return Ok(fallback(self, lhs_min, rhs_min, consts)); - } - - if maximize { - candidates[candidates.len() - 1].clone() - } else { - candidates[0].clone() - } - } - RangeOp::And => { - let candidates = vec![ - lhs_min.range_and(&rhs_min), - lhs_min.range_and(&rhs_max), - lhs_max.range_and(&rhs_min), - lhs_max.range_and(&rhs_max), - ]; - let mut candidates = candidates.into_iter().flatten().collect::>(); - candidates.sort_by(|a, b| match a.range_ord(b, analyzer) { - Some(r) => r, - _ => std::cmp::Ordering::Less, - }); - - if candidates.is_empty() { - return Ok(fallback(self, lhs_min, rhs_min, consts)); - } - - if maximize { - candidates[candidates.len() - 1].clone() - } else { - candidates[0].clone() - } - } - RangeOp::Or => { - let candidates = vec![ - lhs_min.range_or(&rhs_min), - lhs_min.range_or(&rhs_max), - lhs_max.range_or(&rhs_min), - lhs_max.range_or(&rhs_max), - ]; - let mut candidates = candidates.into_iter().flatten().collect::>(); - candidates.sort_by(|a, b| match a.range_ord(b, analyzer) { - Some(r) => r, - _ => std::cmp::Ordering::Less, - }); - - if candidates.is_empty() { - return Ok(fallback(self, lhs_min, rhs_min, consts)); - } - - if maximize { - candidates[candidates.len() - 1].clone() - } else { - candidates[0].clone() - } - } - RangeOp::Not => { - assert!(matches!(rhs_min, Elem::Null) && matches!(rhs_max, Elem::Null)); - let candidates = vec![lhs_min.range_not(), lhs_min.range_not()]; - let mut candidates = candidates.into_iter().flatten().collect::>(); - candidates.sort_by(|a, b| match a.range_ord(b, analyzer) { - Some(r) => r, - _ => std::cmp::Ordering::Less, - }); - - if candidates.is_empty() { - return Ok(fallback(self, lhs_min, rhs_min, consts)); - } - - if maximize { - candidates[candidates.len() - 1].clone() - } else { - candidates[0].clone() - } - } - RangeOp::Cast => { - // the weird thing about cast is that we really dont know until after the cast due to sizing things - // so we should just try them all then compare - let candidates = vec![ - lhs_min.range_cast(&rhs_min), - lhs_min.range_cast(&rhs_max), - lhs_max.range_cast(&rhs_min), - lhs_max.range_cast(&rhs_max), - ]; - let mut candidates = candidates.into_iter().flatten().collect::>(); - candidates.sort_by(|a, b| match a.range_ord(b, analyzer) { - Some(r) => r, - _ => std::cmp::Ordering::Less, - }); - - if candidates.is_empty() { - return Ok(fallback(self, lhs_min, rhs_min, consts)); - } - - if maximize { - candidates[candidates.len() - 1].clone() - } else { - candidates[0].clone() - } - } - RangeOp::Exp => { - // TODO: improve with smarter stuff - let candidates = vec![ - lhs_min.range_exp(&rhs_min), - lhs_min.range_exp(&rhs_max), - lhs_max.range_exp(&rhs_min), - lhs_max.range_exp(&rhs_max), - ]; - let mut candidates = candidates.into_iter().flatten().collect::>(); - candidates.sort_by(|a, b| match a.range_ord(b, analyzer) { - Some(r) => r, - _ => std::cmp::Ordering::Less, - }); - - if candidates.is_empty() { - return Ok(fallback(self, lhs_min, rhs_min, consts)); - } - - if maximize { - candidates[candidates.len() - 1].clone() - } else { - candidates[0].clone() - } - } - RangeOp::BitAnd => { - let mut candidates = vec![ - lhs_min.range_bit_and(&rhs_min), - lhs_min.range_bit_and(&rhs_max), - lhs_max.range_bit_and(&rhs_min), - lhs_max.range_bit_and(&rhs_max), - ]; - - let zero = Elem::from(Concrete::from(U256::from(0))); - let negative_one = Elem::from(Concrete::from(I256::from(-1i32))); - - let min_contains = matches!( - rhs_min.range_ord(&zero, analyzer), - Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) - ); - - let max_contains = matches!( - rhs_max.range_ord(&zero, analyzer), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) - ); - - if min_contains && max_contains { - candidates.push(lhs_min.range_bit_and(&zero)); - candidates.push(lhs_max.range_bit_and(&zero)); - } - - let min_contains = matches!( - rhs_min.range_ord(&negative_one, analyzer), - Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) - ); - - let max_contains = matches!( - rhs_max.range_ord(&negative_one, analyzer), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) - ); - - if min_contains && max_contains { - candidates.push(lhs_min.range_bit_and(&negative_one)); - candidates.push(lhs_max.range_bit_and(&negative_one)); - } - - let mut candidates = candidates.into_iter().flatten().collect::>(); - candidates.sort_by(|a, b| match a.range_ord(b, analyzer) { - Some(r) => r, - _ => std::cmp::Ordering::Less, - }); - - if candidates.is_empty() { - return Ok(fallback(self, lhs_min, rhs_min, consts)); - } - - if maximize { - candidates[candidates.len() - 1].clone() - } else { - candidates[0].clone() - } - } - RangeOp::BitOr => { - let mut candidates = vec![ - lhs_min.range_bit_or(&rhs_min), - lhs_min.range_bit_or(&rhs_max), - lhs_max.range_bit_or(&rhs_min), - lhs_max.range_bit_or(&rhs_max), - ]; - - let zero = Elem::from(Concrete::from(U256::from(0))); - let negative_one = Elem::from(Concrete::from(I256::from(-1i32))); - - let min_contains = matches!( - rhs_min.range_ord(&zero, analyzer), - Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) - ); - - let max_contains = matches!( - rhs_max.range_ord(&zero, analyzer), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) - ); - - if min_contains && max_contains { - candidates.push(lhs_min.range_bit_or(&zero)); - candidates.push(lhs_max.range_bit_or(&zero)); - } - - let min_contains = matches!( - rhs_min.range_ord(&negative_one, analyzer), - Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) - ); - - let max_contains = matches!( - rhs_max.range_ord(&negative_one, analyzer), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) - ); - - if min_contains && max_contains { - candidates.push(lhs_min.range_bit_or(&negative_one)); - candidates.push(lhs_max.range_bit_or(&negative_one)); - } - - let mut candidates = candidates.into_iter().flatten().collect::>(); - candidates.sort_by(|a, b| match a.range_ord(b, analyzer) { - Some(r) => r, - _ => std::cmp::Ordering::Less, - }); - - if candidates.is_empty() { - return Ok(fallback(self, lhs_min, rhs_min, consts)); - } - - if maximize { - candidates[candidates.len() - 1].clone() - } else { - candidates[0].clone() - } - } - RangeOp::BitXor => { - let mut candidates = vec![ - lhs_min.range_bit_xor(&rhs_min), - lhs_min.range_bit_xor(&rhs_max), - lhs_max.range_bit_xor(&rhs_min), - lhs_max.range_bit_xor(&rhs_max), - ]; - - let zero = Elem::from(Concrete::from(U256::from(0))); - let negative_one = Elem::from(Concrete::from(I256::from(-1i32))); - - let min_contains = matches!( - rhs_min.range_ord(&zero, analyzer), - Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) - ); - - let max_contains = matches!( - rhs_max.range_ord(&zero, analyzer), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) - ); - - if min_contains && max_contains { - // if the rhs contains zero, in xor, thats just itself - candidates.push(lhs_max.range_bit_xor(&zero)); - } - - let min_contains = matches!( - rhs_min.range_ord(&negative_one, analyzer), - Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) - ); - - let max_contains = matches!( - rhs_max.range_ord(&negative_one, analyzer), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) - ); - - if min_contains && max_contains { - candidates.push(lhs_min.range_bit_xor(&negative_one)); - candidates.push(lhs_max.range_bit_xor(&negative_one)); - } - - let mut candidates = candidates.into_iter().flatten().collect::>(); - candidates.sort_by(|a, b| match a.range_ord(b, analyzer) { - Some(r) => r, - _ => std::cmp::Ordering::Less, - }); - - if candidates.is_empty() { - return Ok(fallback(self, lhs_min, rhs_min, consts)); - } - - if maximize { - candidates[candidates.len() - 1].clone() - } else { - candidates[0].clone() - } - } - RangeOp::BitNot => { - let mut candidates = vec![lhs_min.range_bit_not(), lhs_max.range_bit_not()]; - - let zero = Elem::from(Concrete::from(U256::from(0))); - - let min_contains = matches!( - lhs_min.range_ord(&zero, analyzer), - Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) - ); - - let max_contains = matches!( - lhs_max.range_ord(&zero, analyzer), - Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) - ); - - if min_contains && max_contains { - match lhs_min { - Elem::Concrete( - ref r @ RangeConcrete { - val: Concrete::Uint(..), - .. - }, - ) => candidates.push(Some(Elem::from(Concrete::max(&r.val).unwrap()))), - Elem::Concrete( - ref r @ RangeConcrete { - val: Concrete::Int(..), - .. - }, - ) => candidates.push(Some(Elem::from(Concrete::min(&r.val).unwrap()))), - _ => {} - } - } - - let mut candidates = candidates.into_iter().flatten().collect::>(); - candidates.sort_by(|a, b| match a.range_ord(b, analyzer) { - Some(r) => r, - _ => std::cmp::Ordering::Less, - }); - - if candidates.is_empty() { - return Ok(fallback(self, lhs_min, rhs_min, consts)); - } - - if maximize { - candidates[candidates.len() - 1].clone() - } else { - candidates[0].clone() - } - } - RangeOp::Concat => { - // TODO: improve with smarter stuff - let candidates = vec![ - lhs_min.range_concat(&rhs_min), - lhs_min.range_concat(&rhs_max), - lhs_max.range_concat(&rhs_min), - lhs_max.range_concat(&rhs_max), - ]; - let mut candidates = candidates.into_iter().flatten().collect::>(); - candidates.sort_by(|a, b| match a.range_ord(b, analyzer) { - Some(r) => r, - _ => std::cmp::Ordering::Less, - }); - - if candidates.is_empty() { - return Ok(fallback(self, lhs_min, rhs_min, consts)); - } - - if maximize { - candidates[candidates.len() - 1].clone() - } else { - candidates[0].clone() - } - } - _ => fallback(self, lhs_min, rhs_min, consts), - }; + .unwrap_or_else(|| Elem::Expr(self.clone())); + tracing::trace!("result: {res}"); Ok(res) } } diff --git a/crates/graph/src/range/exec/exp.rs b/crates/graph/src/range/exec/exp.rs deleted file mode 100644 index 97462da5..00000000 --- a/crates/graph/src/range/exec/exp.rs +++ /dev/null @@ -1,85 +0,0 @@ -use crate::nodes::Concrete; -use crate::range::{elem::*, exec_traits::*}; -use ethers_core::types::U256; - -impl RangeExp for RangeConcrete { - fn range_exp(&self, other: &Self) -> Option> { - match (self.val.into_u256(), other.val.into_u256()) { - (Some(lhs_val), Some(rhs_val)) => { - let max = Concrete::max(&self.val).unwrap(); - if let Some(num) = lhs_val.checked_pow(rhs_val) { - Some(Elem::Concrete(RangeConcrete { - val: self.val.u256_as_original(num.min(max.into_u256().unwrap())), - loc: self.loc, - })) - } else { - Some(Elem::Concrete(RangeConcrete { - val: self.val.u256_as_original(max.into_u256().unwrap()), - loc: self.loc, - })) - } - } - _ => match (&self.val, &other.val) { - (Concrete::Int(lhs_size, neg_v), Concrete::Uint(_, val)) => { - let pow2 = val % U256::from(2) == 0.into(); - if val > &U256::from(u32::MAX) { - if pow2 { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::max(&self.val).unwrap(), - loc: self.loc, - })) - } else { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::min(&self.val).unwrap(), - loc: self.loc, - })) - } - } else { - let min = Concrete::min(&self.val).unwrap().int_val().unwrap(); - let max = Concrete::max(&self.val).unwrap().int_val().unwrap(); - - if let Some(num) = neg_v.checked_pow(val.as_u32()) { - if pow2 { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, num.min(max)), - loc: self.loc, - })) - } else { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, num.max(min)), - loc: self.loc, - })) - } - } else if pow2 { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::max(&self.val).unwrap(), - loc: self.loc, - })) - } else { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::min(&self.val).unwrap(), - loc: self.loc, - })) - } - } - } - _ => None, - }, - } - } -} - -impl RangeExp for Elem { - fn range_exp(&self, other: &Self) -> Option> { - match (self, other) { - (Elem::Concrete(a), Elem::Concrete(b)) => a.range_exp(b), - (Elem::Concrete(a), _) if a.val.into_u256() == Some(U256::zero()) => { - Some(Elem::from(Concrete::from(U256::from(1)))) - } - (_, Elem::Concrete(b)) if b.val.into_u256() == Some(U256::zero()) => { - Some(other.clone()) - } - _ => None, - } - } -} diff --git a/crates/graph/src/range/exec/logical.rs b/crates/graph/src/range/exec/logical.rs deleted file mode 100644 index 5d3c6129..00000000 --- a/crates/graph/src/range/exec/logical.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::nodes::Concrete; -use crate::range::{elem::*, exec_traits::*}; - -impl RangeUnary for RangeConcrete { - fn range_not(&self) -> Option> { - match self.val { - Concrete::Bool(b) => Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(!b), - loc: self.loc, - })), - _ => None, - } - } - - fn range_and(&self, other: &Self) -> Option> { - match (&self.val, &other.val) { - (Concrete::Bool(a), Concrete::Bool(b)) => Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(*a && *b), - loc: self.loc, - })), - _ => None, - } - } - - fn range_or(&self, other: &Self) -> Option> { - match (&self.val, &other.val) { - (Concrete::Bool(a), Concrete::Bool(b)) => Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(*a || *b), - loc: self.loc, - })), - _ => None, - } - } -} - -impl RangeUnary for Elem { - fn range_not(&self) -> Option> { - match self { - Elem::Concrete(a) => a.range_not(), - _ => None, - } - } - fn range_and(&self, other: &Self) -> Option> { - match (self, other) { - (Elem::Concrete(a), Elem::Concrete(b)) => a.range_and(b), - _ => None, - } - } - fn range_or(&self, other: &Self) -> Option> { - match (self, other) { - (Elem::Concrete(a), Elem::Concrete(b)) => a.range_or(b), - _ => None, - } - } -} diff --git a/crates/graph/src/range/exec/math_ops/add.rs b/crates/graph/src/range/exec/math_ops/add.rs new file mode 100644 index 00000000..980ead1f --- /dev/null +++ b/crates/graph/src/range/exec/math_ops/add.rs @@ -0,0 +1,569 @@ +use crate::nodes::Concrete; +use crate::range::{elem::*, exec_traits::*}; +use crate::GraphBackend; + +use shared::RangeArena; + +use ethers_core::types::{I256, U256}; +use solang_parser::pt::Loc; + +impl RangeAdd for RangeConcrete { + fn range_add(&self, other: &Self) -> Option> { + match (self.val.into_u256(), other.val.into_u256()) { + (Some(lhs_val), Some(rhs_val)) => { + // `max_of_type` cannot fail on uint + let max_uint = Concrete::max_of_type(&self.val) + .unwrap() + .into_u256() + .unwrap(); + // min { a + b, max } to cap at maximum of lhs sizing + let op_res = lhs_val.saturating_add(rhs_val).min(max_uint); + let val = self.val.u256_as_original(op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + _ => { + match (&self.val, &other.val) { + (Concrete::Uint(lhs_size, val), Concrete::Int(_, neg_v)) + | (Concrete::Int(lhs_size, neg_v), Concrete::Uint(_, val)) => { + // neg_v guaranteed to be negative here + let abs = neg_v.unsigned_abs(); + if abs > *val { + // |b| - a + let op_res = + I256::from_raw(abs.saturating_sub(*val)) * I256::from(-1i32); + let val = Concrete::Int(*lhs_size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } else { + // a - |b| + let op_res = val.saturating_sub(abs); + let val = self.val.u256_as_original(op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + } + (Concrete::Int(lhs_size, l), Concrete::Int(_rhs_size, r)) => { + // `min_of_type` cannot fail on int + let min = Concrete::min_of_type(&self.val).unwrap().int_val().unwrap(); + // lhs + rhs when both are negative is effectively lhs - rhs which means + // we saturate at the minimum value of the left hand side. + // therefore, max{ l + r, min } is the result + let op_res = l.saturating_add(*r).max(min); + let val = Concrete::Int(*lhs_size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + _ => None, + } + } + } + } + + fn range_wrapping_add(&self, other: &Self) -> Option> { + match (self.val.into_u256(), other.val.into_u256()) { + (Some(lhs_val), Some(rhs_val)) => { + let op_res = lhs_val.overflowing_add(rhs_val).0; + let val = self.val.u256_as_original(op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + _ => match (&self.val, &other.val) { + (Concrete::Uint(..), Concrete::Int(..)) + | (Concrete::Int(..), Concrete::Uint(..)) => { + // just fall back to normal implementation because + // a positive and negative cannot overflow in addition + self.range_add(other) + } + (Concrete::Int(lhs_size, l), Concrete::Int(_rhs_size, r)) => { + let op_res = l.overflowing_add(*r).0; + let val = Concrete::Int(*lhs_size, op_res).size_wrap(); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + _ => None, + }, + } + } +} + +impl RangeAdd for Elem { + fn range_add(&self, other: &Self) -> Option> { + match (self, other) { + (Elem::Concrete(a), _) if a.val.is_zero() => Some(other.clone()), + (_, Elem::Concrete(b)) if b.val.is_zero() => Some(self.clone()), + (Elem::Concrete(a), Elem::Concrete(b)) => a.range_add(b), + _ => None, + } + } + fn range_wrapping_add(&self, other: &Self) -> Option> { + match (self, other) { + (Elem::Concrete(a), _) if a.val.is_zero() => Some(other.clone()), + (_, Elem::Concrete(b)) if b.val.is_zero() => Some(self.clone()), + (Elem::Concrete(a), Elem::Concrete(b)) => a.range_wrapping_add(b), + _ => None, + } + } +} + +/// Executes an addition given the minimum and maximum of each element. It returns either the _minimum_ bound or _maximum_ bound of the operation. +/// +/// ### Explanation +/// A fact about addition is that the smallest value possible (in unbounded integer space), is between two _minimum_ values and the largest +/// is between two _maximum_ values. This fact is used in normal "unbounded" (really, saturating) addition calculations as well as wrapping addition as basis for another fact: +/// +/// In wrapping addition, if the bounds allow for optionally wrapping (e.g.: minimum + minimum does not wrap, but maximum + maximum does wrap), we can +/// by extension include *both* the type's maximum and minimum. +/// +/// For example, assume: +///uint256 x: [100, 2256-1] +///uint256 y: [100, 2256-1] +///unchecked { x + y } +/// +/// +/// In this addition of `x+y`, `100+100` does not wrap, but 2256-1 + 2256-1 does. We can construct a value of x and y such that +/// the result of `x+y` is equal to 2256-1 (100 + 2256-101) or `0` (100 + 2256-99). Therefore, the new bounds +/// on `unchecked { x + y }` is [0, 2256-1]. +/// +/// +/// ### Note +/// Signed integers use 2's complement representation so the maximum is 2size - 1 - 1, while unsigned integers are 2size - 1 +/// +/// +/// ### Truth Tables +/// Truth table for `checked add` operation: +/// +///| Add | Uint | Int | BytesX | Address | Bytes | String | +///|-----------------|------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------|--------|---------|-------|--------| +///| **Uint** | min: lhsmin + rhsmin
max: lhsmax + rhsmax | min: lhsmin + rhsmin
max: lhsmax + rhsmax | N/A | N/A | N/A | N/A | +///| **Int** | min: lhsmin + rhsmin
max: lhsmax + rhsmax | min: lhsmin + rhsmin
max: lhsmax + rhsmax | N/A | N/A | N/A | N/A | +///| **BytesX** | N/A | N/A | N/A | N/A | N/A | N/A | +///| **Address** | N/A | N/A | N/A | N/A | N/A | N/A | +///| **Bytes** | N/A | N/A | N/A | N/A | N/A | N/A | +///| **String** | N/A | N/A | N/A | N/A | N/A | N/A | +/// +/// Truth table for `wrapping add` operation: +/// +///| Wrapping Add | Uint | Int | BytesX | Address | Bytes | String | +///|---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|--------|---------|-------|--------| +///| **Uint** | min: {0, lhsmin + rhsmin}
max: {2size - 1, lhsmax + rhsmax} | min: {0, lhsmin + rhsmin}
max: {2size - 1, lhsmax + rhsmax} | N/A | N/A | N/A | N/A | +///| **Int** | min: {-2size-1, lhsmin + rhsmin}
max: {2size - 1, lhsmax + rhsmax} | min: {0, lhsmin + rhsmin}
max: {2size - 1, lhsmax + rhsmax} | N/A | N/A | N/A | N/A | +///| **BytesX** | N/A | N/A | N/A | N/A | N/A | N/A | +///| **Address** | N/A | N/A | N/A | N/A | N/A | N/A | +///| **Bytes** | N/A | N/A | N/A | N/A | N/A | N/A | +///| **String** | N/A | N/A | N/A | N/A | N/A | N/A | +pub fn exec_add( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, + wrapping: bool, + _analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + tracing::trace!("exec add: unchecked - {wrapping}; maximize - {maximize};"); + if wrapping { + let mut candidates = vec![]; + let mut all_overflowed = true; + let mut one_overflowed = false; + + let zero = Elem::Concrete(RangeConcrete::new( + Concrete::from(U256::zero()), + Loc::Implicit, + )); + let add_candidate = |lhs: &Elem, + rhs: &Elem, + candidates: &mut Vec>, + all_overflowed: &mut bool, + one_overflowed: &mut bool, + arena: &mut RangeArena>| { + if let Some(c) = lhs.range_wrapping_add(rhs) { + let lhs_neg = matches!(lhs.range_ord(&zero, arena), Some(std::cmp::Ordering::Less)); + let rhs_neg = matches!(rhs.range_ord(&zero, arena), Some(std::cmp::Ordering::Less)); + let signed = lhs_neg || rhs_neg; + + let overflowed = if signed { + // signed safemath: (rhs >= 0 && c >= lhs) || (rhs < 0 && c < lhs) ==> no overflowed --invert-> overflowed + (rhs_neg + || matches!(c.range_ord(lhs, arena), Some(std::cmp::Ordering::Greater))) + && (!rhs_neg + || matches!( + c.range_ord(lhs, arena), + Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) + )) + } else { + // unsigned safemath: c < a ==> overflowed + matches!(c.range_ord(lhs, arena), Some(std::cmp::Ordering::Less)) + }; + + if *all_overflowed && !overflowed { + *all_overflowed = false; + } + + if !*one_overflowed && overflowed { + *one_overflowed = true; + } + + candidates.push(c); + } + }; + + add_candidate( + lhs_min, + rhs_min, + &mut candidates, + &mut all_overflowed, + &mut one_overflowed, + arena, + ); + add_candidate( + lhs_min, + rhs_max, + &mut candidates, + &mut all_overflowed, + &mut one_overflowed, + arena, + ); + add_candidate( + lhs_max, + rhs_min, + &mut candidates, + &mut all_overflowed, + &mut one_overflowed, + arena, + ); + add_candidate( + lhs_max, + rhs_max, + &mut candidates, + &mut all_overflowed, + &mut one_overflowed, + arena, + ); + + // We need to check if there is a value in [lhs_min, lhs_max] that when added to a value in [rhs_min, rhs_max] + // will not overflow + // + // If that is the case we can additionally compare saturating addition cases to the candidates + if !all_overflowed { + // We didnt overflow in some case, add saturating addition candidates + let saturating_add = + |lhs: &Elem<_>, rhs: &Elem<_>, candidates: &mut Vec>| -> bool { + if let Some(c) = lhs.range_add(rhs) { + candidates.push(c); + true + } else { + false + } + }; + // if max + max returned a result, that saturating addition will be largest possible candidate + if !saturating_add(lhs_max, rhs_max, &mut candidates) { + saturating_add(lhs_min, rhs_min, &mut candidates); + saturating_add(lhs_min, rhs_max, &mut candidates); + saturating_add(lhs_max, rhs_min, &mut candidates); + } + } + + // We need to check if there is a value in [lhs_min, lhs_max] that when added to a value in [rhs_min, rhs_max] + // will overflow and can result in the minimum value of the type + // + // We can do this by checking if we can conditionally overflow. + let conditional_overflow = !all_overflowed && one_overflowed; + if conditional_overflow { + let add_min = |elem: &Elem, candidates: &mut Vec>| { + if let Some(c) = elem.maybe_concrete() { + if let Some(min) = Concrete::min_of_type(&c.val) { + candidates.push(RangeConcrete::new(min, c.loc).into()); + } + + if let Some(max) = Concrete::max_of_type(&c.val) { + candidates.push(RangeConcrete::new(max, c.loc).into()); + } + } + }; + // We are able to conditionally overflow, so add min + add_min(lhs_min, &mut candidates); + add_min(lhs_max, &mut candidates); + } + + // Sort the candidates + candidates.sort_by(|a, b| match a.range_ord(b, arena) { + Some(r) => r, + _ => std::cmp::Ordering::Less, + }); + + if candidates.is_empty() { + return None; + } + + if maximize { + Some(candidates.remove(candidates.len() - 1)) + } else { + Some(candidates.remove(0)) + } + } else if maximize { + // if we are maximizing, the largest value will always just be the the largest value + the largest value + lhs_max.range_add(rhs_max) + } else { + // if we are minimizing, the smallest value will always just be the the smallest value + the smallest value + lhs_min.range_add(rhs_min) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::DummyGraph; + use ethers_core::types::U256; + use solang_parser::pt::Loc; + + #[test] + fn uint_uint() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(15)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(256, U256::from(5)), Loc::Implicit); + let result = x.range_add(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::from(20))); + } + + #[test] + fn saturating_uint_uint() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::MAX), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(256, U256::MAX), Loc::Implicit); + let result = x.range_add(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::MAX)); + } + + #[test] + fn sized_saturating_uint_uint() { + let x = RangeConcrete::new(Concrete::Uint(8, U256::from(254)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(8, U256::from(254)), Loc::Implicit); + let result = x.range_add(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(8, U256::from(255))); + } + + #[test] + fn int_big_uint() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(15)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(-1i32)), Loc::Implicit); + let result = x.range_add(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::from(14))); + } + + #[test] + fn big_int_uint() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(1)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(-15i32)), Loc::Implicit); + let result = x.range_add(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::from(-14i32))); + } + + #[test] + fn int_int() { + let x = RangeConcrete::new(Concrete::Int(256, I256::from(-15i32)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(-15i32)), Loc::Implicit); + let result = x.range_add(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::from(-30i32))); + } + + #[test] + fn min_int_min_int() { + let x = RangeConcrete::new(Concrete::Int(256, I256::MIN), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::MIN), Loc::Implicit); + let result = x.range_add(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::MIN)); + } + + #[test] + fn saturating_int_int() { + let x = RangeConcrete::new( + Concrete::Int(256, I256::MIN + I256::from(1i32)), + Loc::Implicit, + ); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(-2i32)), Loc::Implicit); + let result = x.range_add(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::MIN)); + } + + #[test] + fn sized_saturating_int_int() { + let x = RangeConcrete::new(Concrete::Int(8, I256::from(-127i32)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(8, I256::from(-2i32)), Loc::Implicit); + let result = x.range_add(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(8, I256::from(-128i32))); + } + + #[test] + fn wrapping_uint_uint() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::MAX), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(256, U256::from(2)), Loc::Implicit); + let result = x + .range_wrapping_add(&y) + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::from(1))); + } + + #[test] + fn sized_wrapping_uint_uint() { + let x = RangeConcrete::new(Concrete::Uint(8, U256::from(255)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(8, U256::from(2)), Loc::Implicit); + let result = x + .range_wrapping_add(&y) + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, Concrete::Uint(8, U256::from(1))); + } + + #[test] + fn wrapping_big_int_uint() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(1)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(-15i32)), Loc::Implicit); + let result = x.range_add(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::from(-14i32))); + } + + #[test] + fn wrapping_int_int() { + let x = RangeConcrete::new(Concrete::Int(256, I256::MIN), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(-1i32)), Loc::Implicit); + let result = x + .range_wrapping_add(&y) + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::MAX)); + } + + #[test] + fn sized_wrapping_int_int() { + let x = RangeConcrete::new(Concrete::Int(8, I256::from(-128i32)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(8, I256::from(-1i32)), Loc::Implicit); + let result = x + .range_wrapping_add(&y) + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, Concrete::Int(8, I256::from(127i32))); + } + + #[test] + fn exec_wrapping_min_int_min_int() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let min = RangeConcrete::new(Concrete::Int(256, I256::MIN), Loc::Implicit).into(); + let max = RangeConcrete::new(Concrete::Int(256, I256::MAX), Loc::Implicit).into(); + let max_result = exec_add(&min, &min, &min, &max, true, true, &g, &mut arena) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Int(256, I256::MAX)); + let min_result = exec_add(&min, &min, &min, &max, false, true, &g, &mut arena) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Int(256, I256::MIN)); + } + + #[test] + fn exec_sized_uint_uint_saturating() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_uint_sized(105).into(); + let lhs_max = rc_uint_sized(150).into(); + let rhs_min = rc_uint_sized(10).into(); + let rhs_max = rc_uint_sized(200).into(); + + let max_result = exec_add( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, true, false, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Uint(8, U256::from(255))); + let min_result = exec_add( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, false, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Uint(8, U256::from(115))); + } + + #[test] + fn exec_sized_wrapping_uint_uint() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_uint_sized(105).into(); + let lhs_max = rc_uint_sized(150).into(); + let rhs_min = rc_uint_sized(10).into(); + let rhs_max = rc_uint_sized(200).into(); + + let max_result = exec_add( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, true, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Uint(8, U256::from(255))); + let min_result = exec_add( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Uint(8, U256::from(0))); + } + + #[test] + fn exec_sized_wrapping_int_uint() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_int_sized(-128).into(); + let lhs_max = rc_int_sized(127).into(); + let rhs_min = rc_uint_sized(0).into(); + let rhs_max = rc_uint_sized(255).into(); + + let max_result = exec_add( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, true, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Int(8, I256::from(127i32))); + let min_result = exec_add( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Int(8, I256::from(-128i32))); + } + + #[test] + fn exec_sized_wrapping_int_int_max() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_int_sized(-128).into(); + let lhs_max = rc_int_sized(-100).into(); + let rhs_min = rc_int_sized(-5).into(); + let rhs_max = rc_int_sized(5).into(); + + let max_result = exec_add( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, true, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Int(8, I256::from(127i32))); + let min_result = exec_add( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Int(8, I256::from(-128i32))); + } +} diff --git a/crates/graph/src/range/exec/math_ops/div.rs b/crates/graph/src/range/exec/math_ops/div.rs new file mode 100644 index 00000000..00ed109d --- /dev/null +++ b/crates/graph/src/range/exec/math_ops/div.rs @@ -0,0 +1,494 @@ +use crate::nodes::Concrete; +use crate::range::{elem::*, exec_traits::*}; +use crate::GraphBackend; + +use shared::RangeArena; + +use ethers_core::types::{I256, U256}; +use solang_parser::pt::Loc; + +impl RangeDiv for RangeConcrete { + fn range_div(&self, other: &Self) -> Option> { + match (self.val.into_u256(), other.val.into_u256()) { + (Some(lhs_val), Some(rhs_val)) => { + if rhs_val == 0.into() { + None + } else { + let op_res = lhs_val / rhs_val; + let val = self.val.u256_as_original(op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + } + _ => match (&self.val, &other.val) { + (Concrete::Uint(lhs_size, val), Concrete::Int(_, neg_v)) => { + // Divisor cannot be zero because it would have been converted + // to a uint + let abs = neg_v.into_sign_and_abs().1; + let op_res = I256::from_raw(val / abs).saturating_div(I256::from(-1i32)); + let val = Concrete::Int(*lhs_size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + (Concrete::Int(lhs_size, neg_v), Concrete::Uint(_, val)) => { + if val == &U256::from(0) { + None + } else { + let abs = neg_v.into_sign_and_abs().1; + let op_res = I256::from_raw(abs / *val).saturating_div(I256::from(-1i32)); + let val = Concrete::Int(*lhs_size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + } + (Concrete::Int(lhs_size, l), Concrete::Int(_rhs_size, r)) => { + if r == &I256::from(0) { + None + } else { + let (op_res, overflow) = l.overflowing_div(*r); + if overflow { + let max = Concrete::max_of_type(&self.val).unwrap().int_val().unwrap(); + let val = Concrete::Int(*lhs_size, max); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } else { + let val = Concrete::Int(*lhs_size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + } + } + _ => None, + }, + } + } + + fn range_wrapping_div(&self, other: &Self) -> Option> { + // Only negative Int / negative Int needs overflowing_div + match (&self.val, &other.val) { + (Concrete::Int(lhs_size, l), Concrete::Int(_rhs_size, r)) + if *l < I256::from(0i32) && *r < I256::from(0i32) => + { + let op_res = l.overflowing_div(*r).0; + let val = Concrete::Int(*lhs_size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + _ => self.range_div(other), + } + } +} + +impl RangeDiv for Elem { + fn range_div(&self, other: &Self) -> Option> { + match (self, other) { + (Elem::Concrete(a), Elem::Concrete(b)) => a.range_div(b), + _ => None, + } + } + + fn range_wrapping_div(&self, other: &Self) -> Option> { + match (self, other) { + (Elem::Concrete(a), Elem::Concrete(b)) => a.range_div(b), + _ => None, + } + } +} + +/// Executes an division given the minimum and maximum of each element. It returns either the _minimum_ bound or _maximum_ bound +/// of the operation. +/// +/// ### Note +/// Signed integers use 2's complement representation so the maximum is 2size - 1 - 1, while unsigned integers are 2size - 1 +/// +/// +/// ### Truth Tables +/// Truth table for `checked div` operation: +/// +/// `todo!()` +/// +/// Truth table for `wrapping div` operation: +/// +/// `todo!()` +/// +pub fn exec_div( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, + wrapping: bool, + _analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + let mut candidates = vec![]; + let saturating_div = |lhs: &Elem<_>, rhs: &Elem<_>, candidates: &mut Vec>| { + if let Some(c) = lhs.range_div(rhs) { + candidates.push(c); + } + }; + + let one = Elem::from(Concrete::from(U256::from(1))); + let negative_one = Elem::from(Concrete::from(I256::from(-1i32))); + + let min_contains = matches!( + rhs_min.range_ord(&one, arena), + Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) + ); + + let max_contains = matches!( + rhs_max.range_ord(&one, arena), + Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) + ); + + // for division, if 1 is contained by the denominator, we can just add the left hand side as candidates + if min_contains && max_contains { + candidates.push(lhs_min.clone()); + candidates.push(lhs_max.clone()); + } + + let min_contains_neg_one = matches!( + rhs_min.range_ord(&negative_one, arena), + Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) + ); + + let max_contains_neg_one = matches!( + rhs_max.range_ord(&negative_one, arena), + Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) + ); + + if min_contains_neg_one && max_contains_neg_one { + // if the divisor contains -1, we can just saturating multiply by -1 + if matches!( + lhs_min.range_ord(&negative_one, arena), + Some(std::cmp::Ordering::Less) + ) { + // lhs can be negative, check if it contains int_min + let type_min = Concrete::min_of_type(&lhs_min.maybe_concrete().unwrap().val).unwrap(); + let int_val = type_min.int_val().unwrap(); + let min = Elem::from(type_min); + let min_plus_one = Elem::Concrete(rc_i256_sized(int_val + I256::from(1i32))); + + let lhs_contains_int_min = matches!( + lhs_min.range_ord(&min, arena), + Some(std::cmp::Ordering::Equal) + ); + + let max_contains_int_min_plus_one = matches!( + rhs_max.range_ord(&min_plus_one, arena), + Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) + ); + + // add int max to candidates + if lhs_contains_int_min && max_contains_int_min_plus_one { + if let Some(c) = min_plus_one.range_mul(&negative_one) { + candidates.push(c); + } + } else if let Some(c) = lhs_min.range_mul(&negative_one) { + // add min * -1 + candidates.push(c); + } + } else if let Some(c) = lhs_min.range_mul(&negative_one) { + // add min * -1 + candidates.push(c); + } + + if let Some(c) = lhs_max.range_mul(&negative_one) { + candidates.push(c); + } + } + + if wrapping { + let mut all_overflowed = true; + let mut one_overflowed = false; + let add_candidate = |lhs: &Elem, + rhs: &Elem, + candidates: &mut Vec>, + all_overflowed: &mut bool, + one_overflowed: &mut bool, + arena: &mut RangeArena>| { + if let Some(c) = lhs.range_wrapping_div(rhs) { + let mut overflowed = false; + let neg_one = + RangeConcrete::new(Concrete::Int(8, I256::from(-1i32)), Loc::Implicit).into(); + if matches!( + lhs.range_ord(&neg_one, arena), + Some(std::cmp::Ordering::Less) + ) { + // rhs == -1 + let div_neg_one = matches!( + rhs.range_ord(&neg_one, arena), + Some(std::cmp::Ordering::Equal) + ); + + let type_min = + Concrete::min_of_type(&lhs.maybe_concrete().unwrap().val).unwrap(); + let min = RangeConcrete::new(type_min, Loc::Implicit).into(); + + // lhs == INT_MIN + let num_int_min = + matches!(lhs.range_ord(&min, arena), Some(std::cmp::Ordering::Equal)); + if div_neg_one && num_int_min { + overflowed = true; + } + } + + if *all_overflowed && !overflowed { + *all_overflowed = false; + } + + if !*one_overflowed && overflowed { + *one_overflowed = true; + } + + candidates.push(c); + } + }; + + add_candidate( + lhs_min, + rhs_min, + &mut candidates, + &mut all_overflowed, + &mut one_overflowed, + arena, + ); + add_candidate( + lhs_min, + rhs_max, + &mut candidates, + &mut all_overflowed, + &mut one_overflowed, + arena, + ); + add_candidate( + lhs_max, + rhs_min, + &mut candidates, + &mut all_overflowed, + &mut one_overflowed, + arena, + ); + add_candidate( + lhs_max, + rhs_max, + &mut candidates, + &mut all_overflowed, + &mut one_overflowed, + arena, + ); + + if one_overflowed { + let add_min = |elem: &Elem, candidates: &mut Vec>| { + if let Some(c) = elem.maybe_concrete() { + if let Some(min) = Concrete::min_of_type(&c.val) { + candidates.push(RangeConcrete::new(min, c.loc).into()); + } + } + }; + add_min(lhs_min, &mut candidates); + add_min(lhs_max, &mut candidates); + } + } else { + // without inspecting types of lhs and rhs, its easiest just to run them all and + // sort + saturating_div(lhs_min, rhs_min, &mut candidates); + saturating_div(lhs_min, rhs_max, &mut candidates); + saturating_div(lhs_max, rhs_min, &mut candidates); + saturating_div(lhs_max, rhs_max, &mut candidates); + } + + // Sort the candidates + candidates.sort_by(|a, b| match a.range_ord(b, arena) { + Some(r) => r, + _ => std::cmp::Ordering::Less, + }); + + if candidates.is_empty() { + return None; + } + + if maximize { + Some(candidates.remove(candidates.len() - 1)) + } else { + Some(candidates.remove(0)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::DummyGraph; + use solang_parser::pt::Loc; + + #[test] + fn uint_uint() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(15)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(256, U256::from(5)), Loc::Implicit); + let result = x.range_div(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::from(3))); + } + + #[test] + fn uint_int() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(15)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(5i32)), Loc::Implicit); + let result = x.range_div(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::from(3))); + } + + #[test] + fn uint_neg_int() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(15)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(-5i32)), Loc::Implicit); + let result = x.range_div(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::from(-3i32))); + } + + #[test] + fn neg_int_uint() { + let x = RangeConcrete::new(Concrete::Int(256, I256::from(-15i32)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(256, U256::from(5)), Loc::Implicit); + let result = x.range_div(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::from(-3i32))); + } + + #[test] + fn neg_int_neg_int() { + let x = RangeConcrete::new(Concrete::Int(256, I256::from(-15i32)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(-5i32)), Loc::Implicit); + let result = x.range_div(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::from(3i32))); + } + + #[test] + fn uint_zero() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(15)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(256, U256::from(0)), Loc::Implicit); + assert!(x.range_div(&y).is_none()); + } + + #[test] + fn int_zero() { + let x = RangeConcrete::new(Concrete::Int(256, I256::from(-15i32)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(256, U256::from(0)), Loc::Implicit); + assert!(x.range_div(&y).is_none()); + } + + #[test] + fn wrapping_int_int() { + let x = RangeConcrete::new(Concrete::Int(256, I256::MIN), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(-1i32)), Loc::Implicit); + let result = x.range_wrapping_div(&y).unwrap(); + let expected = x.clone(); + assert_eq!(result, expected.into()); + } + + #[test] + fn nonwrapping_int_int() { + let x = RangeConcrete::new(Concrete::Int(256, I256::MIN), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(-1i32)), Loc::Implicit); + let result = x.range_div(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::MAX)); + } + + #[test] + fn exec_sized_uint_uint_saturating() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_uint_sized(5).into(); + let lhs_max = rc_uint_sized(15).into(); + let rhs_min = rc_uint_sized(1).into(); + let rhs_max = rc_uint_sized(20).into(); + + let max_result = exec_div( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, true, false, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Uint(8, U256::from(15))); + let min_result = exec_div( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, false, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Uint(8, U256::from(0))); + } + + #[test] + fn exec_sized_wrapping_uint_uint() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_uint_sized(5).into(); + let lhs_max = rc_uint_sized(15).into(); + let rhs_min = rc_uint_sized(1).into(); + let rhs_max = rc_uint_sized(16).into(); + + let max_result = exec_div( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, true, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Uint(8, U256::from(15))); + let min_result = exec_div( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Uint(8, U256::from(0))); + } + + #[test] + fn exec_sized_wrapping_int_uint() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_int_sized(-128).into(); + let lhs_max = rc_int_sized(127).into(); + let rhs_min = rc_uint_sized(0).into(); + let rhs_max = rc_uint_sized(255).into(); + + let max_result = exec_div( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, true, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Int(8, I256::from(127i32))); + let min_result = exec_div( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Int(8, I256::from(-128i32))); + } + + #[test] + fn exec_sized_wrapping_int_int_max() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_int_sized(-128).into(); + let lhs_max = rc_int_sized(-100).into(); + let rhs_min = rc_int_sized(-5).into(); + let rhs_max = rc_int_sized(5).into(); + + let max_result = exec_div( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, true, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Int(8, I256::from(127i32))); + let min_result = exec_div( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Int(8, I256::from(-128i32))); + } +} diff --git a/crates/graph/src/range/exec/math_ops/exp.rs b/crates/graph/src/range/exec/math_ops/exp.rs new file mode 100644 index 00000000..aad9faeb --- /dev/null +++ b/crates/graph/src/range/exec/math_ops/exp.rs @@ -0,0 +1,201 @@ +use crate::nodes::Concrete; +use crate::range::{elem::*, exec_traits::*}; +use crate::GraphBackend; + +use shared::RangeArena; + +use ethers_core::types::U256; + +impl RangeExp for RangeConcrete { + fn range_exp(&self, other: &Self) -> Option> { + match (self.val.into_u256(), other.val.into_u256()) { + (Some(lhs_val), Some(rhs_val)) => { + let max = Concrete::max_of_type(&self.val).unwrap(); + + let op_res = lhs_val.checked_pow(rhs_val); + let op_res = if let Some(num) = op_res { + num.min(max.into_u256().unwrap()) + } else { + max.into_u256().unwrap() + }; + + let val = self.val.u256_as_original(op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + _ => match (&self.val, &other.val.into_u256()) { + // exponent must be positive otherwise return None. + (Concrete::Int(lhs_size, neg_v), Some(val)) => { + let pow2 = val % U256::from(2) == 0.into(); + let val = if val > &U256::from(u32::MAX) { + if pow2 { + Concrete::max_of_type(&self.val).unwrap() + } else { + Concrete::min_of_type(&self.val).unwrap() + } + } else { + let min = Concrete::min_of_type(&self.val).unwrap().int_val().unwrap(); + let max = Concrete::max_of_type(&self.val).unwrap().int_val().unwrap(); + + let op_res = neg_v.checked_pow(val.as_u32()); + if let Some(num) = op_res { + if pow2 { + Concrete::Int(*lhs_size, num.min(max)) + } else { + Concrete::Int(*lhs_size, num.max(min)) + } + } else if pow2 { + Concrete::max_of_type(&self.val).unwrap() + } else { + Concrete::min_of_type(&self.val).unwrap() + } + }; + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + _ => None, + }, + } + } +} + +impl RangeExp for Elem { + fn range_exp(&self, other: &Self) -> Option> { + match (self, other) { + (Elem::Concrete(a), Elem::Concrete(b)) => a.range_exp(b), + (Elem::Concrete(a), _) if a.val.is_zero() => Some(Concrete::from(U256::from(1)).into()), + (_, Elem::Concrete(b)) if b.val.is_zero() => Some(other.clone()), + _ => None, + } + } +} + +/// Executes the `exponentiation` operation given the minimum and maximum of each element. It returns either the _minimum_ bound or _maximum_ bound +/// of the operation. +/// +/// TODO: Add wrapping/unchecked version +pub fn exec_exp( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, + _analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + // TODO: Improve this + let candidates = vec![ + lhs_min.range_exp(rhs_min), + lhs_min.range_exp(rhs_max), + lhs_max.range_exp(rhs_min), + lhs_max.range_exp(rhs_max), + ]; + let mut candidates = candidates.into_iter().flatten().collect::>(); + candidates.sort_by(|a, b| match a.range_ord(b, arena) { + Some(r) => r, + _ => std::cmp::Ordering::Less, + }); + + if candidates.is_empty() { + return None; + } + + if maximize { + Some(candidates.remove(candidates.len() - 1)) + } else { + Some(candidates.remove(0)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::DummyGraph; + use ethers_core::types::{I256, U256}; + use solang_parser::pt::Loc; + + #[test] + fn uint_uint() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(15)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(256, U256::from(5)), Loc::Implicit); + let result = x.range_exp(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::from(759375))); + } + + #[test] + fn saturating_uint_uint() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::MAX), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(256, U256::MAX), Loc::Implicit); + let result = x.range_exp(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::MAX)); + } + + #[test] + fn sized_saturating_uint_uint() { + let x = RangeConcrete::new(Concrete::Uint(8, U256::from(254)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(8, U256::from(254)), Loc::Implicit); + let result = x.range_exp(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(8, U256::from(255))); + } + + #[test] + fn int_uint() { + let x = RangeConcrete::new(Concrete::Int(256, I256::from(-1i32)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(256, U256::from(15)), Loc::Implicit); + let result = x.range_exp(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::from(-1i32))); + } + + #[test] + fn int_uint_2() { + let x = RangeConcrete::new(Concrete::Int(256, I256::from(-15i32)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(15i32)), Loc::Implicit); + let result = x.range_exp(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!( + result.val, + Concrete::Int(256, I256::from(-437893890380859375i128)) + ); + } + + #[test] + fn saturating_int_int() { + let x = RangeConcrete::new( + Concrete::Int(256, I256::MIN + I256::from(1i32)), + Loc::Implicit, + ); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(2i32)), Loc::Implicit); + let result = x.range_exp(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::MAX)); + } + + #[test] + fn sized_saturating_int_int() { + let x = RangeConcrete::new(Concrete::Int(8, I256::from(-127i32)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(8, I256::from(-2i32)), Loc::Implicit); + let result = x.range_add(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(8, I256::from(-128i32))); + } + + #[test] + fn exec_sized_uint_uint_saturating() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_uint_sized(2).into(); + let lhs_max = rc_uint_sized(150).into(); + let rhs_min = rc_uint_sized(3).into(); + let rhs_max = rc_uint_sized(200).into(); + + let max_result = exec_exp(&lhs_min, &lhs_max, &rhs_min, &rhs_max, true, &g, &mut arena) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Uint(8, U256::from(255))); + let min_result = exec_exp( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Uint(8, U256::from(8))); + } +} diff --git a/crates/graph/src/range/exec/math_ops/mod.rs b/crates/graph/src/range/exec/math_ops/mod.rs new file mode 100644 index 00000000..fe4a92c5 --- /dev/null +++ b/crates/graph/src/range/exec/math_ops/mod.rs @@ -0,0 +1,12 @@ +mod add; +mod div; +mod exp; +mod modulo; +mod mul; +mod sub; +pub use add::exec_add; +pub use div::exec_div; +pub use exp::exec_exp; +pub use modulo::exec_mod; +pub use mul::exec_mul; +pub use sub::exec_sub; diff --git a/crates/graph/src/range/exec/math_ops/modulo.rs b/crates/graph/src/range/exec/math_ops/modulo.rs new file mode 100644 index 00000000..54e653eb --- /dev/null +++ b/crates/graph/src/range/exec/math_ops/modulo.rs @@ -0,0 +1,329 @@ +use crate::nodes::Concrete; +use crate::range::{elem::*, exec_traits::*}; +use crate::GraphBackend; + +use shared::RangeArena; + +use ethers_core::types::{I256, U256}; + +impl RangeMod for RangeConcrete { + fn range_mod(&self, other: &Self) -> Option> { + match (self.val.into_u256(), other.val.into_u256()) { + (Some(lhs_val), Some(rhs_val)) => { + if rhs_val == 0.into() { + return None; + } + let op_res = lhs_val % rhs_val; + let val = self.val.u256_as_original(op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + _ => match (&self.val, &other.val) { + (Concrete::Uint(lhs_size, val), Concrete::Int(_, neg_v)) => { + let op_res = I256::from_raw(*val) % *neg_v; + let val = Concrete::Int(*lhs_size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + (Concrete::Int(lhs_size, neg_v), Concrete::Uint(_, val)) if *val != 0.into() => { + let op_res = *neg_v % I256::from_raw(*val); + let val = Concrete::Int(*lhs_size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + (Concrete::Int(lhs_size, l), Concrete::Int(_rhs_size, r)) => { + // the wrapping never actually occurs mathematically. See ethers-rs docs for more info + let op_res = l.wrapping_rem(*r); + let val = Concrete::Int(*lhs_size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + _ => None, + }, + } + } +} + +impl RangeMod for Elem { + fn range_mod(&self, other: &Self) -> Option> { + match (self, other) { + (Elem::Concrete(a), Elem::Concrete(b)) => a.range_mod(b), + _ => None, + } + } +} + +/// Executes an modulus given the minimum and maximum of each element. It returns either the _minimum_ bound or _maximum_ bound +/// of the operation. +/// +/// ### Note +/// Signed integers use 2's complement representation so the maximum is 2size - 1 - 1, while unsigned integers are 2size - 1 +/// +/// +/// ### Truth Tables +/// Truth table for `checked mod` operation: +/// +/// `todo!()` +pub fn exec_mod( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, + _analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + let is_const = |l: &Elem<_>, r: &Elem<_>, arena: &mut RangeArena>| -> bool { + matches!(l.range_ord(r, arena), Some(std::cmp::Ordering::Equal)) + }; + + if is_const(lhs_min, lhs_max, arena) && is_const(rhs_min, rhs_max, arena) { + return lhs_min.range_mod(rhs_min); + } + + let zero = Elem::from(Concrete::from(U256::zero())); + + let lhs_min_is_pos = matches!( + lhs_min.range_ord(&zero, arena), + Some(std::cmp::Ordering::Equal) | Some(std::cmp::Ordering::Greater) + ); + + let lhs_max_is_pos = matches!( + lhs_max.range_ord(&zero, arena), + Some(std::cmp::Ordering::Equal) | Some(std::cmp::Ordering::Greater) + ); + let mod_min_is_pos = matches!( + rhs_min.range_ord(&zero, arena), + Some(std::cmp::Ordering::Equal) | Some(std::cmp::Ordering::Greater) + ); + + let mod_max_is_pos = matches!( + rhs_max.range_ord(&zero, arena), + Some(std::cmp::Ordering::Equal) | Some(std::cmp::Ordering::Greater) + ); + + // check if all lhs values are less than rhs values + if maximize + && lhs_max_is_pos + && mod_max_is_pos + && matches!( + lhs_max.range_ord(rhs_max, arena), + Some(std::cmp::Ordering::Less) + ) + { + // the lhs is entirely smaller than the modulo, so its effectively a noop, just return + // the min or max + return Some(lhs_max.clone()); + } + + let mut candidates = vec![]; + let one = Elem::from(Concrete::from(U256::from(1))); + let negative_one = Elem::from(Concrete::from(I256::from(-1i32))); + if !mod_min_is_pos { + if let Some(r) = rhs_min.range_add(&one) { + candidates.push(r); + } + } else if let Some(r) = rhs_min.range_sub(&one) { + candidates.push(r); + } + + if !mod_max_is_pos { + if let Some(r) = rhs_max.range_add(&one) { + candidates.push(r); + } + } else if let Some(r) = rhs_max.range_sub(&one) { + candidates.push(r); + } + + if !lhs_min_is_pos { + if let Some(neg_max) = rhs_max.range_mul(&negative_one) { + match neg_max.range_ord(lhs_min, arena) { + None => {} + Some(std::cmp::Ordering::Less) => candidates.push(lhs_min.clone()), + Some(std::cmp::Ordering::Greater) => { + candidates.push(neg_max.range_add(&one).unwrap()) + } + _ => candidates.push(lhs_min.clone()), + } + } + } + + // Sort the candidates + candidates.sort_by(|a, b| match a.range_ord(b, arena) { + Some(r) => r, + _ => std::cmp::Ordering::Less, + }); + + if candidates.is_empty() { + return None; + } + + if maximize { + Some(candidates.remove(candidates.len() - 1)) + } else { + Some(candidates.remove(0)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::DummyGraph; + use solang_parser::pt::Loc; + + #[test] + fn uint_uint() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(17)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(256, U256::from(5)), Loc::Implicit); + let result = x.range_mod(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::from(2))); + } + + #[test] + fn uint_int() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(17)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(5i32)), Loc::Implicit); + let result = x.range_mod(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::from(2))); + } + + #[test] + fn uint_neg_int() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(17)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(-5i32)), Loc::Implicit); + let result = x.range_mod(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::from(2))); + } + + #[test] + fn neg_int_uint() { + let x = RangeConcrete::new(Concrete::Int(256, I256::from(-17i32)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(256, U256::from(5)), Loc::Implicit); + let result = x.range_mod(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::from(-2i32))); + } + + #[test] + fn neg_int_neg_int() { + let x = RangeConcrete::new(Concrete::Int(256, I256::from(-17i32)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(-5i32)), Loc::Implicit); + let result = x.range_mod(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::from(-2i32))); + } + + #[test] + fn uint_zero() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(17)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(256, U256::from(0)), Loc::Implicit); + assert!(x.range_mod(&y).is_none()); + } + + #[test] + fn int_zero() { + let x = RangeConcrete::new(Concrete::Int(256, I256::from(-17i32)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(256, U256::from(0)), Loc::Implicit); + assert!(x.range_mod(&y).is_none()); + } + + #[test] + fn int_int() { + let x = RangeConcrete::new(Concrete::Int(256, I256::MIN), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(-1i32)), Loc::Implicit); + let result = x.range_mod(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::from(0i32))); + } + + #[test] + fn exec_sized_uint_uint_1() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_uint_sized(5).into(); + let lhs_max = rc_uint_sized(15).into(); + let rhs_min = rc_uint_sized(1).into(); + let rhs_max = rc_uint_sized(20).into(); + + let max_result = exec_mod(&lhs_min, &lhs_max, &rhs_min, &rhs_max, true, &g, &mut arena) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Uint(8, U256::from(15))); + let min_result = exec_mod( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Uint(8, U256::from(0))); + } + + #[test] + fn exec_sized_uint_uint_2() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_uint_sized(16).into(); + let lhs_max = rc_uint_sized(160).into(); + let rhs_min = rc_uint_sized(1).into(); + let rhs_max = rc_uint_sized(16).into(); + + let max_result = exec_mod(&lhs_min, &lhs_max, &rhs_min, &rhs_max, true, &g, &mut arena) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Uint(8, U256::from(15))); + let min_result = exec_mod( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Uint(8, U256::from(0))); + } + + #[test] + fn exec_sized_int_uint() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_int_sized(-128).into(); + let lhs_max = rc_int_sized(127).into(); + let rhs_min = rc_uint_sized(0).into(); + let rhs_max = rc_uint_sized(255).into(); + + let max_result = exec_mod(&lhs_min, &lhs_max, &rhs_min, &rhs_max, true, &g, &mut arena) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Int(8, I256::from(127i32))); + let min_result = exec_mod( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Int(8, I256::from(-128i32))); + } + + #[test] + fn exec_sized_int_int_max() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_int_sized(-128).into(); + let lhs_max = rc_int_sized(-100).into(); + let rhs_min = rc_int_sized(-5).into(); + let rhs_max = rc_int_sized(5).into(); + + let max_result = exec_mod(&lhs_min, &lhs_max, &rhs_min, &rhs_max, true, &g, &mut arena) + .unwrap() + .maybe_concrete() + .unwrap(); + // TODO: improve mod calc to consider lhs being entirely negative + // assert_eq!(max_result.val, Concrete::Int(8, I256::from(0i32))); + assert_eq!(max_result.val, Concrete::Int(8, I256::from(4i32))); + let min_result = exec_mod( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Int(8, I256::from(-4i32))); + } +} diff --git a/crates/graph/src/range/exec/math_ops/mul.rs b/crates/graph/src/range/exec/math_ops/mul.rs new file mode 100644 index 00000000..a1cb33a0 --- /dev/null +++ b/crates/graph/src/range/exec/math_ops/mul.rs @@ -0,0 +1,507 @@ +use crate::nodes::Concrete; +use crate::range::{elem::*, exec_traits::*}; +use crate::GraphBackend; + +use shared::RangeArena; + +use ethers_core::types::{I256, U256}; + +impl RangeMul for RangeConcrete { + fn range_mul(&self, other: &Self) -> Option> { + match (self.val.into_u256(), other.val.into_u256()) { + (Some(lhs_val), Some(rhs_val)) => { + let max = Concrete::max_of_type(&self.val) + .unwrap() + .into_u256() + .unwrap(); + let mut op_res = lhs_val.saturating_mul(rhs_val).min(max); + if let Some(min) = Concrete::min_of_type(&self.val).unwrap().into_u256() { + op_res = op_res.max(min); + } + let val = self.val.u256_as_original(op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + _ => match (&self.val, &other.val) { + (Concrete::Uint(lhs_size, val), Concrete::Int(_, neg_v)) + | (Concrete::Int(lhs_size, neg_v), Concrete::Uint(_, val)) => { + let tmp = Concrete::Int(*lhs_size, I256::from(0i32)); + let min = Concrete::min_of_type(&tmp).unwrap().int_val().unwrap(); + let max = Concrete::max_of_type(&tmp).unwrap().int_val().unwrap(); + + let op_res = neg_v.saturating_mul(I256::from_raw(*val)).max(min).min(max); + let val = Concrete::Int(*lhs_size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + (Concrete::Int(lhs_size, l), Concrete::Int(_rhs_size, r)) => { + let tmp = Concrete::Int(*lhs_size, I256::from(0i32)); + let min = Concrete::min_of_type(&tmp).unwrap().int_val().unwrap(); + let max = Concrete::max_of_type(&tmp).unwrap().int_val().unwrap(); + + let op_res = l.saturating_mul(*r).min(max).max(min); + let val = Concrete::Int(*lhs_size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + _ => None, + }, + } + } + + fn range_wrapping_mul(&self, other: &Self) -> Option> { + match (self.val.into_u256(), other.val.into_u256()) { + (Some(lhs_val), Some(rhs_val)) => { + let op_res = lhs_val.overflowing_mul(rhs_val).0; + let val = self.val.u256_as_original(op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + _ => match (&self.val, &other.val) { + (Concrete::Uint(lhs_size, val), Concrete::Int(_, neg_v)) + | (Concrete::Int(lhs_size, neg_v), Concrete::Uint(_, val)) => { + let op_res = neg_v.overflowing_mul(I256::from_raw(*val)).0; + let val = Concrete::Int(*lhs_size, op_res).size_wrap(); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + (Concrete::Int(lhs_size, l), Concrete::Int(_rhs_size, r)) => { + let op_res = l.overflowing_mul(*r).0; + let val = Concrete::Int(*lhs_size, op_res).size_wrap(); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + _ => None, + }, + } + } +} + +impl RangeMul for Elem { + fn range_mul(&self, other: &Self) -> Option> { + match (self, other) { + (Elem::Concrete(a), Elem::Concrete(b)) => a.range_mul(b), + (Elem::Concrete(a), _) if a.val.is_zero() => Some(self.clone()), + (_, Elem::Concrete(b)) if b.val.is_zero() => Some(other.clone()), + (Elem::Concrete(a), b) if a.val.is_one() => Some(b.clone()), + (a, Elem::Concrete(b)) if b.val.is_one() => Some(a.clone()), + _ => None, + } + } + + fn range_wrapping_mul(&self, other: &Self) -> Option> { + match (self, other) { + (Elem::Concrete(a), Elem::Concrete(b)) => a.range_wrapping_mul(b), + (Elem::Concrete(a), _) if a.val.is_zero() => Some(self.clone()), + (_, Elem::Concrete(b)) if b.val.is_zero() => Some(other.clone()), + (Elem::Concrete(a), b) if a.val.is_one() => Some(b.clone()), + (a, Elem::Concrete(b)) if b.val.is_one() => Some(a.clone()), + _ => None, + } + } +} + +/// Executes an multiplication given the minimum and maximum of each element. It returns either the _minimum_ bound or _maximum_ bound +/// of the operation. +/// +/// ### Explanation +/// A fact about multiplication is that the smallest value possible (in unbounded unsigned integer space), is between two _minimum_ +/// values and the largest is between two _maximum_ values. In the unbounded signed integer space, the smallest value will be +/// the product of the most negative value and most positive value. +/// +/// Pyrometer _overestimates_ products of multiplication in both the saturating and wrapping cases. This is due to +/// to the fact that the multiplicants may not contain factors of the maximum/minimum. That is to say, +/// the factors of, for example, 2size-1 may not exactly be in the left hand side +/// and right hand side. By default, we allow this overestimation. The only case we cover is when both elements' minimums +/// product always overflows. +/// +/// +/// For example, assume: +///uint256 x: [2240, 2256-1] +///uint256 y: [216, 2256-1] +///unchecked { x * y } +/// +/// +/// In this multiplication of `x*y`, it will always overflow so the minimum is still `x.min * y.min`, and the maximum is still `x.max * y.max`. However, +/// had `x.min * y.min` _not_ overflowed, the maximum would have been `type(uint256).max` (despite not knowing if the factors of `type(uint256).max` are contained +/// in x & y) and the minimum would be `type(uint256).min` (despite not knowing if `unchecked { type(uint256).max + 1 }`'s factors are contained in x & y). Therefore, +/// we have potentially underestimated the minimum and overestimated the maximum of the product. Factorization of large integers is untenable from a performance standpoint +/// so this concession on precision is accepted (and remains sound but can result in false positive analyses if depended on). +/// +/// ### Note +/// Signed integers use 2's complement representation so the maximum is 2size - 1 - 1, while unsigned integers are 2size - 1 +/// +/// +/// ### Truth Tables +/// Truth table for `checked mul` operation: +/// +///| Mul | Uint | Int | BytesX | Address | Bytes | String | +///|-----------------|------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------|--------|---------|-------|--------| +///| **Uint** | min: lhsmin * rhsmin
max: lhsmax * rhsmax | min: lhsmin * rhsmin
max: lhsmax * rhsmax | N/A | N/A | N/A | N/A | +///| **Int** | min: lhsmin * rhsmin
max: lhsmax * rhsmax | min: lhsmin * rhsmin
max: lhsmax * rhsmax | N/A | N/A | N/A | N/A | +///| **BytesX** | N/A | N/A | N/A | N/A | N/A | N/A | +///| **Address** | N/A | N/A | N/A | N/A | N/A | N/A | +///| **Bytes** | N/A | N/A | N/A | N/A | N/A | N/A | +///| **String** | N/A | N/A | N/A | N/A | N/A | N/A | +/// +/// Truth table for `wrapping mul` operation: +/// +/// `todo!()` +/// +// | Wrapping Mul | Uint | Int | BytesX | Address | Bytes | String | +// |---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|--------|---------|-------|--------| +// | **Uint** | min: {0, lhsmin * rhsmin}
max: {2size - 1, lhsmax * rhsmax} | min: {0, lhsmin + rhsmin}
max: {2size - 1, lhsmax + rhsmax} | N/A | N/A | N/A | N/A | +// | **Int** | min: {
   -2size-1,
   lhsmin * rhsmin,
   lhsmin * rhsmax,
   lhsmax * rhsmin
}
max: {
   2size - 1,
   lhsmax * rhsmax
} | min: {0, lhsmin * rhsmin}
max: {2size - 1, lhsmax + rhsmax} | N/A | N/A | N/A | N/A | +// | **BytesX** | N/A | N/A | N/A | N/A | N/A | N/A | +// | **Address** | N/A | N/A | N/A | N/A | N/A | N/A | +// | **Bytes** | N/A | N/A | N/A | N/A | N/A | N/A | +// | **String** | N/A | N/A | N/A | N/A | N/A | N/A | +pub fn exec_mul( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, + wrapping: bool, + _analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + let mut candidates = vec![]; + let saturating_mul = |lhs: &Elem<_>, rhs: &Elem<_>, candidates: &mut Vec>| { + if let Some(c) = lhs.range_mul(rhs) { + candidates.push(c); + } + }; + + if wrapping { + let zero = Elem::from(Concrete::from(U256::zero())); + let mut all_overflowed = true; + let mut one_overflowed = false; + let add_candidate = |lhs: &Elem, + rhs: &Elem, + candidates: &mut Vec>, + all_overflowed: &mut bool, + one_overflowed: &mut bool, + arena: &mut RangeArena<_>| { + if let Some(c) = lhs.range_wrapping_mul(rhs) { + if !matches!(lhs.range_ord(&zero, arena), Some(std::cmp::Ordering::Equal)) { + let reverse = c.range_div(lhs).unwrap(); + let overflowed = !matches!( + reverse.range_ord(rhs, arena).unwrap(), + std::cmp::Ordering::Equal + ); + if *all_overflowed && !overflowed { + *all_overflowed = false; + } + + if !*one_overflowed && overflowed { + *one_overflowed = true; + } + } + + candidates.push(c); + } + }; + + add_candidate( + lhs_min, + rhs_min, + &mut candidates, + &mut all_overflowed, + &mut one_overflowed, + arena, + ); + add_candidate( + lhs_min, + rhs_max, + &mut candidates, + &mut all_overflowed, + &mut one_overflowed, + arena, + ); + add_candidate( + lhs_max, + rhs_min, + &mut candidates, + &mut all_overflowed, + &mut one_overflowed, + arena, + ); + add_candidate( + lhs_max, + rhs_max, + &mut candidates, + &mut all_overflowed, + &mut one_overflowed, + arena, + ); + + if all_overflowed || one_overflowed { + // We overflowed in every case, or had a conditional overflow. + // In this case we just under/overestimate + saturating_mul(lhs_max, rhs_max, &mut candidates); + saturating_mul(lhs_min, rhs_min, &mut candidates); + saturating_mul(lhs_min, rhs_max, &mut candidates); + saturating_mul(lhs_max, rhs_min, &mut candidates); + + let add_min = |elem: &Elem, candidates: &mut Vec>| { + if let Some(c) = elem.maybe_concrete() { + if let Some(min) = Concrete::min_of_type(&c.val) { + candidates.push(RangeConcrete::new(min, c.loc).into()); + } + } + }; + // We are able to conditionally overflow, so add min of both types + add_min(lhs_min, &mut candidates); + add_min(lhs_max, &mut candidates); + add_min(rhs_min, &mut candidates); + add_min(rhs_max, &mut candidates); + } + } else { + // without inspecting types of lhs and rhs, its easiest just to run them all and + // sort + saturating_mul(lhs_min, rhs_min, &mut candidates); + saturating_mul(lhs_min, rhs_max, &mut candidates); + saturating_mul(lhs_max, rhs_min, &mut candidates); + saturating_mul(lhs_max, rhs_max, &mut candidates); + } + + // Sort the candidates + candidates.sort_by(|a, b| match a.range_ord(b, arena) { + Some(r) => r, + _ => std::cmp::Ordering::Less, + }); + + if candidates.is_empty() { + return None; + } + + if maximize { + Some(candidates.remove(candidates.len() - 1)) + } else { + Some(candidates.remove(0)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::DummyGraph; + use ethers_core::types::U256; + use solang_parser::pt::Loc; + + #[test] + fn sized_uint_uint() { + let x = rc_uint_sized(255); + let y = rc_uint_sized(255); + let result = x.range_mul(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(8, U256::from(255))); + } + + #[test] + fn sized_wrapping_uint_uint() { + let x = rc_uint_sized(255); + let y = rc_uint_sized(255); + let result = x + .range_wrapping_mul(&y) + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, Concrete::Uint(8, U256::from(1))); + } + + #[test] + fn sized_int_int() { + let x = rc_int_sized(-127); + let y = rc_int_sized(-127); + let result = x.range_mul(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(8, I256::from(127i32))); + } + + #[test] + fn sized_int_int_one() { + let x = rc_int_sized(-1); + let y = rc_int_sized(-128); + let result = x.range_mul(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(8, I256::from(127i32))); + } + + #[test] + fn sized_int_uint() { + let x = rc_int_sized(-127); + let y = rc_int_sized(127); + let y2 = rc_uint_sized(127); + let result = x.range_mul(&y).unwrap().maybe_concrete_value().unwrap(); + let result2 = x.range_mul(&y2).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result, result2); + assert_eq!(result.val, Concrete::Int(8, I256::from(-128i32))); + } + #[test] + fn sized_uint_int() { + let x = rc_int_sized(127); + let x2 = rc_uint_sized(127); + let y = rc_int_sized(-127); + let result = x.range_mul(&y).unwrap().maybe_concrete_value().unwrap(); + let result2 = x2.range_mul(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result, result2); + assert_eq!(result.val, Concrete::Int(8, I256::from(-128i32))); + } + + #[test] + fn sized_wrapping_int_int() { + let x = RangeConcrete::new(Concrete::Int(8, I256::from(-127i32)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(8, I256::from(-127i32)), Loc::Implicit); + let result = x + .range_wrapping_mul(&y) + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, Concrete::Int(8, I256::from(1i32))); + } + + #[test] + fn sized_wrapping_int_uint() { + let x = RangeConcrete::new(Concrete::Int(8, I256::from(-127i32)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(8, I256::from(127i32)), Loc::Implicit); + let result = x + .range_wrapping_mul(&y) + .unwrap() + .maybe_concrete_value() + .unwrap(); + let y2 = RangeConcrete::new(Concrete::Uint(8, U256::from(127i32)), Loc::Implicit); + let result2 = x + .range_wrapping_mul(&y2) + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result, result2); + assert_eq!(result.val, Concrete::Int(8, I256::from(-1i32))); + } + + #[test] + fn exec_sized_uint_uint_saturating() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_uint_sized(5).into(); + let lhs_max = rc_uint_sized(15).into(); + let rhs_min = rc_uint_sized(1).into(); + let rhs_max = rc_uint_sized(20).into(); + + let max_result = exec_mul( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, true, false, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Uint(8, U256::from(255))); + let min_result = exec_mul( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, false, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Uint(8, U256::from(5))); + } + + #[test] + fn exec_sized_wrapping_uint_uint_no_overflow() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_uint_sized(5).into(); + let lhs_max = rc_uint_sized(15).into(); + let rhs_min = rc_uint_sized(1).into(); + let rhs_max = rc_uint_sized(16).into(); + + let max_result = exec_mul( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, true, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Uint(8, U256::from(240))); + let min_result = exec_mul( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Uint(8, U256::from(5))); + } + + #[test] + fn exec_sized_wrapping_uint_uint_full_overflow() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_uint_sized(126).into(); + let lhs_max = rc_uint_sized(127).into(); + let rhs_min = rc_uint_sized(252).into(); + let rhs_max = rc_uint_sized(255).into(); + + let max_result = exec_mul( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, true, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + // we just have to overestimate + assert_eq!(max_result.val, Concrete::Uint(8, U256::from(255))); + let min_result = exec_mul( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + // we just have to underestimate + assert_eq!(min_result.val, Concrete::Uint(8, U256::from(0))); + } + + #[test] + fn exec_sized_wrapping_int_uint_cond_overflow() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_int_sized(-128).into(); + let lhs_max = rc_int_sized(127).into(); + let rhs_min = rc_uint_sized(0).into(); + let rhs_max = rc_uint_sized(255).into(); + + let max_result = exec_mul( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, true, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Int(8, I256::from(127i32))); + let min_result = exec_mul( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Int(8, I256::from(-128i32))); + } + + #[test] + fn exec_sized_wrapping_int_uint_no_overflow() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_int_sized(-5).into(); + let lhs_max = rc_int_sized(5).into(); + let rhs_min = rc_uint_sized(0).into(); + let rhs_max = rc_uint_sized(3).into(); + + let max_result = exec_mul( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, true, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Int(8, I256::from(15i32))); + let min_result = exec_mul( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Int(8, I256::from(-15i32))); + } +} diff --git a/crates/graph/src/range/exec/math_ops/sub.rs b/crates/graph/src/range/exec/math_ops/sub.rs new file mode 100644 index 00000000..acc5e1d1 --- /dev/null +++ b/crates/graph/src/range/exec/math_ops/sub.rs @@ -0,0 +1,567 @@ +use crate::nodes::Concrete; +use crate::range::{elem::*, exec_traits::*}; +use crate::GraphBackend; + +use shared::RangeArena; + +use ethers_core::types::{I256, U256}; +use solang_parser::pt::Loc; + +impl RangeSub for RangeConcrete { + fn range_sub(&self, other: &Self) -> Option> { + match (self.val.into_u256(), other.val.into_u256()) { + (Some(lhs_val), Some(rhs_val)) => { + if lhs_val > rhs_val { + let op_res = lhs_val.saturating_sub(rhs_val); + let val = self.val.u256_as_original(op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } else { + match self.val { + Concrete::Int(size, val) => { + let min_int = + Concrete::min_of_type(&self.val).unwrap().int_val().unwrap(); + let op_res = val.saturating_sub(I256::from_raw(rhs_val)).max(min_int); + let val = Concrete::Int(size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + _ => { + // TODO: this should cause a revert + let op_res = lhs_val.saturating_sub(rhs_val); + let val = self.val.u256_as_original(op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + } + } + } + _ => match (&self.val, &other.val) { + (Concrete::Uint(lhs_size, val), Concrete::Int(_, neg_v)) => { + let tmp = Concrete::Uint(*lhs_size, U256::zero()); + let max = Concrete::max_of_type(&tmp).unwrap().uint_val().unwrap(); + let abs = neg_v.unsigned_abs(); + let op_res = val.saturating_add(abs).min(max); + let val = self.val.u256_as_original(op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + (Concrete::Int(lhs_size, neg_v), Concrete::Uint(_, val)) => { + let tmp = Concrete::Int(*lhs_size, I256::from(0i32)); + let min = Concrete::min_of_type(&tmp).unwrap().int_val().unwrap(); + + let op_res = neg_v.saturating_sub(I256::from_raw(*val).max(min)); + let val = Concrete::Int(*lhs_size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + (Concrete::Int(lhs_size, l), Concrete::Int(_rhs_size, r)) => { + let tmp = Concrete::Int(*lhs_size, I256::from(0i32)); + let min = Concrete::min_of_type(&tmp).unwrap().int_val().unwrap(); + let max = Concrete::max_of_type(&tmp).unwrap().int_val().unwrap(); + + let op_res = l.saturating_sub(*r).max(min).min(max); + let val = Concrete::Int(*lhs_size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + _ => None, + }, + } + } + + fn range_wrapping_sub(&self, other: &Self) -> Option> { + match (self.val.into_u256(), other.val.into_u256()) { + (Some(lhs_val), Some(rhs_val)) => { + if lhs_val > rhs_val { + let op_res = lhs_val.overflowing_sub(rhs_val).0; + let val = self.val.u256_as_original(op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } else { + match self.val { + Concrete::Int(size, val) => { + let op_res = val.overflowing_sub(I256::from_raw(rhs_val)).0; + let val = Concrete::Int(size, op_res).size_wrap(); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + _ => { + let op_res = lhs_val.overflowing_sub(rhs_val).0; + let val = self.val.u256_as_original(op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + } + } + } + _ => match (&self.val, &other.val) { + (Concrete::Uint(_lhs_size, val), Concrete::Int(_, neg_v)) => { + let op_res = val.overflowing_add(neg_v.into_raw()).0; + let val = self.val.u256_as_original(op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + (Concrete::Int(lhs_size, neg_v), Concrete::Uint(_, val)) => { + let op_res = I256::from_raw(neg_v.into_raw().overflowing_sub(*val).0); + let val = Concrete::Int(*lhs_size, op_res).size_wrap(); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } + (Concrete::Int(lhs_size, l), Concrete::Int(_rhs_size, r)) => { + Some(Elem::Concrete(RangeConcrete::new( + Concrete::Int(*lhs_size, l.overflowing_sub(*r).0).size_wrap(), + self.loc, + ))) + } + _ => None, + }, + } + } +} + +impl RangeSub for Elem { + fn range_sub(&self, other: &Self) -> Option> { + match (self, other) { + (_, Elem::Concrete(b)) if b.val.into_u256() == Some(U256::zero()) => Some(self.clone()), + (Elem::Concrete(a), Elem::Concrete(b)) => a.range_sub(b), + _ => None, + } + } + + fn range_wrapping_sub(&self, other: &Self) -> Option> { + match (self, other) { + (_, Elem::Concrete(b)) if b.val.into_u256() == Some(U256::zero()) => Some(self.clone()), + (Elem::Concrete(a), Elem::Concrete(b)) => a.range_wrapping_sub(b), + _ => None, + } + } +} + +/// Executes an subtraction given the minimum and maximum of each element. It returns either the _minimum_ bound or _maximum_ bound of the operation. +/// +/// ### Explanation +/// A fact about subtraction is that the largest value possible (in unbounded integer space), is between the left hand side _maximum_ and right hand side _minimum_ and the smallest +/// is between the left hand side _minimum_ and right hand side _maximum_ values. This fact is used in normal "unbounded" (really, saturating) subtraction calculations as well as wrapping subtraction as basis for another fact: +/// +/// In wrapping subtraction, if the bounds allow for optionally wrapping (e.g.: maximum - minimum does not wrap, but minimum - maximum does wrap), we can +/// by extension include *both* the type's maximum and minimum. +/// +/// For example, assume: +///uint256 x: [101, 2256-1] +///uint256 y: [100, 2256-1] +///unchecked { x - y } +/// +/// +/// In this subtraction of `x - y`, `101 - 100` does not wrap, but `101 - 102` does (unsigned integer). We can construct a value of x and y such that +/// the result of `x - y` is equal to 2256-1 (`101 - 102`) or `0` (`101 - 101`). Therefore, the new bounds +/// on `unchecked { x - y }` is [0, 2256-1]. +/// +/// ### Note +/// Signed integers use 2's complement representation so the maximum is 2size - 1 - 1, while unsigned integers are 2size - 1 +/// +/// +/// ### Truth Tables +/// Truth table for `checked sub` operation: +/// +///| Sub | Uint | Int | BytesX | Address | Bytes | String | +///|-----------------|----------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|--------|---------|-------|--------| +///| **Uint** | _min_: lhsmin - rhsmax
_max_: lhsmax - rhsmin | _min_: lhsmin - rhsmax
_max_: lhsmax - rhsmin | N/A | N/A | N/A | N/A | +///| **Int** | _min_: lhsmin - rhsmax
_max_: lhsmax - rhsmin | _min_: lhsmin - rhsmax
_max_: lhsmax - rhsmin | N/A | N/A | N/A | N/A | +///| **BytesX** | N/A | N/A | N/A | N/A | N/A | N/A | +///| **Address** | N/A | N/A | N/A | N/A | N/A | N/A | +///| **Bytes** | N/A | N/A | N/A | N/A | N/A | N/A | +///| **String** | N/A | N/A | N/A | N/A | N/A | N/A | +/// +/// Truth table for `wrapping sub` operation: +/// +///| Wrapping Sub | Uint | Int | BytesX | Address | Bytes | String | +///|---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|--------|---------|-------|--------| +///| **Uint** | _min_: {0, lhsmin - rhsmax}
_max_: {2size - 1, lhsmax - rhsmin} | _min_: {0, lhsmin - rhsmax}
_max_: {2size - 1, lhsmax - rhsmin} | N/A | N/A | N/A | N/A | +///| **Int** | _min_: {-2size-1, lhsmin - rhsmax}
_max_: {2size-1 - 1, lhsmax - rhsmin} | _min_: {-2size-1, lhsmin - rhsmax}
_max_: {2size-1 - 1, lhsmax - rhsmin} | N/A | N/A | N/A | N/A | +///| **BytesX** | N/A | N/A | N/A | N/A | N/A | N/A | +///| **Address** | N/A | N/A | N/A | N/A | N/A | N/A | +///| **Bytes** | N/A | N/A | N/A | N/A | N/A | N/A | +///| **String** | N/A | N/A | N/A | N/A | N/A | N/A | +pub fn exec_sub( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, + wrapping: bool, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + // quick check if rhs is const and zero, if so return min or max + if wrapping { + let mut candidates = vec![]; + let mut all_overflowed = true; + let mut one_overflowed = false; + let zero = Elem::Concrete(RangeConcrete::new( + Concrete::from(U256::zero()), + Loc::Implicit, + )); + let add_candidate = |lhs: &Elem, + rhs: &Elem, + candidates: &mut Vec>, + all_overflowed: &mut bool, + one_overflowed: &mut bool, + arena: &mut RangeArena>| { + if let Some(c) = lhs.range_wrapping_sub(rhs) { + let lhs_neg = matches!(lhs.range_ord(&zero, arena), Some(std::cmp::Ordering::Less)); + let rhs_neg = matches!(rhs.range_ord(&zero, arena), Some(std::cmp::Ordering::Less)); + let signed = lhs_neg || rhs_neg; + + let overflowed = if signed { + // signed safemath: (rhs >= 0 && c <= lhs) || (rhs < 0 && c > lhs) ==> no overflowed --invert-> overflowed + // ( rhs < 0 ∣∣ c > lhs) && ( rhs ≥ 0 ∣∣ c ≤ lhs) + + (rhs_neg + || matches!(c.range_ord(lhs, arena), Some(std::cmp::Ordering::Greater))) + && (!rhs_neg + || matches!( + c.range_ord(lhs, arena), + Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) + )) + } else { + // unsigned safemath: c < a ==> overflowed + matches!(c.range_ord(lhs, arena), Some(std::cmp::Ordering::Greater)) + }; + + if *all_overflowed && !overflowed { + *all_overflowed = false; + } + + if !*one_overflowed && overflowed { + *one_overflowed = true; + } + + candidates.push(c); + } + }; + + add_candidate( + lhs_min, + rhs_min, + &mut candidates, + &mut all_overflowed, + &mut one_overflowed, + arena, + ); + add_candidate( + lhs_min, + rhs_max, + &mut candidates, + &mut all_overflowed, + &mut one_overflowed, + arena, + ); + add_candidate( + lhs_max, + rhs_min, + &mut candidates, + &mut all_overflowed, + &mut one_overflowed, + arena, + ); + add_candidate( + lhs_max, + rhs_max, + &mut candidates, + &mut all_overflowed, + &mut one_overflowed, + arena, + ); + + // If we have a conditional overflow, add the min and max of the type of lhs to the candidates + if !all_overflowed && one_overflowed { + let add_extremes = |elem: &Elem, candidates: &mut Vec>| { + if let Some(c) = elem.maybe_concrete() { + if let Some(max) = Concrete::max_of_type(&c.val) { + candidates.push(RangeConcrete::new(max, c.loc).into()); + } + + if let Some(min) = Concrete::min_of_type(&c.val) { + candidates.push(RangeConcrete::new(min, c.loc).into()); + } + } + }; + + add_extremes(lhs_min, &mut candidates); + add_extremes(lhs_max, &mut candidates); + } + + candidates.sort_by(|a, b| match a.range_ord(b, arena) { + Some(r) => r, + _ => std::cmp::Ordering::Less, + }); + + if candidates.is_empty() { + return None; + } + + if maximize { + Some(candidates.remove(candidates.len() - 1)) + } else { + Some(candidates.remove(0)) + } + } else if maximize { + // if we are maximizing, the largest value will always just be the the largest value - the smallest value + lhs_max.range_sub(rhs_min) + } else { + // if we are minimizing, the smallest value will always be smallest value - largest value + lhs_min.range_sub(rhs_max) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::DummyGraph; + use ethers_core::types::U256; + use solang_parser::pt::Loc; + + #[test] + fn uint_uint() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(15)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(256, U256::from(5)), Loc::Implicit); + let result = x.range_sub(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::from(10))); + } + + #[test] + fn saturating_uint_uint() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(1)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(256, U256::MAX), Loc::Implicit); + let result = x.range_sub(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::zero())); + } + + #[test] + fn sized_saturating_uint_uint() { + let x = RangeConcrete::new(Concrete::Uint(8, U256::from(254)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(8, U256::from(255)), Loc::Implicit); + let result = x.range_sub(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(8, U256::zero())); + } + + #[test] + fn int_big_uint() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(15)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(-1i32)), Loc::Implicit); + let result = x.range_sub(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::from(16))); + } + + #[test] + fn big_int_uint() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::from(1)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(-15i32)), Loc::Implicit); + let result = x.range_sub(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::from(16))); + } + + #[test] + fn int_int() { + let x = RangeConcrete::new(Concrete::Int(256, I256::from(-15i32)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(-15i32)), Loc::Implicit); + let result = x.range_sub(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::from(0i32))); + } + + #[test] + fn max_int_int() { + let x = RangeConcrete::new(Concrete::Int(256, I256::MAX), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::MIN), Loc::Implicit); + let result = x.range_sub(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::MAX)); + } + + #[test] + fn int_max_int() { + let x = RangeConcrete::new(Concrete::Int(256, I256::MIN), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::MAX), Loc::Implicit); + let result = x.range_sub(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::MIN)); + } + + #[test] + fn saturating_int_int() { + let x = RangeConcrete::new( + Concrete::Int(256, I256::MIN + I256::from(1i32)), + Loc::Implicit, + ); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(2i32)), Loc::Implicit); + let result = x.range_sub(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::MIN)); + } + + #[test] + fn sized_saturating_int_int() { + let x = RangeConcrete::new(Concrete::Int(8, I256::from(-127i32)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(8, I256::from(2i32)), Loc::Implicit); + let result = x.range_sub(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, Concrete::Int(8, I256::from(-128i32))); + } + + #[test] + fn wrapping_uint_uint() { + let x = RangeConcrete::new(Concrete::Uint(256, U256::zero()), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(256, U256::from(1)), Loc::Implicit); + let result = x + .range_wrapping_sub(&y) + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::MAX)); + } + + #[test] + fn sized_wrapping_uint_uint() { + let x = RangeConcrete::new(Concrete::Uint(8, U256::zero()), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Uint(8, U256::from(1)), Loc::Implicit); + let result = x + .range_wrapping_sub(&y) + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, Concrete::Uint(8, U256::from(255))); + } + + #[test] + fn wrapping_int_int() { + let x = RangeConcrete::new(Concrete::Int(256, I256::from(-1)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(15i32)), Loc::Implicit); + let result = x + .range_wrapping_sub(&y) + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::from(-16i32))); + } + + #[test] + fn wrapping_int_int_2() { + let x = RangeConcrete::new(Concrete::Int(256, I256::MIN), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(256, I256::from(1i32)), Loc::Implicit); + let result = x + .range_wrapping_sub(&y) + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, Concrete::Int(256, I256::MAX)); + } + + #[test] + fn sized_wrapping_int_int() { + let x = RangeConcrete::new(Concrete::Int(8, I256::from(-128i32)), Loc::Implicit); + let y = RangeConcrete::new(Concrete::Int(8, I256::from(1i32)), Loc::Implicit); + let result = x + .range_wrapping_sub(&y) + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, Concrete::Int(8, I256::from(127i32))); + } + + #[test] + fn exec_sized_uint_uint_saturating() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_uint_sized(105).into(); + let lhs_max = rc_uint_sized(150).into(); + let rhs_min = rc_uint_sized(10).into(); + let rhs_max = rc_uint_sized(200).into(); + + let max_result = exec_sub( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, true, false, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Uint(8, U256::from(140))); + let min_result = exec_sub( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, false, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Uint(8, U256::from(0))); + } + + #[test] + fn exec_sized_wrapping_uint_uint() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_uint_sized(105).into(); + let lhs_max = rc_uint_sized(150).into(); + let rhs_min = rc_uint_sized(10).into(); + let rhs_max = rc_uint_sized(200).into(); + + let max_result = exec_sub( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, true, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Uint(8, U256::from(255))); + let min_result = exec_sub( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Uint(8, U256::from(0))); + } + + #[test] + fn exec_sized_wrapping_int_uint() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_int_sized(-128).into(); + let lhs_max = rc_int_sized(127).into(); + let rhs_min = rc_uint_sized(0).into(); + let rhs_max = rc_uint_sized(255).into(); + + let max_result = exec_sub( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, true, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Int(8, I256::from(127i32))); + let min_result = exec_sub( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Int(8, I256::from(-128i32))); + } + + #[test] + fn exec_sized_wrapping_int_int_max() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let lhs_min = rc_int_sized(-128).into(); + let lhs_max = rc_int_sized(-100).into(); + let rhs_min = rc_int_sized(-5).into(); + let rhs_max = rc_int_sized(5).into(); + + let max_result = exec_sub( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, true, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(max_result.val, Concrete::Int(8, I256::from(127i32))); + let min_result = exec_sub( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, true, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); + assert_eq!(min_result.val, Concrete::Int(8, I256::from(-128i32))); + } +} diff --git a/crates/graph/src/range/exec/max.rs b/crates/graph/src/range/exec/max.rs index 74f1d62c..b2b32de6 100644 --- a/crates/graph/src/range/exec/max.rs +++ b/crates/graph/src/range/exec/max.rs @@ -1,31 +1,29 @@ use crate::nodes::Concrete; use crate::range::{elem::*, exec_traits::*}; +use crate::GraphBackend; + +use shared::RangeArena; impl RangeMax for RangeConcrete { fn range_max(&self, other: &Self) -> Option> { match (self.val.into_u256(), other.val.into_u256()) { - (Some(lhs_val), Some(rhs_val)) => Some(Elem::Concrete(RangeConcrete { - val: self.val.u256_as_original(lhs_val.max(rhs_val)), - loc: self.loc, - })), + (Some(lhs_val), Some(rhs_val)) => { + let op_res = lhs_val.max(rhs_val); + let val = self.val.u256_as_original(op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } _ => match (&self.val, &other.val) { - (Concrete::Uint(lhs_size, val), Concrete::Int(_, _)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Uint(*lhs_size, *val), - loc: self.loc, - })) - } - (Concrete::Int(lhs_size, _), Concrete::Uint(_, val)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Uint(*lhs_size, *val), - loc: self.loc, - })) + (Concrete::Uint(lhs_size, val), Concrete::Int(_, _)) + | (Concrete::Int(lhs_size, _), Concrete::Uint(_, val)) => { + let val = Concrete::Uint(*lhs_size, *val); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) } (Concrete::Int(lhs_size, l), Concrete::Int(_rhs_size, r)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, *l.max(r)), - loc: self.loc, - })) + let val = Concrete::Int(*lhs_size, *l.max(r)); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) } _ => None, }, @@ -37,18 +35,48 @@ impl RangeMax for Elem { fn range_max(&self, other: &Self) -> Option> { match (self, other) { (Elem::Concrete(a), Elem::Concrete(b)) => a.range_max(b), - (Elem::ConcreteDyn(a), Elem::ConcreteDyn(b)) => { - if a.op_num > b.op_num { - Some(self.clone()) - } else if a.op_num < b.op_num { - Some(other.clone()) - } else { - None - } - } + (Elem::ConcreteDyn(a), Elem::ConcreteDyn(b)) => match a.op_num.cmp(&b.op_num) { + std::cmp::Ordering::Greater => Some(self.clone()), + std::cmp::Ordering::Less => Some(other.clone()), + _ => None, + }, (_, Elem::Null) => Some(self.clone()), (Elem::Null, _) => Some(other.clone()), _ => None, } } } + +/// Executes the maximum given the minimum and maximum of each element. It returns either the _minimum_ bound or _maximum_ bound +/// of the operation. +pub fn exec_max( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, + _analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + let candidates = vec![ + lhs_min.range_max(rhs_min), + lhs_min.range_max(rhs_max), + lhs_max.range_max(rhs_min), + lhs_max.range_max(rhs_max), + ]; + let mut candidates = candidates.into_iter().flatten().collect::>(); + candidates.sort_by(|a, b| match a.range_ord(b, arena) { + Some(r) => r, + _ => std::cmp::Ordering::Less, + }); + + if candidates.is_empty() { + return None; + } + + if maximize { + Some(candidates.remove(candidates.len() - 1)) + } else { + Some(candidates.remove(0)) + } +} diff --git a/crates/graph/src/range/exec/mem_ops/concat.rs b/crates/graph/src/range/exec/mem_ops/concat.rs index e28dd4f3..3abf3bef 100644 --- a/crates/graph/src/range/exec/mem_ops/concat.rs +++ b/crates/graph/src/range/exec/mem_ops/concat.rs @@ -1,35 +1,26 @@ use crate::nodes::Concrete; use crate::range::{elem::*, exec_traits::*}; +use crate::GraphBackend; + +use shared::RangeArena; use ethers_core::types::{H256, U256}; use std::collections::BTreeMap; impl RangeConcat for RangeConcrete { fn range_concat(&self, other: &Self) -> Option> { - Some(Elem::Concrete(RangeConcrete { - val: self.val.clone().concat(&other.val)?, - loc: self.loc, - })) + Some(Elem::Concrete(RangeConcrete::new( + self.val.clone().concat(&other.val)?, + self.loc, + ))) } } impl RangeConcat> for RangeDyn { fn range_concat(&self, other: &RangeConcrete) -> Option> { - match (other.val.clone(), self.val.iter().take(1).next()) { - ( - Concrete::DynBytes(val), - Some(( - _, - ( - Elem::Concrete(RangeConcrete { - val: Concrete::Bytes(..), - .. - }), - _, - ), - )), - ) - | (Concrete::DynBytes(val), None) => { + let inner = self.val.values().take(1).next().map(|(a, _)| a); + match (other.val.clone(), inner) { + (Concrete::DynBytes(val), inner) if inner.is_none() || inner.unwrap().is_bytes() => { let last = self.len.clone(); let mut existing = self.val.clone(); let new = val @@ -41,30 +32,19 @@ impl RangeConcat> for RangeDyn { let mut bytes = [0x00; 32]; bytes[0] = *v; let v = Elem::from(Concrete::Bytes(1, H256::from(bytes))); - (idx, (v, self.op_num + i)) + (idx, (v, self.op_num + i + 1)) }) .collect::>(); existing.extend(new); Some(Elem::ConcreteDyn(RangeDyn::new_w_op_nums( - Elem::from(Concrete::from(U256::from(val.len()))), + (*self.len).clone() + Elem::from(Concrete::from(U256::from(val.len()))), existing, other.loc, ))) } - ( - Concrete::String(val), - Some(( - _, - ( - Elem::Concrete(RangeConcrete { - val: Concrete::String(..), - .. - }), - _, - ), - )), - ) - | (Concrete::String(val), None) => { + (Concrete::String(val), inner) + if inner.is_none() || inner.unwrap().is_string() || inner.unwrap().is_bytes() => + { let last = self.len.clone(); let mut existing = self.val.clone(); let new = val @@ -76,48 +56,30 @@ impl RangeConcat> for RangeDyn { let mut bytes = [0x00; 32]; v.encode_utf8(&mut bytes[..]); let v = Elem::from(Concrete::Bytes(1, H256::from(bytes))); - (idx, (v, self.op_num + i)) + (idx, (v, self.op_num + i + 1)) }) .collect::>(); existing.extend(new); Some(Elem::ConcreteDyn(RangeDyn::new_w_op_nums( - Elem::from(Concrete::from(U256::from(val.len()))), + (*self.len).clone() + Elem::from(Concrete::from(U256::from(val.len()))), existing, other.loc, ))) } - _e => None, + e => { + debug_assert!(false, "was not concattable type: {e:#?}"); + None + } } } } impl RangeConcat> for RangeDyn { fn range_concat(&self, other: &Self) -> Option> { - let val: Option<(_, &(Elem, _))> = self.val.iter().take(1).next(); - let o_val: Option<(_, &(Elem, _))> = other.val.iter().take(1).next(); + let val = self.val.values().take(1).next().map(|(a, _)| a); + let o_val = other.val.values().take(1).next().map(|(a, _)| a); match (val, o_val) { - ( - Some(( - _, - &( - Elem::Concrete(RangeConcrete { - val: Concrete::Bytes(..), - .. - }), - _, - ), - )), - Some(( - _, - &( - Elem::Concrete(RangeConcrete { - val: Concrete::Bytes(..), - .. - }), - _, - ), - )), - ) => { + (Some(v), Some(o)) if v.is_bytes() && o.is_bytes() => { let last = self.len.clone(); let mut existing = self.val.clone(); let other_vals = other @@ -125,23 +87,19 @@ impl RangeConcat> for RangeDyn { .clone() .into_iter() .enumerate() - .map(|(i, (key, (v, _op)))| (key + *last.clone(), (v, self.op_num + i))) + .map(|(i, (key, (v, _op)))| (key + *last.clone(), (v, self.op_num + i + 1))) .collect::>(); existing.extend(other_vals); Some(Elem::ConcreteDyn(RangeDyn::new_w_op_nums( - *self.len.clone() - + *other - .len - .clone() - .max(Box::new(Elem::from(Concrete::from(U256::from(1))))), + *self.len.clone() + *other.len.clone(), existing, other.loc, ))) } - (Some((_, (l @ Elem::Reference(_), _))), None) => Some(l.clone()), - (None, Some((_, (r @ Elem::Reference(_), _)))) => Some(r.clone()), + (Some(l @ Elem::Reference(_)), None) => Some(l.clone()), + (None, Some(r @ Elem::Reference(_))) => Some(r.clone()), (None, None) => Some(Elem::ConcreteDyn(self.clone())), _e => None, } @@ -159,3 +117,202 @@ impl RangeConcat for Elem { } } } + +/// Executes a concatenation of bytes. +pub fn exec_concat( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, + _analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + // TODO: improve with smarter stuff + let candidates = vec![ + lhs_min.range_concat(rhs_min), + lhs_min.range_concat(rhs_max), + lhs_max.range_concat(rhs_min), + lhs_max.range_concat(rhs_max), + ]; + let mut candidates = candidates.into_iter().flatten().collect::>(); + candidates.sort_by(|a, b| match a.range_ord(b, arena) { + Some(r) => r, + _ => std::cmp::Ordering::Less, + }); + + if candidates.is_empty() { + return None; + } + + if maximize { + Some(candidates.remove(candidates.len() - 1)) + } else { + Some(candidates.remove(0)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::DummyGraph; + use pretty_assertions::assert_eq; + use solang_parser::pt::Loc; + + #[test] + fn concrete_concrete_bytes() { + let x = RangeConcrete::new( + Concrete::from(vec![b'h', b'e', b'l', b'l', b'o']), + Loc::Implicit, + ); + let y = RangeConcrete::new( + Concrete::from(vec![b'w', b'o', b'r', b'l', b'd']), + Loc::Implicit, + ); + let expected = Concrete::from(vec![ + b'h', b'e', b'l', b'l', b'o', b'w', b'o', b'r', b'l', b'd', + ]); + let result = x.range_concat(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, expected); + } + + #[test] + fn concrete_concrete_bytes_str_fail() { + let x = RangeConcrete::new( + Concrete::from(vec![b'h', b'e', b'l', b'l', b'o']), + Loc::Implicit, + ); + let y = RangeConcrete::new(Concrete::from("world"), Loc::Implicit); + assert!(x.range_concat(&y).is_none()); + } + + #[test] + fn concrete_concrete_bytes_none() { + let x = RangeConcrete::new( + Concrete::from(vec![b'h', b'e', b'l', b'l', b'o']), + Loc::Implicit, + ); + let y = RangeConcrete::new(Concrete::DynBytes(vec![]), Loc::Implicit); + let result = x.range_concat(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, x.val); + } + + #[test] + fn concrete_concrete_str() { + let x = RangeConcrete::new(Concrete::from("hello"), Loc::Implicit); + let y = RangeConcrete::new(Concrete::from("world"), Loc::Implicit); + let expected = Concrete::from("helloworld"); + let result = x.range_concat(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, expected); + } + + #[test] + fn concrete_concrete_str_bytes_fail() { + let x = RangeConcrete::new(Concrete::from("world"), Loc::Implicit); + let y = RangeConcrete::new( + Concrete::from(vec![b'h', b'e', b'l', b'l', b'o']), + Loc::Implicit, + ); + assert!(x.range_concat(&y).is_none()); + } + + #[test] + fn concrete_concrete_str_none() { + let x = RangeConcrete::new(Concrete::from("hello"), Loc::Implicit); + let y = RangeConcrete::new(Concrete::from(""), Loc::Implicit); + let result = x.range_concat(&y).unwrap().maybe_concrete_value().unwrap(); + assert_eq!(result.val, x.val); + } + + #[test] + fn dyn_concrete_bytes() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let x = RangeDyn::from_concrete( + Concrete::from(vec![b'h', b'e', b'l', b'l', b'o']), + Loc::Implicit, + ) + .unwrap(); + let y = RangeConcrete::new( + Concrete::from(vec![b'w', b'o', b'r', b'l', b'd']), + Loc::Implicit, + ); + let expected: Elem<_> = Elem::ConcreteDyn( + RangeDyn::from_concrete( + Concrete::from(vec![ + b'h', b'e', b'l', b'l', b'o', b'w', b'o', b'r', b'l', b'd', + ]), + Loc::Implicit, + ) + .unwrap(), + ); + let result = x + .range_concat(&y) + .unwrap() + .maximize(&g, &mut arena) + .unwrap(); + assert_eq!(result, expected); + } + + #[test] + fn dyn_dyn_bytes() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let x = RangeDyn::from_concrete( + Concrete::from(vec![b'h', b'e', b'l', b'l', b'o']), + Loc::Implicit, + ) + .unwrap(); + let y = RangeDyn::from_concrete( + Concrete::from(vec![b'w', b'o', b'r', b'l', b'd']), + Loc::Implicit, + ) + .unwrap(); + let expected: Elem<_> = Elem::ConcreteDyn( + RangeDyn::from_concrete( + Concrete::from(vec![ + b'h', b'e', b'l', b'l', b'o', b'w', b'o', b'r', b'l', b'd', + ]), + Loc::Implicit, + ) + .unwrap(), + ); + let result = x + .range_concat(&y) + .unwrap() + .maximize(&g, &mut arena) + .unwrap(); + assert_eq!(result, expected); + } + + #[test] + fn dyn_concrete_str() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let x = RangeDyn::from_concrete(Concrete::from("hello"), Loc::Implicit).unwrap(); + let y = RangeConcrete::new(Concrete::from("world"), Loc::Implicit); + let expected: Elem<_> = Elem::ConcreteDyn( + RangeDyn::from_concrete(Concrete::from("helloworld"), Loc::Implicit).unwrap(), + ); + let result = x.range_concat(&y).unwrap(); + let result = result.maximize(&g, &mut arena).unwrap(); + assert_eq!(result, expected); + } + + #[test] + fn dyn_dyn_str() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let x = RangeDyn::from_concrete(Concrete::from("hello"), Loc::Implicit).unwrap(); + let y = RangeDyn::from_concrete(Concrete::from("world"), Loc::Implicit).unwrap(); + let expected: Elem<_> = Elem::ConcreteDyn( + RangeDyn::from_concrete(Concrete::from("helloworld"), Loc::Implicit).unwrap(), + ); + let result = x + .range_concat(&y) + .unwrap() + .maximize(&g, &mut arena) + .unwrap(); + assert_eq!(result, expected); + } +} diff --git a/crates/graph/src/range/exec/mem_ops/mem_get.rs b/crates/graph/src/range/exec/mem_ops/mem_get.rs new file mode 100644 index 00000000..62d3c99e --- /dev/null +++ b/crates/graph/src/range/exec/mem_ops/mem_get.rs @@ -0,0 +1,409 @@ +use crate::GraphBackend; +use crate::{ + nodes::Concrete, + range::{elem::*, exec_traits::*}, +}; + +use shared::RangeArena; + +use ethers_core::types::U256; +use solang_parser::pt::Loc; + +impl RangeMemLen for RangeDyn { + fn range_get_length(&self) -> Option> { + Some(*self.len.clone()) + } +} + +impl> + Clone> RangeMemGet for RangeDyn { + fn range_get_index(&self, index: &Rhs) -> Option> { + self.val + .get(&(index.clone().into())) + .map(|(v, _)| v.clone()) + } +} + +impl RangeMemGet for RangeConcrete { + fn range_get_index(&self, index: &RangeConcrete) -> Option> { + self.val.get_index(&index.val).map(Elem::from) + } +} + +impl RangeMemLen for RangeConcrete { + fn range_get_length(&self) -> Option> { + Some(RangeConcrete::new(Concrete::from(self.val.maybe_array_size()?), self.loc).into()) + } +} + +impl RangeMemLen for Elem { + fn range_get_length(&self) -> Option> { + match self { + Elem::Concrete(a) => a.range_get_length(), + Elem::ConcreteDyn(a) => Some(*a.len.clone()), + _e => None, + } + } +} + +impl RangeMemGet> for Elem { + fn range_get_index(&self, index: &Elem) -> Option> { + match (self, index) { + (Elem::Concrete(a), Elem::Concrete(b)) => a.range_get_index(b), + (Elem::ConcreteDyn(a), idx @ Elem::Concrete(_)) => { + if let Some((val, _)) = a.val.get(idx).cloned() { + Some(val) + } else { + None + } + } + (Elem::ConcreteDyn(a), idx @ Elem::Reference(_)) => { + if let Some((val, _)) = a.val.get(idx).cloned() { + Some(val) + } else { + None + } + } + _e => None, + } + } +} + +/// 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( + lhs_min: &Elem, + lhs_max: &Elem, + maximize: bool, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + if maximize { + let new = lhs_max.clone(); + let new_max = new.simplify_maximize(analyzer, arena).ok()?; + + new_max.range_get_length() + } else { + let new_min = lhs_min.simplify_minimize(analyzer, arena).ok()?; + + new_min.range_get_length() + } +} + +/// Executes the `range_get_index` 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_index( + lhs: &Elem, + rhs: &Elem, + maximize: bool, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + // for each key in LHS, check if it overlaps the RHS index range + // e.g.: + // lhs: { + // [12, 100]: val, + // [220, 1000]: val, + // } + // + // if: + // rhs: [0, 2**224] + // all values would be added to candidates + // + // if: + // rhs: [0, 2] + // No values would be added to candidates + // + // if: + // rhs: [50, 50] + // the first value would be added to candidates + + let mut candidates = vec![]; + fn match_lhs( + lhs: &Elem, + rhs: &Elem, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + candidates: &mut Vec>, + ) { + match lhs { + Elem::Arena(_) => { + let (d, idx) = lhs.dearenaize(arena); + match_lhs(&d, rhs, analyzer, arena, candidates); + lhs.rearenaize(d, idx, arena); + } + Elem::Reference(_) => { + if let Ok(min) = lhs.minimize(analyzer, arena) { + match_lhs(&min, rhs, analyzer, arena, candidates); + } + + if let Ok(max) = lhs.maximize(analyzer, arena) { + match_lhs(&max, rhs, analyzer, arena, candidates); + } + } + Elem::ConcreteDyn(d) => { + d.val.iter().for_each(|(k, (v, _op))| { + if let Ok(Some(true)) = k.overlaps(rhs, true, analyzer, arena) { + candidates.push(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(rhs, true, analyzer, arena) { + if let Some(val) = c.range_get_index(&as_rc) { + candidates.push(val) + } + } + curr += U256::from(1); + } + } + } + _ => {} + }; + } + + match_lhs(lhs, rhs, analyzer, arena, &mut candidates); + + candidates = candidates + .into_iter() + .filter_map(|val| { + if maximize { + val.maximize(analyzer, arena).ok() + } else { + val.minimize(analyzer, arena).ok() + } + }) + .collect(); + + // Sort the candidates + candidates.sort_by(|a, b| match a.range_ord(b, arena) { + Some(r) => r, + _ => std::cmp::Ordering::Less, + }); + + if candidates.is_empty() { + return Some(Elem::Null); + } + + if maximize { + Some(candidates.remove(candidates.len() - 1)) + } else { + Some(candidates.remove(0)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::DummyGraph; + use ethers_core::types::U256; + use pretty_assertions::assert_eq; + use solang_parser::pt::Loc; + + #[test] + fn concrete_len() { + let x: RangeConcrete = RangeConcrete::new( + Concrete::from(vec![b'h', b'e', b'l', b'l', b'o']), + Loc::Implicit, + ); + let expected = rc_uint256(5); + let result = Elem::from(x) + .range_get_length() + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, expected.val); + } + + #[test] + fn dyn_len() { + let x = RangeDyn::from_concrete( + Concrete::from(vec![b'h', b'e', b'l', b'l', b'o']), + Loc::Implicit, + ) + .unwrap(); + let expected = rc_uint256(5); + let result = x + .range_get_length() + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, expected.val); + } + + #[test] + fn concrete_concrete_index() { + let x = RangeConcrete::new( + Concrete::from(vec![b'h', b'e', b'l', b'l', b'o']), + Loc::Implicit, + ); + let idx = rc_uint256(2); + let result = x + .range_get_index(&idx) + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, Concrete::from(b'l')); + } + + #[test] + fn dyn_concrete_index() { + let x = RangeDyn::from_concrete( + Concrete::from(vec![b'h', b'e', b'l', b'l', b'o']), + Loc::Implicit, + ) + .unwrap(); + let idx = rc_uint256(2); + let result = x + .range_get_index(&idx) + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, Concrete::from(b'l')); + } + + #[test] + fn dyn_ref_index() { + let idx = Elem::Reference(Reference::new(1.into())); + let rand: Elem<_> = rc_uint256(0).into(); + let val = rc_uint256(200).into(); + let x = RangeDyn::new_for_indices( + vec![(rand.clone(), rand), (idx.clone(), val)], + Loc::Implicit, + ); + + let result = x + .range_get_index(&idx) + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::from(200))); + } + + #[test] + fn exec_dyn_get_ref_idx_low() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let idx0 = test_reference(1, 12.into(), 100.into()); + let idx1 = test_reference(2, 220.into(), 1000.into()); + let val0 = rc_uint256(200).into(); + let val1 = rc_uint256(201).into(); + let x = RangeDyn::new_for_indices(vec![(idx0, val0), (idx1, val1)], Loc::Implicit); + + let get_idx = test_reference(3, 0.into(), 12.into()); + + let result = exec_get_index(&Elem::ConcreteDyn(x), &get_idx, true, &g, &mut arena) + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::from(200))); + } + + #[test] + fn exec_dyn_get_ref_idx_high() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let idx0 = test_reference(1, 12.into(), 100.into()); + let idx1 = test_reference(2, 220.into(), 1000.into()); + let val0 = rc_uint256(200).into(); + let val1 = rc_uint256(201).into(); + let x = RangeDyn::new_for_indices(vec![(idx0, val0), (idx1, val1)], Loc::Implicit); + + let get_idx = test_reference(3, 400.into(), 400.into()); + + let result = exec_get_index(&Elem::ConcreteDyn(x), &get_idx, true, &g, &mut arena) + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::from(201))); + } + + #[test] + fn exec_dyn_get_ref_idx_all() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let idx0 = test_reference(1, 12.into(), 100.into()); + let idx1 = test_reference(2, 220.into(), 1000.into()); + let val0 = rc_uint256(200).into(); + let val1 = rc_uint256(201).into(); + let x = RangeDyn::new_for_indices(vec![(idx0, val0), (idx1, val1)], Loc::Implicit); + + let get_idx = test_reference(3, 0.into(), U256::MAX); + + let result = exec_get_index(&Elem::ConcreteDyn(x), &get_idx, true, &g, &mut arena) + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, Concrete::Uint(256, U256::from(201))); + } + + #[test] + fn exec_dyn_get_ref_idx_null() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let idx0 = test_reference(1, 12.into(), 100.into()); + let idx1 = test_reference(2, 220.into(), 1000.into()); + let val0 = rc_uint256(200).into(); + let val1 = rc_uint256(201).into(); + let x = RangeDyn::new_for_indices(vec![(idx0, val0), (idx1, val1)], Loc::Implicit); + + let get_idx = test_reference(3, 0.into(), 2.into()); + + let result = exec_get_index(&Elem::ConcreteDyn(x), &get_idx, true, &g, &mut arena); + assert_eq!(result.unwrap(), Elem::Null); + } + + #[test] + fn exec_concrete_get_ref_idx_low() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let x: RangeConcrete = RangeConcrete::new( + Concrete::from(vec![b'h', b'e', b'l', b'l', b'o']), + Loc::Implicit, + ); + let get_idx = test_reference(1, 0.into(), 2.into()); + + let result = exec_get_index(&Elem::Concrete(x), &get_idx, true, &g, &mut arena) + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, Concrete::from(b'l')); + } + + #[test] + fn exec_concrete_get_ref_idx_null() { + let g = DummyGraph::default(); + let mut arena = Default::default(); + let x: RangeConcrete = RangeConcrete::new( + Concrete::from(vec![b'h', b'e', b'l', b'l', b'o']), + Loc::Implicit, + ); + let get_idx = test_reference(1, 6.into(), 8.into()); + + let result = exec_get_index(&Elem::Concrete(x), &get_idx, true, &g, &mut arena); + assert_eq!(result.unwrap(), Elem::Null); + } + + fn test_reference(id: usize, min: U256, max: U256) -> Elem { + let mut re = Reference::new(id.into()); + let mi = Box::new(Elem::Concrete(RangeConcrete::new( + Concrete::from(min), + Loc::Implicit, + ))); + let ma = Box::new(Elem::Concrete(RangeConcrete::new( + Concrete::from(max), + Loc::Implicit, + ))); + re.minimized = Some(MinMaxed::Minimized(mi.clone())); + re.maximized = Some(MinMaxed::Maximized(ma.clone())); + re.flattened_min = Some(mi); + re.flattened_max = Some(ma); + Elem::Reference(re) + } +} diff --git a/crates/graph/src/range/exec/mem_ops/mem_set.rs b/crates/graph/src/range/exec/mem_ops/mem_set.rs index 6fb7028f..acc6ab72 100644 --- a/crates/graph/src/range/exec/mem_ops/mem_set.rs +++ b/crates/graph/src/range/exec/mem_ops/mem_set.rs @@ -1,8 +1,11 @@ +use crate::GraphBackend; use crate::{ nodes::Concrete, range::{elem::*, exec_traits::*}, }; +use shared::RangeArena; + use ethers_core::types::{H256, U256}; use std::collections::BTreeMap; @@ -23,36 +26,20 @@ impl RangeMemSet for RangeDyn { ))) } - fn range_get_index(&self, _index: &Self) -> Option> { - unreachable!() - } - - fn range_set_length(&self, _other: &Self) -> Option> { - unreachable!() - } - - fn range_get_length(&self) -> Option> { - unreachable!() + fn range_set_length(&self, other: &Self) -> Option> { + let mut a = self.clone(); + a.len.clone_from(&other.len); + Some(Elem::ConcreteDyn(a)) } } impl RangeMemSet> for RangeDyn { fn range_set_indices(&self, range: &RangeConcrete) -> Option> { - match (range.val.clone(), self.val.iter().take(1).next()) { - ( - Concrete::DynBytes(val), - Some(( - _, - ( - Elem::Concrete(RangeConcrete { - val: Concrete::Bytes(..), - .. - }), - _, - ), - )), - ) - | (Concrete::DynBytes(val), None) => { + match ( + range.val.clone(), + self.val.values().take(1).next().map(|(a, _)| a), + ) { + (Concrete::DynBytes(val), s) if s.is_none() || s.unwrap().is_bytes() => { let mut existing = self.val.clone(); let new = val .iter() @@ -74,20 +61,7 @@ impl RangeMemSet> for RangeDyn { range.loc, ))) } - ( - Concrete::String(val), - Some(( - _, - ( - Elem::Concrete(RangeConcrete { - val: Concrete::String(..), - .. - }), - _, - ), - )), - ) - | (Concrete::String(val), None) => { + (Concrete::String(val), s) if s.is_none() || s.unwrap().is_string() => { let mut existing = self.val.clone(); let new = val .chars() @@ -113,16 +87,10 @@ impl RangeMemSet> for RangeDyn { } } - fn range_get_index(&self, _index: &RangeConcrete) -> Option> { - unreachable!() - } - - fn range_set_length(&self, _other: &RangeConcrete) -> Option> { - unreachable!() - } - - fn range_get_length(&self) -> Option> { - unreachable!() + fn range_set_length(&self, other: &RangeConcrete) -> Option> { + let mut a = self.clone(); + a.len = Box::new(Elem::Concrete(other.clone())); + Some(Elem::ConcreteDyn(a)) } } @@ -130,22 +98,89 @@ impl RangeMemSet for RangeConcrete { fn range_set_indices(&self, range: &Self) -> Option> { let mut new_val = self.val.clone(); new_val.set_indices(&range.val); - Some(Elem::Concrete(RangeConcrete { - val: new_val, - loc: range.loc, - })) - } - - fn range_get_index(&self, index: &Self) -> Option> { - self.val.get_index(&index.val).map(Elem::from) - } - - fn range_set_length(&self, _other: &Self) -> Option> { - unreachable!() + Some(Elem::Concrete(RangeConcrete::new(new_val, range.loc))) } - fn range_get_length(&self) -> Option> { - unreachable!() + fn range_set_length(&self, other: &Self) -> Option> { + match other.val.into_u256() { + Some(len) if len <= U256::from(32) => match self.val { + Concrete::DynBytes(ref val) => Some(Elem::Concrete(RangeConcrete::new( + Concrete::DynBytes({ + let mut v = val.clone(); + v.resize(len.as_usize(), 0); + v + }), + self.loc, + ))), + Concrete::String(ref val) => Some(Elem::Concrete(RangeConcrete::new( + Concrete::String({ + let mut v = val.clone(); + v.push_str(&" ".repeat(len.as_usize() - v.chars().count())); + v + }), + self.loc, + ))), + Concrete::Bytes(_, val) => Some(Elem::Concrete(RangeConcrete::new( + Concrete::Bytes(len.as_u32() as u8, val), + self.loc, + ))), + _ => None, + }, + _ => { + let new = match self.val { + Concrete::DynBytes(ref val) => Some( + val.iter() + .enumerate() + .map(|(i, v)| { + let mut bytes = [0x00; 32]; + bytes[0] = *v; + let v = Elem::from(Concrete::Bytes(1, H256::from(bytes))); + (Elem::from(Concrete::from(U256::from(i))), (v, i)) + }) + .collect::>(), + ), + Concrete::String(ref val) => Some( + val.chars() + .enumerate() + .map(|(i, v)| { + let mut bytes = [0x00; 32]; + v.encode_utf8(&mut bytes[..]); + let v = Elem::from(Concrete::Bytes(1, H256::from(bytes))); + (Elem::from(Concrete::from(U256::from(i))), (v, i)) + }) + .collect::>(), + ), + Concrete::Array(ref val) => Some( + val.iter() + .enumerate() + .map(|(i, v)| { + let t = Elem::Concrete(RangeConcrete::new(v.clone(), self.loc)); + (Elem::from(Concrete::from(U256::from(i))), (t, i)) + }) + .collect::>(), + ), + Concrete::Bytes(size, val) => Some( + val.0 + .iter() + .take(size as usize) + .enumerate() + .map(|(i, v)| { + let mut bytes = [0x00; 32]; + bytes[0] = *v; + let v = Elem::from(Concrete::Bytes(1, H256::from(bytes))); + (Elem::from(Concrete::from(U256::from(i))), (v, i)) + }) + .collect::>(), + ), + _ => None, + }; + Some(Elem::ConcreteDyn(RangeDyn::new_w_op_nums( + Elem::Concrete(other.clone()), + new?, + self.loc, + ))) + } + } } } @@ -154,17 +189,9 @@ impl RangeMemSet> for RangeConcrete { todo!() } - fn range_get_index(&self, _range: &RangeDyn) -> Option> { - unreachable!() - } - fn range_set_length(&self, _other: &RangeDyn) -> Option> { unreachable!() } - - fn range_get_length(&self) -> Option> { - unreachable!() - } } impl RangeMemSet for Elem { @@ -180,47 +207,221 @@ impl RangeMemSet for Elem { fn range_set_length(&self, other: &Self) -> Option> { match (self, other) { - (Elem::ConcreteDyn(a), Elem::ConcreteDyn(b)) => { - let mut a = a.clone(); - a.len.clone_from(&b.len); - Some(Elem::ConcreteDyn(a.clone())) - } - (a @ Elem::Concrete(_), _b @ Elem::Concrete(_)) => Some(a.clone()), + (Elem::ConcreteDyn(a), Elem::ConcreteDyn(b)) => a.range_set_length(b), + (Elem::Concrete(a), Elem::Concrete(b)) => a.range_set_length(b), (Elem::ConcreteDyn(a), _) => { let mut a = a.clone(); a.len = Box::new(other.clone()); - Some(Elem::ConcreteDyn(a.clone())) + Some(Elem::ConcreteDyn(a)) } _e => None, } } +} - fn range_get_length(&self) -> Option> { - match self { - Elem::Concrete(a) => Some(Elem::from(Concrete::from(a.val.maybe_array_size()?))), - Elem::ConcreteDyn(a) => Some(*a.len.clone()), - _e => None, - } +pub fn exec_set_length( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, +) -> Option> { + if maximize { + lhs_max.range_set_length(rhs_max) + } else { + lhs_min.range_set_length(rhs_min) } +} - fn range_get_index(&self, index: &Elem) -> Option> { - match (self, index) { - (Elem::Concrete(a), Elem::Concrete(b)) => a.range_get_index(b), - (Elem::ConcreteDyn(a), idx @ Elem::Concrete(_)) => { - if let Some((val, _)) = a.val.get(idx).cloned() { - Some(val) - } else { - None - } - } - (Elem::ConcreteDyn(a), idx @ Elem::Reference(_)) => { - if let Some((val, _)) = a.val.get(idx).cloned() { - Some(val) - } else { - None - } - } - _e => None, +pub fn exec_set_indices( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + rhs: &Elem, + maximize: bool, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + if maximize { + if let Some(t) = lhs_max.range_set_indices(rhs_max) { + Some(t) + } else { + let max = rhs.simplify_maximize(analyzer, arena).ok()?; + lhs_max.range_set_indices(&max) } + } else if let Some(t) = lhs_min.range_set_indices(rhs_min) { + Some(t) + } else { + let min = rhs.simplify_minimize(analyzer, arena).ok()?; + lhs_min.range_set_indices(&min) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use ethers_core::types::U256; + use pretty_assertions::assert_eq; + use solang_parser::pt::Loc; + + #[test] + fn concrete_set_len() { + let x: RangeConcrete = RangeConcrete::new( + Concrete::from(vec![b'h', b'e', b'l', b'l', b'o']), + Loc::Implicit, + ); + let new_len = rc_uint256(10); + let result = x.range_set_length(&new_len).unwrap(); + assert_eq!(result.range_get_length().unwrap(), Elem::Concrete(new_len)); + } + + #[test] + fn dyn_set_len() { + let x = RangeDyn::from_concrete( + Concrete::from(vec![b'h', b'e', b'l', b'l', b'o']), + Loc::Implicit, + ) + .unwrap(); + let new_len = rc_uint256(10); + let result = x.range_set_length(&new_len).unwrap(); + assert_eq!(result.range_get_length().unwrap(), Elem::Concrete(new_len)); + } + + #[test] + fn dyn_set_ref_len() { + let x = RangeDyn::from_concrete( + Concrete::from(vec![b'h', b'e', b'l', b'l', b'o']), + Loc::Implicit, + ) + .unwrap(); + let new_len = test_reference(0, 6.into(), 10.into()); + let result = Elem::ConcreteDyn(x).range_set_length(&new_len).unwrap(); + assert_eq!(result.range_get_length().unwrap(), new_len); + } + + #[test] + fn concrete_concrete_set_indices() { + let x = RangeConcrete::new( + Concrete::from(vec![b'h', b'e', b'l', b'l', b'o', b's']), + Loc::Implicit, + ); + let y = RangeConcrete::new( + Concrete::from(vec![b'w', b'o', b'r', b'l', b'd']), + Loc::Implicit, + ); + + let expected = RangeConcrete::new( + Concrete::from(vec![b'w', b'o', b'r', b'l', b'd', b's']), + Loc::Implicit, + ); + let result = x + .range_set_indices(&y) + .unwrap() + .maybe_concrete_value() + .unwrap(); + assert_eq!(result.val, expected.val); + } + + #[test] + fn dyn_concrete_index() { + let x = RangeDyn::from_concrete( + Concrete::from(vec![b'h', b'e', b'l', b'l', b'o', b's']), + Loc::Implicit, + ) + .unwrap(); + let y = RangeConcrete::new( + Concrete::from(vec![b'w', b'o', b'r', b'l', b'd']), + Loc::Implicit, + ); + + let expected = RangeDyn::new_w_op_nums( + rc_uint256(6).into(), + vec![ + ( + Elem::from(rc_uint256(0)), + ( + Elem::Concrete(RangeConcrete::new(Concrete::from(b'w'), Loc::Implicit)), + 5usize, + ), + ), + ( + Elem::from(rc_uint256(1)), + ( + Elem::Concrete(RangeConcrete::new(Concrete::from(b'o'), Loc::Implicit)), + 6usize, + ), + ), + ( + Elem::from(rc_uint256(2)), + ( + Elem::Concrete(RangeConcrete::new(Concrete::from(b'r'), Loc::Implicit)), + 7usize, + ), + ), + ( + Elem::from(rc_uint256(3)), + ( + Elem::Concrete(RangeConcrete::new(Concrete::from(b'l'), Loc::Implicit)), + 8usize, + ), + ), + ( + Elem::from(rc_uint256(4)), + ( + Elem::Concrete(RangeConcrete::new(Concrete::from(b'd'), Loc::Implicit)), + 9usize, + ), + ), + ( + Elem::from(rc_uint256(5)), + ( + Elem::Concrete(RangeConcrete::new(Concrete::from(b's'), Loc::Implicit)), + 5usize, + ), + ), + ] + .into_iter() + .collect::, (Elem<_>, usize)>>(), + Loc::Implicit, + ); + + let result = x.range_set_indices(&y).unwrap(); + assert_eq!(result.dyn_map().unwrap(), &expected.val); + } + + #[test] + fn dyn_ref_set_indices() { + let idx = test_reference(0, 0.into(), 2000.into()); + let rand: Elem<_> = rc_uint256(1337).into(); + let val: Elem<_> = rc_uint256(200).into(); + let x = RangeDyn::new_for_indices(vec![(rand.clone(), rand.clone())], Loc::Implicit); + + let y = RangeDyn::new_for_indices(vec![(idx.clone(), val.clone())], Loc::Implicit); + + let expected = Elem::ConcreteDyn(RangeDyn::new_for_indices( + vec![(rand.clone(), rand), (idx.clone(), val)], + Loc::Implicit, + )); + let result = x.range_set_indices(&y).unwrap(); + assert_eq!(result, expected); + } + + fn test_reference(id: usize, min: U256, max: U256) -> Elem { + let mut re = Reference::new(id.into()); + let mi = Box::new(Elem::Concrete(RangeConcrete::new( + Concrete::from(min), + Loc::Implicit, + ))); + let ma = Box::new(Elem::Concrete(RangeConcrete::new( + Concrete::from(max), + Loc::Implicit, + ))); + re.minimized = Some(MinMaxed::Minimized(mi.clone())); + re.maximized = Some(MinMaxed::Maximized(ma.clone())); + re.flattened_min = Some(mi); + re.flattened_max = Some(ma); + Elem::Reference(re) } } diff --git a/crates/graph/src/range/exec/mem_ops/memcopy.rs b/crates/graph/src/range/exec/mem_ops/memcopy.rs new file mode 100644 index 00000000..1df79ff9 --- /dev/null +++ b/crates/graph/src/range/exec/mem_ops/memcopy.rs @@ -0,0 +1,25 @@ +use crate::elem::Elem; +use crate::exec_traits::RangeMemOps; +use crate::nodes::Concrete; + +impl RangeMemOps for Elem { + fn range_memcopy(&self) -> Option> { + match self { + Elem::Concrete(_a) => Some(self.clone()), + Elem::ConcreteDyn(_a) => Some(self.clone()), + _e => None, + } + } +} + +pub fn exec_memcopy( + lhs_min: &Elem, + lhs_max: &Elem, + maximize: bool, +) -> Option> { + if maximize { + Some(lhs_max.clone()) + } else { + Some(lhs_min.clone()) + } +} diff --git a/crates/graph/src/range/exec/mem_ops/mod.rs b/crates/graph/src/range/exec/mem_ops/mod.rs index 4bbd3e2c..2b8c69f7 100644 --- a/crates/graph/src/range/exec/mem_ops/mod.rs +++ b/crates/graph/src/range/exec/mem_ops/mod.rs @@ -1,16 +1,9 @@ mod concat; +mod mem_get; mod mem_set; +mod memcopy; -use crate::elem::Elem; -use crate::exec_traits::RangeMemOps; -use crate::nodes::Concrete; - -impl RangeMemOps for Elem { - fn range_memcopy(&self) -> Option> { - match self { - Elem::Concrete(_a) => Some(self.clone()), - Elem::ConcreteDyn(_a) => Some(self.clone()), - _e => None, - } - } -} +pub use concat::exec_concat; +pub use mem_get::{exec_get_index, exec_get_length}; +pub use mem_set::{exec_set_indices, exec_set_length}; +pub use memcopy::exec_memcopy; diff --git a/crates/graph/src/range/exec/min.rs b/crates/graph/src/range/exec/min.rs index 823d8a26..adb818ad 100644 --- a/crates/graph/src/range/exec/min.rs +++ b/crates/graph/src/range/exec/min.rs @@ -1,31 +1,29 @@ use crate::nodes::Concrete; use crate::range::{elem::*, exec_traits::*}; +use crate::GraphBackend; + +use shared::RangeArena; impl RangeMin for RangeConcrete { fn range_min(&self, other: &Self) -> Option> { match (self.val.into_u256(), other.val.into_u256()) { - (Some(lhs_val), Some(rhs_val)) => Some(Elem::Concrete(RangeConcrete { - val: self.val.u256_as_original(lhs_val.min(rhs_val)), - loc: self.loc, - })), + (Some(lhs_val), Some(rhs_val)) => { + let op_res = lhs_val.min(rhs_val); + let val = self.val.u256_as_original(op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) + } _ => match (&self.val, &other.val) { - (Concrete::Uint(lhs_size, _), Concrete::Int(_, neg_v)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, *neg_v), - loc: self.loc, - })) - } - (Concrete::Int(lhs_size, neg_v), Concrete::Uint(_, _)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, *neg_v), - loc: self.loc, - })) + (Concrete::Uint(lhs_size, _), Concrete::Int(_, neg_v)) + | (Concrete::Int(lhs_size, neg_v), Concrete::Uint(_, _)) => { + let val = Concrete::Int(*lhs_size, *neg_v); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) } (Concrete::Int(lhs_size, l), Concrete::Int(_rhs_size, r)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, *l.min(r)), - loc: self.loc, - })) + let val = Concrete::Int(*lhs_size, *l.min(r)); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) } _ => None, }, @@ -37,15 +35,11 @@ impl RangeMin for Elem { fn range_min(&self, other: &Self) -> Option> { match (self, other) { (Elem::Concrete(a), Elem::Concrete(b)) => a.range_min(b), - (Elem::ConcreteDyn(a), Elem::ConcreteDyn(b)) => { - if a.op_num > b.op_num { - Some(self.clone()) - } else if a.op_num < b.op_num { - Some(other.clone()) - } else { - None - } - } + (Elem::ConcreteDyn(a), Elem::ConcreteDyn(b)) => match a.op_num.cmp(&b.op_num) { + std::cmp::Ordering::Greater => Some(self.clone()), + std::cmp::Ordering::Less => Some(other.clone()), + _ => None, + }, (c @ Elem::Concrete(_), Elem::ConcreteDyn(b)) | (Elem::ConcreteDyn(b), c @ Elem::Concrete(_)) => { if b.op_num == 0 { @@ -60,3 +54,37 @@ impl RangeMin for Elem { } } } + +/// Executes the minimum given the minimum and maximum of each element. It returns either the _minimum_ bound or _maximum_ bound +/// of the operation. +pub fn exec_min( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, + _analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + let candidates = vec![ + lhs_min.range_min(rhs_min), + lhs_min.range_min(rhs_max), + lhs_max.range_min(rhs_min), + lhs_max.range_min(rhs_max), + ]; + let mut candidates = candidates.into_iter().flatten().collect::>(); + candidates.sort_by(|a, b| match a.range_ord(b, arena) { + Some(r) => r, + _ => std::cmp::Ordering::Less, + }); + + if candidates.is_empty() { + return None; + } + + if maximize { + Some(candidates.remove(candidates.len() - 1)) + } else { + Some(candidates.remove(0)) + } +} diff --git a/crates/graph/src/range/exec/mod.rs b/crates/graph/src/range/exec/mod.rs index f7aed5af..a12a21e8 100644 --- a/crates/graph/src/range/exec/mod.rs +++ b/crates/graph/src/range/exec/mod.rs @@ -1,15 +1,29 @@ -mod add; +pub mod exec_op; + mod bitwise; +pub use bitwise::{exec_bit_and, exec_bit_not, exec_bit_or, exec_bit_xor}; + mod cast; -mod div; -mod exec_op; -mod exp; -mod logical; +pub use cast::exec_cast; + mod max; -mod mem_ops; +pub use max::exec_max; + mod min; -mod modulo; -mod mul; -mod ord; +pub use min::exec_min; + mod shift; -mod sub; +pub use shift::{exec_shl, exec_shr}; + +mod math_ops; +pub use math_ops::{exec_add, exec_div, exec_exp, exec_mod, exec_mul, exec_sub}; + +mod truthy_ops; +pub use truthy_ops::{ + exec_and, exec_eq_neq, exec_gt, exec_gte, exec_lt, exec_lte, exec_not, exec_or, +}; + +mod mem_ops; +pub use mem_ops::{ + exec_concat, exec_get_index, exec_get_length, exec_memcopy, exec_set_indices, exec_set_length, +}; diff --git a/crates/graph/src/range/exec/modulo.rs b/crates/graph/src/range/exec/modulo.rs deleted file mode 100644 index 7e8a8eea..00000000 --- a/crates/graph/src/range/exec/modulo.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::nodes::Concrete; -use crate::range::{elem::*, exec_traits::*}; - -use ethers_core::types::I256; - -impl RangeMod for RangeConcrete { - fn range_mod(&self, other: &Self) -> Option> { - match (self.val.into_u256(), other.val.into_u256()) { - (Some(lhs_val), Some(rhs_val)) if rhs_val != 0.into() => { - Some(Elem::Concrete(RangeConcrete { - val: self.val.u256_as_original(lhs_val % rhs_val), - loc: self.loc, - })) - } - _ => match (&self.val, &other.val) { - (Concrete::Uint(lhs_size, val), Concrete::Int(_, neg_v)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, I256::from_raw(*val) % *neg_v), - loc: self.loc, - })) - } - (Concrete::Int(lhs_size, neg_v), Concrete::Uint(_, val)) if *val != 0.into() => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, *neg_v % I256::from_raw(*val)), - loc: self.loc, - })) - } - (Concrete::Int(lhs_size, l), Concrete::Int(_rhs_size, r)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, *l % *r), - loc: self.loc, - })) - } - _ => None, - }, - } - } -} - -impl RangeMod for Elem { - fn range_mod(&self, other: &Self) -> Option> { - match (self, other) { - (Elem::Concrete(a), Elem::Concrete(b)) => a.range_mod(b), - _ => None, - } - } -} diff --git a/crates/graph/src/range/exec/mul.rs b/crates/graph/src/range/exec/mul.rs deleted file mode 100644 index 63940124..00000000 --- a/crates/graph/src/range/exec/mul.rs +++ /dev/null @@ -1,111 +0,0 @@ -use crate::nodes::Concrete; -use crate::range::{elem::*, exec_traits::*}; - -use ethers_core::types::{I256, U256}; - -impl RangeMul for RangeConcrete { - fn range_mul(&self, other: &Self) -> Option> { - match (self.val.into_u256(), other.val.into_u256()) { - (Some(lhs_val), Some(rhs_val)) => { - let max = Concrete::max(&self.val).unwrap(); - let res = lhs_val - .saturating_mul(rhs_val) - .min(max.into_u256().unwrap()); - Some(Elem::Concrete(RangeConcrete { - val: self.val.u256_as_original(res), - loc: self.loc, - })) - } - _ => match (&self.val, &other.val) { - (Concrete::Uint(lhs_size, val), Concrete::Int(_, neg_v)) - | (Concrete::Int(lhs_size, neg_v), Concrete::Uint(_, val)) => { - let max = if *lhs_size == 256 { - I256::MAX - } else { - I256::from_raw(U256::from(1u8) << U256::from(*lhs_size - 1)) - I256::from(1) - }; - let min = max * I256::from(-1i32) - I256::from(1i32); - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int( - *lhs_size, - neg_v.saturating_mul(I256::from_raw(*val)).max(min), - ), - loc: self.loc, - })) - } - (Concrete::Int(lhs_size, l), Concrete::Int(_rhs_size, r)) => { - let max = if *lhs_size == 256 { - I256::MAX - } else { - I256::from_raw(U256::from(1u8) << U256::from(*lhs_size - 1)) - I256::from(1) - }; - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, l.saturating_mul(*r).min(max)), - loc: self.loc, - })) - } - _ => None, - }, - } - } - - fn range_wrapping_mul(&self, other: &Self) -> Option> { - match (self.val.into_u256(), other.val.into_u256()) { - (Some(lhs_val), Some(rhs_val)) => { - let _max = Concrete::max(&self.val).unwrap(); - let res = lhs_val.overflowing_mul(rhs_val).0; - Some(Elem::Concrete(RangeConcrete { - val: self.val.u256_as_original(res), - loc: self.loc, - })) - } - _ => match (&self.val, &other.val) { - (Concrete::Uint(lhs_size, val), Concrete::Int(_, neg_v)) - | (Concrete::Int(lhs_size, neg_v), Concrete::Uint(_, val)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int( - *lhs_size, - neg_v.overflowing_mul(I256::from_raw(*val)).0, - ), - loc: self.loc, - })) - } - (Concrete::Int(lhs_size, l), Concrete::Int(_rhs_size, r)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, l.overflowing_mul(*r).0), - loc: self.loc, - })) - } - _ => None, - }, - } - } -} - -impl RangeMul for Elem { - fn range_mul(&self, other: &Self) -> Option> { - match (self, other) { - (Elem::Concrete(a), Elem::Concrete(b)) => a.range_mul(b), - (Elem::Concrete(a), _) if a.val.into_u256() == Some(U256::zero()) => Some(self.clone()), - (_, Elem::Concrete(b)) if b.val.into_u256() == Some(U256::zero()) => { - Some(other.clone()) - } - (Elem::Concrete(a), b) if a.val.into_u256() == Some(U256::from(1)) => Some(b.clone()), - (a, Elem::Concrete(b)) if b.val.into_u256() == Some(U256::from(1)) => Some(a.clone()), - _ => None, - } - } - - fn range_wrapping_mul(&self, other: &Self) -> Option> { - match (self, other) { - (Elem::Concrete(a), Elem::Concrete(b)) => a.range_wrapping_mul(b), - (Elem::Concrete(a), _) if a.val.into_u256() == Some(U256::zero()) => Some(self.clone()), - (_, Elem::Concrete(b)) if b.val.into_u256() == Some(U256::zero()) => { - Some(other.clone()) - } - (Elem::Concrete(a), b) if a.val.into_u256() == Some(U256::from(1)) => Some(b.clone()), - (a, Elem::Concrete(b)) if b.val.into_u256() == Some(U256::from(1)) => Some(a.clone()), - _ => None, - } - } -} diff --git a/crates/graph/src/range/exec/ord.rs b/crates/graph/src/range/exec/ord.rs deleted file mode 100644 index 4e867e25..00000000 --- a/crates/graph/src/range/exec/ord.rs +++ /dev/null @@ -1,216 +0,0 @@ -use crate::nodes::Concrete; -use crate::range::{elem::*, exec_traits::*}; - -impl RangeOrd for RangeConcrete { - fn range_ord_eq(&self, other: &Self) -> Option> { - match (self.val.into_u256(), other.val.into_u256()) { - (Some(lhs_val), Some(rhs_val)) => Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(lhs_val == rhs_val), - loc: self.loc, - })), - _ => match (&self.val, &other.val) { - (Concrete::Uint(_, _), Concrete::Int(_, _)) - | (Concrete::Int(_, _), Concrete::Uint(_, _)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(false), - loc: self.loc, - })) - } - (Concrete::Int(_lhs_size, l), Concrete::Int(_rhs_size, r)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(l == r), - loc: self.loc, - })) - } - _ => None, - }, - } - } - - fn range_neq(&self, other: &Self) -> Option> { - match (self.val.into_u256(), other.val.into_u256()) { - (Some(lhs_val), Some(rhs_val)) => Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(lhs_val != rhs_val), - loc: self.loc, - })), - _ => match (&self.val, &other.val) { - (Concrete::Uint(_, _), Concrete::Int(_, _)) - | (Concrete::Int(_, _), Concrete::Uint(_, _)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(true), - loc: self.loc, - })) - } - (Concrete::Int(_lhs_size, l), Concrete::Int(_rhs_size, r)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(l != r), - loc: self.loc, - })) - } - _ => None, - }, - } - } - - fn range_gt(&self, other: &Self) -> Option> { - match (self.val.into_u256(), other.val.into_u256()) { - (Some(lhs_val), Some(rhs_val)) => Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(lhs_val > rhs_val), - loc: self.loc, - })), - _ => match (&self.val, &other.val) { - (Concrete::Uint(_lhs_size, _val), Concrete::Int(_, _)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(true), - loc: self.loc, - })) - } - (Concrete::Int(_lhs_size, _), Concrete::Uint(_, _val)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(false), - loc: self.loc, - })) - } - (Concrete::Int(_lhs_size, l), Concrete::Int(_rhs_size, r)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(l > r), - loc: self.loc, - })) - } - _ => None, - }, - } - } - - fn range_lt(&self, other: &Self) -> Option> { - match (self.val.into_u256(), other.val.into_u256()) { - (Some(lhs_val), Some(rhs_val)) => Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(lhs_val < rhs_val), - loc: self.loc, - })), - _ => match (&self.val, &other.val) { - (Concrete::Uint(_lhs_size, _val), Concrete::Int(_, _)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(false), - loc: self.loc, - })) - } - (Concrete::Int(_lhs_size, _), Concrete::Uint(_, _val)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(true), - loc: self.loc, - })) - } - (Concrete::Int(_lhs_size, l), Concrete::Int(_rhs_size, r)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(l < r), - loc: self.loc, - })) - } - _ => None, - }, - } - } - - fn range_gte(&self, other: &Self) -> Option> { - match (self.val.into_u256(), other.val.into_u256()) { - (Some(lhs_val), Some(rhs_val)) => Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(lhs_val >= rhs_val), - loc: self.loc, - })), - _ => match (&self.val, &other.val) { - (Concrete::Uint(_lhs_size, _val), Concrete::Int(_, _)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(true), - loc: self.loc, - })) - } - (Concrete::Int(_lhs_size, _), Concrete::Uint(_, _val)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(false), - loc: self.loc, - })) - } - (Concrete::Int(_lhs_size, l), Concrete::Int(_rhs_size, r)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(l >= r), - loc: self.loc, - })) - } - _ => None, - }, - } - } - - fn range_lte(&self, other: &Self) -> Option> { - match (self.val.into_u256(), other.val.into_u256()) { - (Some(lhs_val), Some(rhs_val)) => Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(lhs_val <= rhs_val), - loc: self.loc, - })), - _ => match (&self.val, &other.val) { - (Concrete::Uint(_lhs_size, _val), Concrete::Int(_, _)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(false), - loc: self.loc, - })) - } - (Concrete::Int(_lhs_size, _), Concrete::Uint(_, _val)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(true), - loc: self.loc, - })) - } - (Concrete::Int(_lhs_size, l), Concrete::Int(_rhs_size, r)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Bool(l <= r), - loc: self.loc, - })) - } - _ => None, - }, - } - } -} - -impl RangeOrd for Elem { - fn range_ord_eq(&self, other: &Self) -> Option> { - match (self, other) { - (Elem::Concrete(a), Elem::Concrete(b)) => a.range_ord_eq(b), - _ => None, - } - } - fn range_neq(&self, other: &Self) -> Option> { - match (self, other) { - (Elem::Concrete(a), Elem::Concrete(b)) => a.range_neq(b), - _ => None, - } - } - fn range_gt(&self, other: &Self) -> Option> { - match (self, other) { - (Elem::Concrete(a), Elem::Concrete(b)) => a.range_gt(b), - _ => None, - } - } - - fn range_lt(&self, other: &Self) -> Option> { - match (self, other) { - (Elem::Concrete(a), Elem::Concrete(b)) => a.range_lt(b), - _ => None, - } - } - - fn range_gte(&self, other: &Self) -> Option> { - match (self, other) { - (Elem::Concrete(a), Elem::Concrete(b)) => a.range_gte(b), - _ => None, - } - } - - fn range_lte(&self, other: &Self) -> Option> { - match (self, other) { - (Elem::Concrete(a), Elem::Concrete(b)) => a.range_lte(b), - _ => None, - } - } -} diff --git a/crates/graph/src/range/exec/shift.rs b/crates/graph/src/range/exec/shift.rs index cf68f6fc..13436542 100644 --- a/crates/graph/src/range/exec/shift.rs +++ b/crates/graph/src/range/exec/shift.rs @@ -1,5 +1,8 @@ use crate::nodes::Concrete; use crate::range::{elem::*, exec_traits::*}; +use crate::GraphBackend; + +use shared::RangeArena; use ethers_core::types::{I256, U256}; @@ -8,31 +11,28 @@ impl RangeShift for RangeConcrete { match (self.val.into_u256(), other.val.into_u256()) { (Some(lhs_val), Some(rhs_val)) => { if rhs_val > 256.into() { - return Some(Elem::Concrete(RangeConcrete { - val: self.val.u256_as_original(U256::zero()), - loc: self.loc, - })); + let val = self.val.u256_as_original(U256::zero()); + let rc = RangeConcrete::new(val, self.loc); + return Some(rc.into()); } - let max = Concrete::max(&self.val).unwrap().into_u256().unwrap(); + + let max = Concrete::max_of_type(&self.val) + .unwrap() + .into_u256() + .unwrap(); if self.val.int_val().is_some() { // ints get weird treatment because they can push into the negatives - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int( - self.val.int_size().unwrap(), - I256::from_raw(lhs_val << rhs_val), - ), - loc: self.loc, - })) + let size = self.val.int_size().unwrap(); + let op_res = I256::from_raw(lhs_val << rhs_val); + let val = Concrete::Int(size, op_res); + Some(RangeConcrete::new(val, self.loc).into()) } else if rhs_val > lhs_val.leading_zeros().into() { - Some(Elem::Concrete(RangeConcrete { - val: max.into(), - loc: self.loc, - })) + Some(RangeConcrete::new(max.into(), self.loc).into()) } else { - Some(Elem::Concrete(RangeConcrete { - val: self.val.u256_as_original((lhs_val << rhs_val).min(max)), - loc: self.loc, - })) + let op_res = (lhs_val << rhs_val).min(max); + let val = self.val.u256_as_original(op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) } } _ => match (&self.val, &other.val) { @@ -41,28 +41,22 @@ impl RangeShift for RangeConcrete { return Some(Elem::Concrete(self.clone())); } - let max = if *lhs_size == 256 { - I256::MAX - } else { - I256::from_raw(U256::from(1u8) << U256::from(*lhs_size - 1)) - I256::from(1) - }; + let tmp = Concrete::Int(*lhs_size, I256::from(0i32)); + let min = Concrete::min_of_type(&tmp).unwrap().int_val().unwrap(); - let min = max * I256::from(-1i32) - I256::from(1i32); let (abs, is_min) = neg_v.overflowing_abs(); if is_min { if val > &U256::zero() { - Some(Elem::from(self.clone())) + Some(self.clone().into()) } else { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, I256::zero()), - loc: self.loc, - })) + let val = Concrete::Int(*lhs_size, I256::zero()); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) } } else if val > &U256::from(abs.leading_zeros()) { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, I256::zero()), - loc: self.loc, - })) + let val = Concrete::Int(*lhs_size, I256::zero()); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) } else { let raw = I256::from_raw(abs.into_raw() << val); let as_int = if raw == I256::MIN { @@ -70,10 +64,11 @@ impl RangeShift for RangeConcrete { } else { I256::from(-1i32) * raw }; - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, as_int.max(min)), - loc: self.loc, - })) + + let op_res = as_int.max(min); + let val = Concrete::Int(*lhs_size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) } } _ => None, @@ -87,15 +82,13 @@ impl RangeShift for RangeConcrete { if rhs_val == U256::zero() { Some(Elem::Concrete(self.clone())) } else if rhs_val > U256::from(256) { - Some(Elem::Concrete(RangeConcrete { - val: self.val.u256_as_original(U256::zero()), - loc: self.loc, - })) + let op_res = self.val.u256_as_original(U256::zero()); + let rc = RangeConcrete::new(op_res, self.loc); + Some(rc.into()) } else { - Some(Elem::Concrete(RangeConcrete { - val: self.val.u256_as_original(lhs_val >> rhs_val), - loc: self.loc, - })) + let op_res = self.val.u256_as_original(lhs_val >> rhs_val); + let rc = RangeConcrete::new(op_res, self.loc); + Some(rc.into()) } } _ => match (&self.val, &other.val) { @@ -103,19 +96,12 @@ impl RangeShift for RangeConcrete { if val == &U256::zero() { Some(Elem::Concrete(self.clone())) } else if val > &U256::from(*lhs_size) { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, I256::from(-1i32)), - loc: self.loc, - })) + let val = Concrete::Int(*lhs_size, I256::from(-1i32)); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) } else { - let max = if *lhs_size == 256 { - I256::MAX - } else { - I256::from_raw(U256::from(1u8) << U256::from(*lhs_size - 1)) - - I256::from(1) - }; - let min = max * I256::from(-1i32) - I256::from(1i32); - + let tmp = Concrete::Int(*lhs_size, I256::from(0i32)); + let min = Concrete::min_of_type(&tmp).unwrap().int_val().unwrap(); let (abs, is_min) = neg_v.overflowing_abs(); let bits = if is_min { 255 @@ -124,20 +110,16 @@ impl RangeShift for RangeConcrete { }; if val >= &U256::from(bits) { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, I256::from(-1i32)), - loc: self.loc, - })) + let val = Concrete::Int(*lhs_size, I256::from(-1i32)); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) } else { let shr_val = abs.into_raw() >> val; let as_int = I256::from_raw(shr_val); - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int( - *lhs_size, - (I256::from(-1i32) * as_int).max(min), - ), - loc: self.loc, - })) + let op_res = (I256::from(-1i32) * as_int).max(min); + let val = Concrete::Int(*lhs_size, op_res); + let rc = RangeConcrete::new(val, self.loc); + Some(rc.into()) } } } @@ -161,3 +143,71 @@ impl RangeShift for Elem { } } } + +/// Executes the `shift left` operation given the minimum and maximum of each element. It returns either the _minimum_ bound or _maximum_ bound +/// of the operation. +pub fn exec_shl( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, + _analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + let candidates = vec![ + lhs_min.range_shl(rhs_min), + lhs_min.range_shl(rhs_max), + lhs_max.range_shl(rhs_min), + lhs_max.range_shl(rhs_max), + ]; + let mut candidates = candidates.into_iter().flatten().collect::>(); + candidates.sort_by(|a, b| match a.range_ord(b, arena) { + Some(r) => r, + _ => std::cmp::Ordering::Less, + }); + + if candidates.is_empty() { + return None; + } + + if maximize { + Some(candidates.remove(candidates.len() - 1)) + } else { + Some(candidates.remove(0)) + } +} + +/// Executes the `shift right` operation given the minimum and maximum of each element. It returns either the _minimum_ bound or _maximum_ bound +/// of the operation. +pub fn exec_shr( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, + _analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + let candidates = vec![ + lhs_min.range_shr(rhs_min), + lhs_min.range_shr(rhs_max), + lhs_max.range_shr(rhs_min), + lhs_max.range_shr(rhs_max), + ]; + let mut candidates = candidates.into_iter().flatten().collect::>(); + candidates.sort_by(|a, b| match a.range_ord(b, arena) { + Some(r) => r, + _ => std::cmp::Ordering::Less, + }); + + if candidates.is_empty() { + return None; + } + + if maximize { + Some(candidates.remove(candidates.len() - 1)) + } else { + Some(candidates.remove(0)) + } +} diff --git a/crates/graph/src/range/exec/sub.rs b/crates/graph/src/range/exec/sub.rs deleted file mode 100644 index dfe80219..00000000 --- a/crates/graph/src/range/exec/sub.rs +++ /dev/null @@ -1,149 +0,0 @@ -use crate::nodes::Concrete; -use crate::range::{elem::*, exec_traits::*}; - -use ethers_core::types::{I256, U256}; - -impl RangeSub for RangeConcrete { - fn range_sub(&self, other: &Self) -> Option> { - match (self.val.into_u256(), other.val.into_u256()) { - (Some(lhs_val), Some(rhs_val)) => { - if lhs_val > rhs_val { - let val = lhs_val.saturating_sub(rhs_val); - Some(Elem::Concrete(RangeConcrete { - val: self.val.u256_as_original(val), - loc: self.loc, - })) - } else { - match self.val { - Concrete::Int(size, val) => Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(size, val.saturating_sub(I256::from_raw(rhs_val))), - loc: self.loc, - })), - _ => { - // TODO: this should cause a revert - let val = lhs_val.saturating_sub(rhs_val); - Some(Elem::Concrete(RangeConcrete { - val: self.val.u256_as_original(val), - loc: self.loc, - })) - } - } - } - } - _ => match (&self.val, &other.val) { - (Concrete::Uint(lhs_size, val), Concrete::Int(_, neg_v)) => { - let max = if *lhs_size == 256 { - U256::MAX - } else { - U256::from(2).pow(U256::from(*lhs_size)) - 1 - }; - Some(Elem::Concrete(RangeConcrete { - val: self - .val - .u256_as_original(val.saturating_add(neg_v.into_raw()).min(max)), - loc: self.loc, - })) - } - (Concrete::Int(lhs_size, neg_v), Concrete::Uint(_, val)) => { - let max = if *lhs_size == 256 { - I256::MAX - } else { - I256::from_raw(U256::from(1u8) << U256::from(*lhs_size - 1)) - I256::from(1) - }; - - let min = max * I256::from(-1i32) - I256::from(1i32); - - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int( - *lhs_size, - neg_v.saturating_sub(I256::from_raw(*val).max(min)), - ), - loc: self.loc, - })) - } - (Concrete::Int(lhs_size, l), Concrete::Int(_rhs_size, r)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, l.saturating_sub(*r)), - loc: self.loc, - })) - } - _ => None, - }, - } - } - - fn range_wrapping_sub(&self, other: &Self) -> Option> { - match (self.val.into_u256(), other.val.into_u256()) { - (Some(lhs_val), Some(rhs_val)) => { - if lhs_val > rhs_val { - let val = lhs_val.overflowing_sub(rhs_val).0; - Some(Elem::Concrete(RangeConcrete { - val: self.val.u256_as_original(val), - loc: self.loc, - })) - } else { - match self.val { - Concrete::Int(size, val) => Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int( - size, - val.overflowing_sub(I256::from_raw(rhs_val)).0, - ), - loc: self.loc, - })), - _ => { - let val = lhs_val.overflowing_sub(rhs_val).0; - Some(Elem::Concrete(RangeConcrete { - val: self.val.u256_as_original(val), - loc: self.loc, - })) - } - } - } - } - _ => match (&self.val, &other.val) { - (Concrete::Uint(_lhs_size, val), Concrete::Int(_, neg_v)) => { - Some(Elem::Concrete(RangeConcrete { - val: self - .val - .u256_as_original(val.overflowing_add(neg_v.into_raw()).0), - loc: self.loc, - })) - } - (Concrete::Int(lhs_size, neg_v), Concrete::Uint(_, val)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int( - *lhs_size, - I256::from_raw(neg_v.into_raw().overflowing_sub(*val).0), - ), - loc: self.loc, - })) - } - (Concrete::Int(lhs_size, l), Concrete::Int(_rhs_size, r)) => { - Some(Elem::Concrete(RangeConcrete { - val: Concrete::Int(*lhs_size, l.overflowing_sub(*r).0), - loc: self.loc, - })) - } - _ => None, - }, - } - } -} - -impl RangeSub for Elem { - fn range_sub(&self, other: &Self) -> Option> { - match (self, other) { - (_, Elem::Concrete(b)) if b.val.into_u256() == Some(U256::zero()) => Some(self.clone()), - (Elem::Concrete(a), Elem::Concrete(b)) => a.range_sub(b), - _ => None, - } - } - - fn range_wrapping_sub(&self, other: &Self) -> Option> { - match (self, other) { - (_, Elem::Concrete(b)) if b.val.into_u256() == Some(U256::zero()) => Some(self.clone()), - (Elem::Concrete(a), Elem::Concrete(b)) => a.range_wrapping_sub(b), - _ => None, - } - } -} diff --git a/crates/graph/src/range/exec/truthy_ops/logical.rs b/crates/graph/src/range/exec/truthy_ops/logical.rs new file mode 100644 index 00000000..aa4796b7 --- /dev/null +++ b/crates/graph/src/range/exec/truthy_ops/logical.rs @@ -0,0 +1,145 @@ +use crate::nodes::Concrete; +use crate::range::{elem::*, exec_traits::*}; +use crate::GraphBackend; + +use shared::RangeArena; + +impl RangeUnary for RangeConcrete { + fn range_not(&self) -> Option> { + match self.val { + Concrete::Bool(b) => Some(RangeConcrete::new(Concrete::Bool(!b), self.loc).into()), + _ => None, + } + } + + fn range_and(&self, other: &Self) -> Option> { + match (&self.val, &other.val) { + (Concrete::Bool(a), Concrete::Bool(b)) => { + Some(RangeConcrete::new(Concrete::Bool(*a && *b), self.loc).into()) + } + _ => None, + } + } + + fn range_or(&self, other: &Self) -> Option> { + match (&self.val, &other.val) { + (Concrete::Bool(a), Concrete::Bool(b)) => { + Some(RangeConcrete::new(Concrete::Bool(*a || *b), self.loc).into()) + } + _ => None, + } + } +} + +impl RangeUnary for Elem { + fn range_not(&self) -> Option> { + match self { + Elem::Concrete(a) => a.range_not(), + _ => None, + } + } + fn range_and(&self, other: &Self) -> Option> { + match (self, other) { + (Elem::Concrete(a), Elem::Concrete(b)) => a.range_and(b), + _ => None, + } + } + fn range_or(&self, other: &Self) -> Option> { + match (self, other) { + (Elem::Concrete(a), Elem::Concrete(b)) => a.range_or(b), + _ => None, + } + } +} + +pub fn exec_and( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, + _analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + let candidates = vec![ + lhs_min.range_and(rhs_min), + lhs_min.range_and(rhs_max), + lhs_max.range_and(rhs_min), + lhs_max.range_and(rhs_max), + ]; + let mut candidates = candidates.into_iter().flatten().collect::>(); + candidates.sort_by(|a, b| match a.range_ord(b, arena) { + Some(r) => r, + _ => std::cmp::Ordering::Less, + }); + + if candidates.is_empty() { + return None; + } + + if maximize { + Some(candidates.remove(candidates.len() - 1)) + } else { + Some(candidates.remove(0)) + } +} + +pub fn exec_or( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, + _analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + let candidates = vec![ + lhs_min.range_or(rhs_min), + lhs_min.range_or(rhs_max), + lhs_max.range_or(rhs_min), + lhs_max.range_or(rhs_max), + ]; + let mut candidates = candidates.into_iter().flatten().collect::>(); + candidates.sort_by(|a, b| match a.range_ord(b, arena) { + Some(r) => r, + _ => std::cmp::Ordering::Less, + }); + + if candidates.is_empty() { + return None; + } + + if maximize { + Some(candidates.remove(candidates.len() - 1)) + } else { + Some(candidates.remove(0)) + } +} + +pub fn exec_not( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, + _analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + assert!(matches!(rhs_min, Elem::Null) && matches!(rhs_max, Elem::Null)); + let candidates = vec![lhs_min.range_not(), lhs_max.range_not()]; + let mut candidates = candidates.into_iter().flatten().collect::>(); + candidates.sort_by(|a, b| match a.range_ord(b, arena) { + Some(r) => r, + _ => std::cmp::Ordering::Less, + }); + + if candidates.is_empty() { + return None; + } + + if maximize { + Some(candidates.remove(candidates.len() - 1)) + } else { + Some(candidates.remove(0)) + } +} diff --git a/crates/graph/src/range/exec/truthy_ops/mod.rs b/crates/graph/src/range/exec/truthy_ops/mod.rs new file mode 100644 index 00000000..4a100732 --- /dev/null +++ b/crates/graph/src/range/exec/truthy_ops/mod.rs @@ -0,0 +1,5 @@ +mod logical; +mod ord; + +pub use logical::{exec_and, exec_not, exec_or}; +pub use ord::{exec_eq_neq, exec_gt, exec_gte, exec_lt, exec_lte}; diff --git a/crates/graph/src/range/exec/truthy_ops/ord.rs b/crates/graph/src/range/exec/truthy_ops/ord.rs new file mode 100644 index 00000000..b5bc1046 --- /dev/null +++ b/crates/graph/src/range/exec/truthy_ops/ord.rs @@ -0,0 +1,333 @@ +use crate::nodes::Concrete; +use crate::range::{elem::*, exec_traits::*}; +use crate::GraphBackend; + +use shared::RangeArena; + +use solang_parser::pt::Loc; + +impl RangeOrd for RangeConcrete { + fn range_ord_eq(&self, other: &Self) -> Option> { + match (self.val.into_u256(), other.val.into_u256()) { + (Some(lhs_val), Some(rhs_val)) => { + let rc = RangeConcrete::new(Concrete::Bool(lhs_val == rhs_val), self.loc); + Some(rc.into()) + } + _ => match (&self.val, &other.val) { + (Concrete::Uint(_, _), Concrete::Int(_, _)) + | (Concrete::Int(_, _), Concrete::Uint(_, _)) => { + Some(RangeConcrete::new(Concrete::Bool(false), self.loc).into()) + } + (Concrete::Int(_lhs_size, l), Concrete::Int(_rhs_size, r)) => { + Some(RangeConcrete::new(Concrete::Bool(l == r), self.loc).into()) + } + _ => None, + }, + } + } + + fn range_neq(&self, other: &Self) -> Option> { + match (self.val.into_u256(), other.val.into_u256()) { + (Some(lhs_val), Some(rhs_val)) => { + Some(RangeConcrete::new(Concrete::Bool(lhs_val != rhs_val), self.loc).into()) + } + _ => match (&self.val, &other.val) { + (Concrete::Uint(_, _), Concrete::Int(_, _)) + | (Concrete::Int(_, _), Concrete::Uint(_, _)) => { + Some(RangeConcrete::new(Concrete::Bool(true), self.loc).into()) + } + (Concrete::Int(_lhs_size, l), Concrete::Int(_rhs_size, r)) => { + Some(RangeConcrete::new(Concrete::Bool(l != r), self.loc).into()) + } + _ => None, + }, + } + } + + fn range_gt(&self, other: &Self) -> Option> { + match (self.val.into_u256(), other.val.into_u256()) { + (Some(lhs_val), Some(rhs_val)) => { + Some(RangeConcrete::new(Concrete::Bool(lhs_val > rhs_val), self.loc).into()) + } + _ => match (&self.val, &other.val) { + (Concrete::Uint(_lhs_size, _val), Concrete::Int(_, _)) => { + Some(RangeConcrete::new(Concrete::Bool(true), self.loc).into()) + } + (Concrete::Int(_lhs_size, _), Concrete::Uint(_, _val)) => { + Some(RangeConcrete::new(Concrete::Bool(false), self.loc).into()) + } + (Concrete::Int(_lhs_size, l), Concrete::Int(_rhs_size, r)) => { + Some(RangeConcrete::new(Concrete::Bool(l > r), self.loc).into()) + } + _ => None, + }, + } + } + + fn range_lt(&self, other: &Self) -> Option> { + match (self.val.into_u256(), other.val.into_u256()) { + (Some(lhs_val), Some(rhs_val)) => { + Some(RangeConcrete::new(Concrete::Bool(lhs_val < rhs_val), self.loc).into()) + } + _ => match (&self.val, &other.val) { + (Concrete::Uint(_lhs_size, _val), Concrete::Int(_, _)) => { + Some(RangeConcrete::new(Concrete::Bool(false), self.loc).into()) + } + (Concrete::Int(_lhs_size, _), Concrete::Uint(_, _val)) => { + Some(RangeConcrete::new(Concrete::Bool(true), self.loc).into()) + } + (Concrete::Int(_lhs_size, l), Concrete::Int(_rhs_size, r)) => { + Some(RangeConcrete::new(Concrete::Bool(l < r), self.loc).into()) + } + _ => None, + }, + } + } + + fn range_gte(&self, other: &Self) -> Option> { + match (self.val.into_u256(), other.val.into_u256()) { + (Some(lhs_val), Some(rhs_val)) => { + Some(RangeConcrete::new(Concrete::Bool(lhs_val >= rhs_val), self.loc).into()) + } + _ => match (&self.val, &other.val) { + (Concrete::Uint(_lhs_size, _val), Concrete::Int(_, _)) => { + Some(RangeConcrete::new(Concrete::Bool(true), self.loc).into()) + } + (Concrete::Int(_lhs_size, _), Concrete::Uint(_, _val)) => { + Some(RangeConcrete::new(Concrete::Bool(false), self.loc).into()) + } + (Concrete::Int(_lhs_size, l), Concrete::Int(_rhs_size, r)) => { + Some(RangeConcrete::new(Concrete::Bool(l >= r), self.loc).into()) + } + _ => None, + }, + } + } + + fn range_lte(&self, other: &Self) -> Option> { + match (self.val.into_u256(), other.val.into_u256()) { + (Some(lhs_val), Some(rhs_val)) => { + Some(RangeConcrete::new(Concrete::Bool(lhs_val <= rhs_val), self.loc).into()) + } + _ => match (&self.val, &other.val) { + (Concrete::Uint(_lhs_size, _val), Concrete::Int(_, _)) => { + Some(RangeConcrete::new(Concrete::Bool(false), self.loc).into()) + } + (Concrete::Int(_lhs_size, _), Concrete::Uint(_, _val)) => { + Some(RangeConcrete::new(Concrete::Bool(true), self.loc).into()) + } + (Concrete::Int(_lhs_size, l), Concrete::Int(_rhs_size, r)) => { + Some(RangeConcrete::new(Concrete::Bool(l <= r), self.loc).into()) + } + _ => None, + }, + } + } +} + +impl RangeOrd for Elem { + fn range_ord_eq(&self, other: &Self) -> Option> { + match (self, other) { + (Elem::Concrete(a), Elem::Concrete(b)) => a.range_ord_eq(b), + _ => None, + } + } + fn range_neq(&self, other: &Self) -> Option> { + match (self, other) { + (Elem::Concrete(a), Elem::Concrete(b)) => a.range_neq(b), + _ => None, + } + } + fn range_gt(&self, other: &Self) -> Option> { + match (self, other) { + (Elem::Concrete(a), Elem::Concrete(b)) => a.range_gt(b), + _ => None, + } + } + + fn range_lt(&self, other: &Self) -> Option> { + match (self, other) { + (Elem::Concrete(a), Elem::Concrete(b)) => a.range_lt(b), + _ => None, + } + } + + fn range_gte(&self, other: &Self) -> Option> { + match (self, other) { + (Elem::Concrete(a), Elem::Concrete(b)) => a.range_gte(b), + _ => None, + } + } + + fn range_lte(&self, other: &Self) -> Option> { + match (self, other) { + (Elem::Concrete(a), Elem::Concrete(b)) => a.range_lte(b), + _ => None, + } + } +} + +/// Executes the `greater than` operation given the minimum and maximum of each element. It returns either the _minimum_ bound or _maximum_ bound +/// of the operation. +pub fn exec_gt( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, +) -> Option> { + if maximize { + lhs_max.range_gt(rhs_min) + } else { + lhs_min.range_gt(rhs_max) + } +} + +/// Executes the `less than` operation given the minimum and maximum of each element. It returns either the _minimum_ bound or _maximum_ bound +/// of the operation. +pub fn exec_lt( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, +) -> Option> { + if maximize { + lhs_min.range_lt(rhs_max) + } else { + lhs_max.range_lt(rhs_min) + } +} + +/// Executes the `greater than or equal` operation given the minimum and maximum of each element. It returns either the _minimum_ bound or _maximum_ bound +/// of the operation. +pub fn exec_gte( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, +) -> Option> { + if maximize { + lhs_max.range_gte(rhs_min) + } else { + lhs_min.range_gte(rhs_max) + } +} + +/// Executes the `less than or equal` operation given the minimum and maximum of each element. It returns either the _minimum_ bound or _maximum_ bound +/// of the operation. +pub fn exec_lte( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, +) -> Option> { + if maximize { + lhs_min.range_lte(rhs_max) + } else { + lhs_max.range_lte(rhs_min) + } +} + +/// Executes the `equal` operation or `not equal` operation given the minimum and maximum of each element. It returns either the _minimum_ bound or _maximum_ bound +/// of the operation. +pub fn exec_eq_neq( + lhs_min: &Elem, + lhs_max: &Elem, + rhs_min: &Elem, + rhs_max: &Elem, + maximize: bool, + eq: bool, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Option> { + // prevent trying to eval when we have dependents + if !lhs_min.dependent_on(analyzer, arena).is_empty() + || !lhs_max.dependent_on(analyzer, arena).is_empty() + || !rhs_min.dependent_on(analyzer, arena).is_empty() + || !rhs_max.dependent_on(analyzer, arena).is_empty() + { + return None; + } + + let loc = if let Some(c) = lhs_min.maybe_concrete() { + c.loc + } else if let Some(c) = lhs_max.maybe_concrete() { + c.loc + } else if let Some(c) = rhs_min.maybe_concrete() { + c.loc + } else if let Some(c) = rhs_max.maybe_concrete() { + c.loc + } else { + Loc::Implicit + }; + + // We want to prove that there exists some values for LHS and RHS that are equal + // We do this for equality maximization and inequality minimization + let overlap_test = eq && maximize || !eq && !maximize; + + if overlap_test { + // check for any overlap + // + // Check if lhs max > rhs min + // LHS: <--?---| max + // RHS: min |----?----> + let lhs_max_rhs_min_ord = lhs_max.range_ord(rhs_min, arena); + + // Check if lhs min < rhs max + // LHS: min |----?----> + // RHS: <--?---| max + let lhs_min_rhs_max_ord = lhs_min.range_ord(rhs_max, arena); + + // if lhs max is less than the rhs min, it has to be false + if matches!(lhs_max_rhs_min_ord, Some(std::cmp::Ordering::Less)) { + return Some(Elem::Concrete(RangeConcrete { + val: Concrete::Bool(!eq), + loc, + })); + } + + // if lhs min is greater than the rhs max, it has to be false + if matches!(lhs_min_rhs_max_ord, Some(std::cmp::Ordering::Greater)) { + return Some(Elem::Concrete(RangeConcrete { + val: Concrete::Bool(!eq), + loc, + })); + } + + // lhs_max >= rhs_min + // lhs_min <= rhs_max + Some(Elem::Concrete(RangeConcrete { + val: Concrete::Bool(eq), + loc, + })) + } else { + // We want to check that there is *some* case in which they can be *not* equal. + // This only occurs when both sides are constant and equal + match ( + // check if lhs is constant + lhs_min.range_ord(lhs_max, arena), + // check if rhs is constant + rhs_min.range_ord(rhs_max, arena), + // check if lhs is equal to rhs + lhs_min.range_ord(rhs_min, arena), + ) { + // LHS & RHS are constant and equal + ( + Some(std::cmp::Ordering::Equal), + Some(std::cmp::Ordering::Equal), + Some(std::cmp::Ordering::Equal), + ) => Some(Elem::Concrete(RangeConcrete { + val: Concrete::Bool(eq), + loc, + })), + // LHS or RHS is not constant or they are constant and unequal + _ => Some(Elem::Concrete(RangeConcrete { + val: Concrete::Bool(!eq), + loc, + })), + } + } +} diff --git a/crates/graph/src/range/exec_traits.rs b/crates/graph/src/range/exec_traits.rs index 6fb33895..061b466d 100644 --- a/crates/graph/src/range/exec_traits.rs +++ b/crates/graph/src/range/exec_traits.rs @@ -1,7 +1,10 @@ use crate::{range::elem::Elem, GraphBackend}; +use shared::RangeArena; + +use std::hash::Hash; /// For execution of operations to be performed on range expressions -pub trait ExecOp { +pub trait ExecOp { type GraphError; /// Attempts to execute ops by evaluating expressions and applying the op for the left-hand-side /// and right-hand-side @@ -9,6 +12,7 @@ pub trait ExecOp { &self, maximize: bool, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, Self::GraphError>; fn exec( @@ -16,22 +20,26 @@ pub trait ExecOp { parts: (Elem, Elem, Elem, Elem), maximize: bool, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, Self::GraphError>; /// Cache execution fn cache_exec_op( &mut self, maximize: bool, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) -> Result<(), Self::GraphError>; fn spread( &self, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result<(Elem, Elem, Elem, Elem), Self::GraphError>; fn simplify_spread( &self, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result<(Elem, Elem, Elem, Elem), Self::GraphError>; fn uncache_exec(&mut self); @@ -40,6 +48,7 @@ pub trait ExecOp { &self, maximize: bool, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, Self::GraphError>; /// Attempts to simplify an expression (i.e. just apply constant folding) @@ -48,8 +57,9 @@ pub trait ExecOp { parts: (Elem, Elem, Elem, Elem), maximize: bool, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, Self::GraphError> { - self.exec(parts, maximize, analyzer) + self.exec(parts, maximize, analyzer, arena) } } @@ -158,9 +168,16 @@ pub trait RangeConcat { pub trait RangeMemSet { /// Applies a transformation of indices fn range_set_indices(&self, other: &Rhs) -> Option>; - /// Gets an index - fn range_get_index(&self, other: &Rhs) -> Option>; /// Applies a transformation of length fn range_set_length(&self, other: &Rhs) -> Option>; +} + +pub trait RangeMemGet: RangeMemLen { + /// Gets an index + fn range_get_index(&self, other: &Rhs) -> Option>; +} + +pub trait RangeMemLen { + /// Gets the length fn range_get_length(&self) -> Option>; } diff --git a/crates/graph/src/range/mod.rs b/crates/graph/src/range/mod.rs index f61240b9..119e48ac 100644 --- a/crates/graph/src/range/mod.rs +++ b/crates/graph/src/range/mod.rs @@ -1,3 +1,14 @@ +//! Ranges consist of a minimum range element and a maximum range element. +//! +//! +//! +//! We define an algebra of types. This means we can perform calculations between two range elements. +//! +//! +//! +//! +//! + pub mod elem; pub mod exec; pub mod exec_traits; diff --git a/crates/graph/src/range/range_string.rs b/crates/graph/src/range/range_string.rs index 6a56660d..d845012f 100644 --- a/crates/graph/src/range/range_string.rs +++ b/crates/graph/src/range/range_string.rs @@ -3,6 +3,8 @@ use crate::{ range::elem::*, GraphBackend, }; +use shared::RangeArena; + use solang_parser::pt::Loc; use std::collections::BTreeMap; @@ -37,13 +39,26 @@ impl RangeString { /// String related functions for ranges pub trait ToRangeString { /// Gets the definition string of the range element - fn def_string(&self, analyzer: &impl GraphBackend) -> RangeElemString; + fn def_string( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> RangeElemString; /// Converts a range to a human string - fn to_range_string(&self, maximize: bool, analyzer: &impl GraphBackend) -> RangeElemString; + fn to_range_string( + &self, + maximize: bool, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> RangeElemString; } impl ToRangeString for Elem { - fn def_string(&self, analyzer: &impl GraphBackend) -> RangeElemString { + fn def_string( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> RangeElemString { match self { Elem::Concrete(c) => RangeElemString::new(c.val.as_human_string(), c.loc), Elem::Reference(Reference { idx, .. }) => { @@ -53,34 +68,42 @@ impl ToRangeString for Elem { .unwrap(); RangeElemString::new(cvar.display_name.clone(), cvar.loc.unwrap_or(Loc::Implicit)) } - Elem::ConcreteDyn(rd) => rd.def_string(analyzer), - Elem::Expr(expr) => expr.def_string(analyzer), + Elem::ConcreteDyn(rd) => rd.def_string(analyzer, arena), + Elem::Expr(expr) => expr.def_string(analyzer, arena), Elem::Null => RangeElemString::new("null".to_string(), Loc::Implicit), - Elem::Arena(_) => self.dearenaize(analyzer).borrow().def_string(analyzer), + Elem::Arena(_) => self.dearenaize_clone(arena).def_string(analyzer, arena), } } - fn to_range_string(&self, maximize: bool, analyzer: &impl GraphBackend) -> RangeElemString { + fn to_range_string( + &self, + maximize: bool, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> RangeElemString { match self { Elem::Concrete(c) => RangeElemString::new(c.val.as_human_string(), c.loc), Elem::Reference(Reference { idx, .. }) => { let as_var = ContextVarNode::from(*idx); - let name = as_var.as_controllable_name(analyzer).unwrap(); + let name = as_var.as_controllable_name(analyzer, arena).unwrap(); RangeElemString::new(name, as_var.loc(analyzer).unwrap()) } - Elem::ConcreteDyn(rd) => rd.to_range_string(maximize, analyzer), - Elem::Expr(expr) => expr.to_range_string(maximize, analyzer), + Elem::ConcreteDyn(rd) => rd.to_range_string(maximize, analyzer, arena), + Elem::Expr(expr) => expr.to_range_string(maximize, analyzer, arena), Elem::Null => RangeElemString::new("null".to_string(), Loc::Implicit), Elem::Arena(_) => self - .dearenaize(analyzer) - .borrow() - .to_range_string(maximize, analyzer), + .dearenaize_clone(arena) + .to_range_string(maximize, analyzer, arena), } } } impl ToRangeString for RangeDyn { - fn def_string(&self, analyzer: &impl GraphBackend) -> RangeElemString { + fn def_string( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> RangeElemString { let displayed_vals = self.val.iter().take(20).collect::>(); let val_str = displayed_vals @@ -88,8 +111,8 @@ impl ToRangeString for RangeDyn { .map(|(key, (val, _))| { format!( "{}: {}", - key.def_string(analyzer).s, - val.def_string(analyzer).s + key.def_string(analyzer, arena).s, + val.def_string(analyzer, arena).s ) }) .collect::>() @@ -98,14 +121,19 @@ impl ToRangeString for RangeDyn { RangeElemString::new( format!( "{{len: {}, indices: [{}]}}", - self.len.to_range_string(false, analyzer).s, + self.len.to_range_string(false, analyzer, arena).s, val_str ), self.loc, ) } - fn to_range_string(&self, maximize: bool, analyzer: &impl GraphBackend) -> RangeElemString { + fn to_range_string( + &self, + maximize: bool, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> RangeElemString { let val_str = if self.val.len() > 10 { let displayed_vals = self .val @@ -114,8 +142,8 @@ impl ToRangeString for RangeDyn { .filter(|(_key, (val, _op))| *val != Elem::Null) .map(|(key, (val, _op))| { ( - key.to_range_string(maximize, analyzer).s, - val.to_range_string(maximize, analyzer).s, + key.to_range_string(maximize, analyzer, arena).s, + val.to_range_string(maximize, analyzer, arena).s, ) }) .collect::>(); @@ -135,8 +163,8 @@ impl ToRangeString for RangeDyn { .map(|(key, (val, _op))| { // (key.to_range_string(maximize, analyzer).s, val.to_range_string(maximize, analyzer).s) ( - key.to_range_string(maximize, analyzer).s, - val.to_range_string(maximize, analyzer).s, + key.to_range_string(maximize, analyzer, arena).s, + val.to_range_string(maximize, analyzer, arena).s, ) }) .collect::>(); @@ -155,8 +183,8 @@ impl ToRangeString for RangeDyn { .filter(|(_key, (val, _op))| *val != Elem::Null) .map(|(key, (val, _op))| { ( - key.to_range_string(maximize, analyzer).s, - val.to_range_string(maximize, analyzer).s, + key.to_range_string(maximize, analyzer, arena).s, + val.to_range_string(maximize, analyzer, arena).s, ) }) .collect::>(); @@ -171,7 +199,7 @@ impl ToRangeString for RangeDyn { RangeElemString::new( format!( "{{len: {}, indices: {{{}}}}}", - self.len.to_range_string(maximize, analyzer).s, + self.len.to_range_string(maximize, analyzer, arena).s, val_str ), self.loc, @@ -180,17 +208,26 @@ impl ToRangeString for RangeDyn { } impl ToRangeString for RangeExpr { - fn def_string(&self, analyzer: &impl GraphBackend) -> RangeElemString { - self.lhs.def_string(analyzer) + fn def_string( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> RangeElemString { + self.lhs.def_string(analyzer, arena) } - fn to_range_string(&self, maximize: bool, analyzer: &impl GraphBackend) -> RangeElemString { + fn to_range_string( + &self, + maximize: bool, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> RangeElemString { if let MaybeCollapsed::Collapsed(collapsed) = - collapse(&self.lhs, self.op, &self.rhs, analyzer) + collapse(*self.lhs.clone(), self.op, *self.rhs.clone(), arena) { - return collapsed.to_range_string(maximize, analyzer); + return collapsed.to_range_string(maximize, analyzer, arena); } - let lhs_r_str = self.lhs.to_range_string(maximize, analyzer); + let lhs_r_str = self.lhs.to_range_string(maximize, analyzer, arena); let lhs_str = match *self.lhs { Elem::Expr(_) => { let new_str = format!("({})", lhs_r_str.s); @@ -199,7 +236,7 @@ impl ToRangeString for RangeExpr { _ => lhs_r_str, }; - let rhs_r_str = self.rhs.to_range_string(maximize, analyzer); + let rhs_r_str = self.rhs.to_range_string(maximize, analyzer, arena); let rhs_str = match *self.rhs { Elem::Expr(_) => { @@ -216,9 +253,9 @@ impl ToRangeString for RangeExpr { ) } else if matches!(self.op, RangeOp::Cast) { let rhs = if maximize { - self.rhs.maximize(analyzer).unwrap() + self.rhs.maximize(analyzer, arena).unwrap() } else { - self.rhs.minimize(analyzer).unwrap() + self.rhs.minimize(analyzer, arena).unwrap() }; match rhs { @@ -237,9 +274,9 @@ impl ToRangeString for RangeExpr { } } else if matches!(self.op, RangeOp::BitNot) { let lhs = if maximize { - self.lhs.maximize(analyzer).unwrap() + self.lhs.maximize(analyzer, arena).unwrap() } else { - self.lhs.minimize(analyzer).unwrap() + self.lhs.minimize(analyzer, arena).unwrap() }; match lhs { diff --git a/crates/graph/src/range/range_trait.rs b/crates/graph/src/range/range_trait.rs index 4a5f0853..e5b1bd75 100644 --- a/crates/graph/src/range/range_trait.rs +++ b/crates/graph/src/range/range_trait.rs @@ -1,32 +1,40 @@ use crate::FlattenedRange; use crate::{range::elem::RangeElem, GraphBackend}; -use shared::NodeIdx; -use std::borrow::Cow; +use shared::{NodeIdx, RangeArena}; +use std::{borrow::Cow, hash::Hash}; -pub trait Range { +pub trait Range { type GraphError; - type ElemTy: RangeElem + Clone; + type ElemTy: RangeElem + Clone + Hash; /// Evaluate both the minimum and the maximum - cache along the way - fn cache_eval(&mut self, analyzer: &mut impl GraphBackend) -> Result<(), Self::GraphError>; + fn cache_eval( + &mut self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena, + ) -> Result<(), Self::GraphError>; /// Evaluate the range minimum fn evaled_range_min( &self, analyzer: &impl GraphBackend, + arena: &mut RangeArena, ) -> Result; /// Evaluate the range maximum fn evaled_range_max( &self, analyzer: &impl GraphBackend, + arena: &mut RangeArena, ) -> Result; /// Simplify the minimum, leaving references in place fn simplified_range_min( &self, analyzer: &impl GraphBackend, + arena: &mut RangeArena, ) -> Result; /// Simplify the maximum, leaving references in place fn simplified_range_max( &self, analyzer: &impl GraphBackend, + arena: &mut RangeArena, ) -> Result; /// Return the range minimum fn range_min(&self) -> std::borrow::Cow<'_, Self::ElemTy>; @@ -66,6 +74,7 @@ pub trait Range { self_idx: NodeIdx, new_idx: NodeIdx, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena, ); /// Replace a potential recursion causing node index with a new index fn filter_max_recursion( @@ -73,13 +82,19 @@ pub trait Range { self_idx: NodeIdx, new_idx: NodeIdx, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena, ); /// Cache the flattened range - fn cache_flatten(&mut self, analyzer: &mut impl GraphBackend) -> Result<(), Self::GraphError>; + fn cache_flatten( + &mut self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena, + ) -> Result<(), Self::GraphError>; /// Produce a flattened range or use the cached flattened range fn flattened_range<'a>( &'a mut self, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena, ) -> Result, Self::GraphError> where Self: Sized + Clone; @@ -87,17 +102,33 @@ pub trait Range { fn take_flattened_range( &mut self, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena, ) -> Result where Self: Sized; } -pub trait RangeEval> { - fn sat(&self, analyzer: &impl GraphBackend) -> bool; - fn unsat(&self, analyzer: &impl GraphBackend) -> bool { - !self.sat(analyzer) +pub trait RangeEval + Hash> { + fn sat(&self, analyzer: &impl GraphBackend, arena: &mut RangeArena) -> bool; + fn unsat(&self, analyzer: &impl GraphBackend, arena: &mut RangeArena) -> bool { + !self.sat(analyzer, arena) } - fn contains(&self, other: &Self, analyzer: &impl GraphBackend) -> bool; - fn contains_elem(&self, other: &T, analyzer: &impl GraphBackend) -> bool; - fn overlaps(&self, other: &Self, analyzer: &impl GraphBackend) -> bool; + fn contains( + &self, + other: &Self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena, + ) -> bool; + fn contains_elem( + &self, + other: &T, + analyzer: &impl GraphBackend, + arena: &mut RangeArena, + ) -> bool; + fn overlaps( + &self, + other: &Self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena, + ) -> bool; } diff --git a/crates/graph/src/range/solc_range.rs b/crates/graph/src/range/solc_range.rs index cb6bd8a8..eddff194 100644 --- a/crates/graph/src/range/solc_range.rs +++ b/crates/graph/src/range/solc_range.rs @@ -4,7 +4,7 @@ use crate::{ AsDotStr, GraphBackend, GraphError, }; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; use ethers_core::types::{Address, H256, I256, U256}; use solang_parser::pt::Loc; @@ -39,20 +39,24 @@ pub struct SolcRange { } impl AsDotStr for SolcRange { - fn as_dot_str(&self, analyzer: &impl GraphBackend) -> String { + fn as_dot_str( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> String { format!( "[{}, {}] excluding: [{}]", - self.evaled_range_min(analyzer) + self.evaled_range_min(analyzer, arena) .unwrap() - .to_range_string(false, analyzer) + .to_range_string(false, analyzer, arena) .s, - self.evaled_range_max(analyzer) + self.evaled_range_max(analyzer, arena) .unwrap() - .to_range_string(true, analyzer) + .to_range_string(true, analyzer, arena) .s, self.exclusions .iter() - .map(|excl| Elem::Arena(*excl).to_range_string(false, analyzer).s) + .map(|excl| Elem::Arena(*excl).to_range_string(false, analyzer, arena).s) .collect::>() .join(", ") ) @@ -61,10 +65,7 @@ impl AsDotStr for SolcRange { impl From for SolcRange { fn from(b: bool) -> Self { - let val = Elem::Concrete(RangeConcrete { - val: Concrete::Bool(b), - loc: Loc::Implicit, - }); + let val = Elem::Concrete(RangeConcrete::new(Concrete::Bool(b), Loc::Implicit)); Self::new(val.clone(), val, vec![]) } } @@ -77,9 +78,13 @@ impl From> for SolcRange { impl SolcRange { /// Get all ContextVarNodes that this range references - pub fn dependent_on(&self, analyzer: &impl GraphBackend) -> Vec { - let mut deps = self.range_min().dependent_on(analyzer); - deps.extend(self.range_max().dependent_on(analyzer)); + pub fn dependent_on( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Vec { + let mut deps = self.range_min().dependent_on(analyzer, arena); + deps.extend(self.range_max().dependent_on(analyzer, arena)); deps.dedup(); deps.into_iter().map(ContextVarNode::from).collect() @@ -88,9 +93,10 @@ impl SolcRange { pub fn recursive_dependent_on( &self, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, GraphError> { - let mut deps = self.range_min().recursive_dependent_on(analyzer)?; - deps.extend(self.range_max().recursive_dependent_on(analyzer)?); + let mut deps = self.range_min().recursive_dependent_on(analyzer, arena)?; + deps.extend(self.range_max().recursive_dependent_on(analyzer, arena)?); deps.dedup(); Ok(deps) @@ -112,37 +118,51 @@ impl SolcRange { to_replace: NodeIdx, replacement: Elem, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) { if let Some(ref mut flattened) = &mut self.flattened { - Elem::Arena(flattened.min).replace_dep(to_replace, replacement.clone(), analyzer); - Elem::Arena(flattened.max).replace_dep(to_replace, replacement.clone(), analyzer); + Elem::Arena(flattened.min).replace_dep( + to_replace, + replacement.clone(), + analyzer, + arena, + ); + Elem::Arena(flattened.max).replace_dep( + to_replace, + replacement.clone(), + analyzer, + arena, + ); } self.min - .replace_dep(to_replace, replacement.clone(), analyzer); - self.max.replace_dep(to_replace, replacement, analyzer); + .replace_dep(to_replace, replacement.clone(), analyzer, arena); + self.max + .replace_dep(to_replace, replacement, analyzer, arena); self.min_cached = None; self.max_cached = None; } - pub fn is_const(&self, analyzer: &impl GraphBackend) -> Result { - let min = self.evaled_range_min(analyzer)?; - let max = self.evaled_range_max(analyzer)?; - Ok(min.range_eq(&max, analyzer)) + pub fn is_const( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result { + let min = self.evaled_range_min(analyzer, arena)?; + let max = self.evaled_range_max(analyzer, arena)?; + Ok(min.range_eq(&max, arena)) } - pub fn min_is_negative(&self, analyzer: &impl GraphBackend) -> Result { - self.min.is_negative(false, analyzer) + pub fn min_is_negative( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result { + self.min.is_negative(false, analyzer, arena) } pub fn default_bool() -> Self { - let min = Elem::Concrete(RangeConcrete { - val: Concrete::Bool(false), - loc: Loc::Implicit, - }); - let max = Elem::Concrete(RangeConcrete { - val: Concrete::Bool(true), - loc: Loc::Implicit, - }); + let min = Elem::Concrete(RangeConcrete::new(Concrete::Bool(false), Loc::Implicit)); + let max = Elem::Concrete(RangeConcrete::new(Concrete::Bool(true), Loc::Implicit)); Self::new(min, max, vec![]) } pub fn from(c: Concrete) -> Option { @@ -152,14 +172,8 @@ impl SolcRange { | c @ Concrete::Bool(_) | c @ Concrete::Address(_) | c @ Concrete::Bytes(_, _) => Some(SolcRange::new( - Elem::Concrete(RangeConcrete { - val: c.clone(), - loc: Loc::Implicit, - }), - Elem::Concrete(RangeConcrete { - val: c, - loc: Loc::Implicit, - }), + Elem::Concrete(RangeConcrete::new(c.clone(), Loc::Implicit)), + Elem::Concrete(RangeConcrete::new(c, Loc::Implicit)), vec![], )), Concrete::String(s) => { @@ -209,26 +223,26 @@ impl SolcRange { Builtin::Uint(size) => { if *size == 256 { Some(SolcRange::new( - Elem::Concrete(RangeConcrete { - val: Concrete::Uint(*size, 0.into()), - loc: Loc::Implicit, - }), - Elem::Concrete(RangeConcrete { - val: Concrete::Uint(*size, U256::MAX), - loc: Loc::Implicit, - }), + Elem::Concrete(RangeConcrete::new( + Concrete::Uint(*size, 0.into()), + Loc::Implicit, + )), + Elem::Concrete(RangeConcrete::new( + Concrete::Uint(*size, U256::MAX), + Loc::Implicit, + )), vec![], )) } else { Some(SolcRange::new( - Elem::Concrete(RangeConcrete { - val: Concrete::Uint(*size, 0.into()), - loc: Loc::Implicit, - }), - Elem::Concrete(RangeConcrete { - val: Concrete::Uint(*size, U256::from(2).pow(U256::from(*size)) - 1), - loc: Loc::Implicit, - }), + Elem::Concrete(RangeConcrete::new( + Concrete::Uint(*size, 0.into()), + Loc::Implicit, + )), + Elem::Concrete(RangeConcrete::new( + Concrete::Uint(*size, U256::from(2).pow(U256::from(*size)) - 1), + Loc::Implicit, + )), vec![], )) } @@ -236,14 +250,14 @@ impl SolcRange { Builtin::Int(size) => { if *size == 256 { Some(SolcRange::new( - Elem::Concrete(RangeConcrete { - val: Concrete::Int(*size, I256::MIN), - loc: Loc::Implicit, - }), - Elem::Concrete(RangeConcrete { - val: Concrete::Int(*size, I256::MAX), - loc: Loc::Implicit, - }), + Elem::Concrete(RangeConcrete::new( + Concrete::Int(*size, I256::MIN), + Loc::Implicit, + )), + Elem::Concrete(RangeConcrete::new( + Concrete::Int(*size, I256::MAX), + Loc::Implicit, + )), vec![], )) } else { @@ -251,38 +265,32 @@ impl SolcRange { I256::from_raw(U256::from(1u8) << U256::from(size - 1)) - I256::from(1); let min = max * I256::from(-1i32) - I256::from(1i32); Some(SolcRange::new( - Elem::Concrete(RangeConcrete { - val: Concrete::Int(*size, min), - loc: Loc::Implicit, - }), - Elem::Concrete(RangeConcrete { - val: Concrete::Int(*size, max), - loc: Loc::Implicit, - }), + Elem::Concrete(RangeConcrete::new( + Concrete::Int(*size, min), + Loc::Implicit, + )), + Elem::Concrete(RangeConcrete::new( + Concrete::Int(*size, max), + Loc::Implicit, + )), vec![], )) } } Builtin::Bool => Some(SolcRange::new( - Elem::Concrete(RangeConcrete { - val: Concrete::Bool(false), - loc: Loc::Implicit, - }), - Elem::Concrete(RangeConcrete { - val: Concrete::Bool(true), - loc: Loc::Implicit, - }), + Elem::Concrete(RangeConcrete::new(Concrete::Bool(false), Loc::Implicit)), + Elem::Concrete(RangeConcrete::new(Concrete::Bool(true), Loc::Implicit)), vec![], )), Builtin::Address | Builtin::Payable | Builtin::AddressPayable => Some(SolcRange::new( - Elem::Concrete(RangeConcrete { - val: Concrete::Address(Address::from_slice(&[0x00; 20])), - loc: Loc::Implicit, - }), - Elem::Concrete(RangeConcrete { - val: Concrete::Address(Address::from_slice(&[0xff; 20])), - loc: Loc::Implicit, - }), + Elem::Concrete(RangeConcrete::new( + Concrete::Address(Address::from_slice(&[0x00; 20])), + Loc::Implicit, + )), + Elem::Concrete(RangeConcrete::new( + Concrete::Address(Address::from_slice(&[0xff; 20])), + Loc::Implicit, + )), vec![], )), Builtin::Bytes(size) => { @@ -290,14 +298,14 @@ impl SolcRange { .map(|i| if i < *size { 0xff } else { 0x00 }) .collect(); Some(SolcRange::new( - Elem::Concrete(RangeConcrete { - val: Concrete::Bytes(*size, H256::from_slice(&[0x00; 32])), - loc: Loc::Implicit, - }), - Elem::Concrete(RangeConcrete { - val: Concrete::Bytes(*size, H256::from_slice(&v[..])), - loc: Loc::Implicit, - }), + Elem::Concrete(RangeConcrete::new( + Concrete::Bytes(*size, H256::from_slice(&[0x00; 32])), + Loc::Implicit, + )), + Elem::Concrete(RangeConcrete::new( + Concrete::Bytes(*size, H256::from_slice(&v[..])), + Loc::Implicit, + )), vec![], )) } @@ -347,10 +355,7 @@ impl SolcRange { self.min, self.max.min( Elem::from(other) - - Elem::Concrete(RangeConcrete { - val: U256::from(1).into(), - loc: Loc::Implicit, - }), + - Elem::Concrete(RangeConcrete::new(U256::from(1).into(), Loc::Implicit)), ), self.exclusions, ) @@ -360,10 +365,7 @@ impl SolcRange { Self::new( self.min.max( Elem::from(other) - + Elem::Concrete(RangeConcrete { - val: U256::from(1).into(), - loc: Loc::Implicit, - }), + + Elem::Concrete(RangeConcrete::new(U256::from(1).into(), Loc::Implicit)), ), self.max, self.exclusions, @@ -395,14 +397,6 @@ impl SolcRange { RangeOp::BitAnd => &Self::bit_and_dyn, RangeOp::BitOr => &Self::bit_or_dyn, RangeOp::BitXor => &Self::bit_xor_dyn, - // RangeOp::And => ( - // &Self::and_dyn, - // (DynSide::Min, DynSide::Max), - // ), - // RangeOp::Or => ( - // &Self::or_dyn, - // (DynSide::Min, DynSide::Max), - // ), e => unreachable!("Comparator operations shouldn't exist in a range: {:?}", e), } } @@ -556,23 +550,24 @@ impl SolcRange { pub fn into_flattened_range( &mut self, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) -> Result { if let Some(cached) = &self.flattened { return Ok(cached.clone()); } - let mut min = Elem::Arena(analyzer.range_arena_idx_or_upsert(self.min.clone())); - let mut max = Elem::Arena(analyzer.range_arena_idx_or_upsert(self.max.clone())); - min.cache_flatten(analyzer)?; - max.cache_flatten(analyzer)?; + let mut min = Elem::Arena(arena.idx_or_upsert(self.min.clone(), analyzer)); + let mut max = Elem::Arena(arena.idx_or_upsert(self.max.clone(), analyzer)); + min.cache_flatten(analyzer, arena)?; + max.cache_flatten(analyzer, arena)?; self.min = min.clone(); self.max = max.clone(); - let simp_min = min.simplify_minimize(analyzer)?; - let simp_max = max.simplify_maximize(analyzer)?; - let min = analyzer.range_arena_idx_or_upsert(simp_min); - let max = analyzer.range_arena_idx_or_upsert(simp_max); + let simp_min = min.simplify_minimize(analyzer, arena)?; + let simp_max = max.simplify_maximize(analyzer, arena)?; + let min = arena.idx_or_upsert(simp_min, analyzer); + let max = arena.idx_or_upsert(simp_max, analyzer); let flat_range = FlattenedRange { min, @@ -601,57 +596,71 @@ impl Range for SolcRange { &mut self.max } - fn cache_eval(&mut self, analyzer: &mut impl GraphBackend) -> Result<(), GraphError> { + fn cache_eval( + &mut self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), GraphError> { let min = std::mem::take(&mut self.min); let max = std::mem::take(&mut self.max); - self.min = Elem::Arena(analyzer.range_arena_idx_or_upsert(min)); - self.max = Elem::Arena(analyzer.range_arena_idx_or_upsert(max)); + self.min = Elem::Arena(arena.idx_or_upsert(min, analyzer)); + self.max = Elem::Arena(arena.idx_or_upsert(max, analyzer)); if self.max_cached.is_none() { let max = self.range_max_mut(); - max.cache_maximize(analyzer)?; - self.max_cached = - Some(analyzer.range_arena_idx_or_upsert(self.range_max().maximize(analyzer)?)); + max.cache_maximize(analyzer, arena)?; + let res = self.range_max().maximize(analyzer, arena)?; + self.max_cached = Some(arena.idx_or_upsert(res, analyzer)); } if self.min_cached.is_none() { let min = self.range_min_mut(); - min.cache_minimize(analyzer)?; - self.min_cached = - Some(analyzer.range_arena_idx_or_upsert(self.range_min().minimize(analyzer)?)); + min.cache_minimize(analyzer, arena)?; + let res = self.range_min().minimize(analyzer, arena)?; + self.min_cached = Some(arena.idx_or_upsert(res, analyzer)); } Ok(()) } - fn evaled_range_min(&self, analyzer: &impl GraphBackend) -> Result { + fn evaled_range_min( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result { if let Some(cached) = &self.min_cached { - Ok(Elem::Arena(*cached).dearenaize(analyzer).borrow().clone()) + Ok(Elem::Arena(*cached).dearenaize_clone(arena)) } else { - self.range_min().minimize(analyzer) + self.range_min().minimize(analyzer, arena) } } - fn evaled_range_max(&self, analyzer: &impl GraphBackend) -> Result { + fn evaled_range_max( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result { if let Some(cached) = &self.max_cached { - Ok(Elem::Arena(*cached).dearenaize(analyzer).borrow().clone()) + Ok(Elem::Arena(*cached).dearenaize_clone(arena)) } else { - self.range_max().maximize(analyzer) + self.range_max().maximize(analyzer, arena) } } fn simplified_range_min( &self, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result { self.range_min() - .flatten(false, analyzer)? - .simplify_minimize(analyzer) + .flatten(false, analyzer, arena)? + .simplify_minimize(analyzer, arena) } fn simplified_range_max( &self, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result { self.range_max() - .flatten(true, analyzer)? - .simplify_maximize(analyzer) + .flatten(true, analyzer, arena)? + .simplify_maximize(analyzer, arena) } fn range_exclusions(&self) -> Vec { @@ -685,21 +694,29 @@ impl Range for SolcRange { self_idx: NodeIdx, new_idx: NodeIdx, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) { - self.min.filter_recursion(self_idx, new_idx, analyzer); + self.min + .filter_recursion(self_idx, new_idx, analyzer, arena); } fn filter_max_recursion( &mut self, self_idx: NodeIdx, new_idx: NodeIdx, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) { - self.max.filter_recursion(self_idx, new_idx, analyzer); + self.max + .filter_recursion(self_idx, new_idx, analyzer, arena); } - fn cache_flatten(&mut self, analyzer: &mut impl GraphBackend) -> Result<(), Self::GraphError> { + fn cache_flatten( + &mut self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result<(), Self::GraphError> { if self.flattened.is_none() { - self.into_flattened_range(analyzer)?; + self.into_flattened_range(analyzer, arena)?; } Ok(()) } @@ -707,12 +724,13 @@ impl Range for SolcRange { fn flattened_range<'a>( &'a mut self, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, Self::GraphError> where Self: Sized + Clone, { if self.flattened.is_none() { - self.cache_flatten(analyzer)?; + self.cache_flatten(analyzer, arena)?; let Some(flat) = &self.flattened else { unreachable!(); }; @@ -728,6 +746,7 @@ impl Range for SolcRange { fn take_flattened_range( &mut self, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) -> Result where Self: Sized, @@ -736,46 +755,56 @@ impl Range for SolcRange { if let Some(flat) = taken { Ok(flat) } else { - self.cache_flatten(analyzer)?; - self.take_flattened_range(analyzer) + self.cache_flatten(analyzer, arena)?; + self.take_flattened_range(analyzer, arena) } } } impl RangeEval> for SolcRange { #[tracing::instrument(level = "trace", skip_all)] - fn sat(&self, analyzer: &impl GraphBackend) -> bool { + fn sat(&self, analyzer: &impl GraphBackend, arena: &mut RangeArena>) -> bool { matches!( - self.evaled_range_min(analyzer) + self.evaled_range_min(analyzer, arena) .unwrap() - .range_ord(&self.evaled_range_max(analyzer).unwrap(), analyzer), + .range_ord(&self.evaled_range_max(analyzer, arena).unwrap(), arena), None | Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) ) } - fn contains(&self, other: &Self, analyzer: &impl GraphBackend) -> bool { + fn contains( + &self, + other: &Self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> bool { let min_contains = matches!( - self.evaled_range_min(analyzer) + self.evaled_range_min(analyzer, arena) .unwrap() - .range_ord(&other.evaled_range_min(analyzer).unwrap(), analyzer), + .range_ord(&other.evaled_range_min(analyzer, arena).unwrap(), arena), Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) ); let max_contains = matches!( - self.evaled_range_max(analyzer) + self.evaled_range_max(analyzer, arena) .unwrap() - .range_ord(&other.evaled_range_max(analyzer).unwrap(), analyzer), + .range_ord(&other.evaled_range_max(analyzer, arena).unwrap(), arena), Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) ); min_contains && max_contains } - fn contains_elem(&self, other: &Elem, analyzer: &impl GraphBackend) -> bool { + fn contains_elem( + &self, + other: &Elem, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> bool { let min_contains = match self - .evaled_range_min(analyzer) + .evaled_range_min(analyzer, arena) .unwrap() - .range_ord(&other.minimize(analyzer).unwrap(), analyzer) + .range_ord(&other.minimize(analyzer, arena).unwrap(), arena) { Some(std::cmp::Ordering::Less) => true, Some(std::cmp::Ordering::Equal) => return true, @@ -783,9 +812,9 @@ impl RangeEval> for SolcRange { }; let max_contains = match self - .evaled_range_max(analyzer) + .evaled_range_max(analyzer, arena) .unwrap() - .range_ord(&other.maximize(analyzer).unwrap(), analyzer) + .range_ord(&other.maximize(analyzer, arena).unwrap(), arena) { Some(std::cmp::Ordering::Greater) => true, Some(std::cmp::Ordering::Equal) => return true, @@ -795,18 +824,23 @@ impl RangeEval> for SolcRange { min_contains && max_contains } - fn overlaps(&self, other: &Self, analyzer: &impl GraphBackend) -> bool { - let lhs_min = self.evaled_range_min(analyzer).unwrap(); - let rhs_max = other.evaled_range_max(analyzer).unwrap(); + fn overlaps( + &self, + other: &Self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> bool { + let lhs_min = self.evaled_range_min(analyzer, arena).unwrap(); + let rhs_max = other.evaled_range_max(analyzer, arena).unwrap(); - match lhs_min.range_ord(&rhs_max, analyzer) { + match lhs_min.range_ord(&rhs_max, arena) { Some(std::cmp::Ordering::Less) => { // we know our min is less than the other max // check that the max is greater than or eq their min - let lhs_max = self.evaled_range_max(analyzer).unwrap(); - let rhs_min = other.evaled_range_min(analyzer).unwrap(); + let lhs_max = self.evaled_range_max(analyzer, arena).unwrap(); + let rhs_min = other.evaled_range_min(analyzer, arena).unwrap(); matches!( - lhs_max.range_ord(&rhs_min, analyzer), + lhs_max.range_ord(&rhs_min, arena), Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) ) } @@ -817,21 +851,37 @@ impl RangeEval> for SolcRange { } impl RangeEval> for FlattenedRange { - fn sat(&self, analyzer: &impl GraphBackend) -> bool { - >::into(self.clone()).sat(analyzer) + fn sat(&self, analyzer: &impl GraphBackend, arena: &mut RangeArena>) -> bool { + >::into(self.clone()).sat(analyzer, arena) } - fn unsat(&self, analyzer: &impl GraphBackend) -> bool { - !self.sat(analyzer) + fn unsat(&self, analyzer: &impl GraphBackend, arena: &mut RangeArena>) -> bool { + !self.sat(analyzer, arena) } - fn contains(&self, other: &Self, analyzer: &impl GraphBackend) -> bool { + fn contains( + &self, + other: &Self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> bool { let other = >::into(other.clone()); - >::into(self.clone()).contains(&other, analyzer) + >::into(self.clone()).contains(&other, analyzer, arena) } - fn contains_elem(&self, other: &Elem, analyzer: &impl GraphBackend) -> bool { - >::into(self.clone()).contains_elem(other, analyzer) + fn contains_elem( + &self, + other: &Elem, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> bool { + >::into(self.clone()) + .contains_elem(other, analyzer, arena) } - fn overlaps(&self, other: &Self, analyzer: &impl GraphBackend) -> bool { + fn overlaps( + &self, + other: &Self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> bool { let other = >::into(other.clone()); - >::into(self.clone()).overlaps(&other, analyzer) + >::into(self.clone()).overlaps(&other, analyzer, arena) } } diff --git a/crates/graph/src/solvers/atoms.rs b/crates/graph/src/solvers/atoms.rs index bdb1351f..27f0d3c3 100644 --- a/crates/graph/src/solvers/atoms.rs +++ b/crates/graph/src/solvers/atoms.rs @@ -8,6 +8,7 @@ use crate::{ }, GraphBackend, }; +use shared::RangeArena; use ethers_core::types::U256; use std::{collections::BTreeMap, rc::Rc}; @@ -42,16 +43,17 @@ impl AtomOrPart { &self, solves: &BTreeMap>, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) -> Self { match self { AtomOrPart::Part(part) => { let mut new_part = part.clone(); solves.iter().for_each(|(dep, replacement)| { - new_part.replace_dep(dep.0.into(), replacement.clone(), analyzer) + new_part.replace_dep(dep.0.into(), replacement.clone(), analyzer, arena) }); AtomOrPart::Part(new_part) } - AtomOrPart::Atom(atom) => AtomOrPart::Atom(atom.replace_deps(solves, analyzer)), + AtomOrPart::Atom(atom) => AtomOrPart::Atom(atom.replace_deps(solves, analyzer, arena)), } } @@ -86,10 +88,14 @@ impl AtomOrPart { } } - pub fn dependent_on(&self, analyzer: &impl GraphBackend) -> Vec { + pub fn dependent_on( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Vec { match self { - AtomOrPart::Part(e) => e.dependent_on(analyzer), - AtomOrPart::Atom(a) => a.dependent_on(analyzer), + AtomOrPart::Part(e) => e.dependent_on(analyzer, arena), + AtomOrPart::Atom(a) => a.dependent_on(analyzer, arena), } } } @@ -126,25 +132,41 @@ pub struct SolverAtom { } impl ToRangeString for SolverAtom { - fn def_string(&self, analyzer: &impl GraphBackend) -> RangeElemString { - self.into_expr_elem().def_string(analyzer) + fn def_string( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> RangeElemString { + self.into_expr_elem().def_string(analyzer, arena) } - fn to_range_string(&self, maximize: bool, analyzer: &impl GraphBackend) -> RangeElemString { - self.into_expr_elem().to_range_string(maximize, analyzer) + fn to_range_string( + &self, + maximize: bool, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> RangeElemString { + self.into_expr_elem() + .to_range_string(maximize, analyzer, arena) } } impl SolverAtom { + pub fn assert_nonnull(&self) { + self.lhs.into_elem().assert_nonnull(); + self.rhs.into_elem().assert_nonnull(); + } + pub fn replace_deps( &self, solves: &BTreeMap>, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) -> Self { SolverAtom { ty: self.ty, - lhs: Rc::new(self.lhs.clone().replace_deps(solves, analyzer)), + lhs: Rc::new(self.lhs.clone().replace_deps(solves, analyzer, arena)), op: self.op, - rhs: Rc::new(self.rhs.clone().replace_deps(solves, analyzer)), + rhs: Rc::new(self.rhs.clone().replace_deps(solves, analyzer, arena)), } } @@ -167,9 +189,13 @@ impl SolverAtom { self.ty = self.max_ty(); } - pub fn dependent_on(&self, analyzer: &impl GraphBackend) -> Vec { - let mut deps = self.lhs.dependent_on(analyzer); - deps.extend(self.rhs.dependent_on(analyzer)); + pub fn dependent_on( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Vec { + let mut deps = self.lhs.dependent_on(analyzer, arena); + deps.extend(self.rhs.dependent_on(analyzer, arena)); deps } @@ -247,32 +273,45 @@ pub static LIA_OPS: &[RangeOp] = &[ ]; pub trait Atomize { - fn atoms_or_part(&self, analyzer: &mut impl GraphBackend) -> AtomOrPart; - fn atomize(&self, analyzer: &mut impl GraphBackend) -> Option; + fn atoms_or_part( + &self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> AtomOrPart; + fn atomize( + &self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Option; } impl Atomize for Elem { #[tracing::instrument(level = "trace", skip_all)] - fn atoms_or_part(&self, analyzer: &mut impl GraphBackend) -> AtomOrPart { + fn atoms_or_part( + &self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> AtomOrPart { match self { - Elem::Arena(_) => self.dearenaize(analyzer).borrow().atoms_or_part(analyzer), + Elem::Arena(_) => self.dearenaize_clone(arena).atoms_or_part(analyzer, arena), Elem::Concrete(_) | Elem::Reference(_) => AtomOrPart::Part(self.clone()), Elem::ConcreteDyn(_) => AtomOrPart::Part(self.clone()), - Elem::Expr(expr) => { - match collapse(&expr.lhs, expr.op, &expr.rhs, analyzer) { + _e @ Elem::Expr(expr) => { + // println!("collapsing: {e}"); + match collapse(*expr.lhs.clone(), expr.op, *expr.rhs.clone(), arena) { MaybeCollapsed::Concretes(_l, _r) => { - let exec_res = expr.exec_op(true, analyzer).unwrap(); - return exec_res.atoms_or_part(analyzer); + let exec_res = expr.exec_op(true, analyzer, arena).unwrap(); + return exec_res.atoms_or_part(analyzer, arena); } MaybeCollapsed::Collapsed(elem) => { - return elem.atoms_or_part(analyzer); + return elem.atoms_or_part(analyzer, arena); } MaybeCollapsed::Not(..) => {} } match ( - expr.lhs.atoms_or_part(analyzer), - expr.rhs.atoms_or_part(analyzer), + expr.lhs.atoms_or_part(analyzer, arena), + expr.rhs.atoms_or_part(analyzer, arena), ) { (ref lp @ AtomOrPart::Part(ref l), ref rp @ AtomOrPart::Part(ref r)) => { // println!("part part"); @@ -325,12 +364,12 @@ impl Atomize for Elem { todo!("here4"); } (Elem::Concrete(_), Elem::Concrete(_)) => { - let _ = expr.clone().arenaize(analyzer); - let res = expr.exec_op(true, analyzer).unwrap(); + let _ = expr.clone().arenaize(analyzer, arena); + let res = expr.exec_op(true, analyzer, arena).unwrap(); if res == Elem::Expr(expr.clone()) { AtomOrPart::Part(res) } else { - res.atoms_or_part(analyzer) + res.atoms_or_part(analyzer, arena) } } (Elem::ConcreteDyn(_), _) => AtomOrPart::Part(Elem::Null), @@ -359,9 +398,13 @@ impl Atomize for Elem { } #[tracing::instrument(level = "trace", skip_all)] - fn atomize(&self, analyzer: &mut impl GraphBackend) -> Option { + fn atomize( + &self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Option { use Elem::*; - + tracing::trace!("atomize: {}", self); match self { Reference(_) => None, //{ println!("was dyn"); None}, Null => None, //{ println!("was null"); None}, @@ -369,24 +412,19 @@ impl Atomize for Elem { ConcreteDyn(_) => None, //{ println!("was concDyn"); None}, Expr(_) => { // println!("atomized: was expr"); - let AtomOrPart::Atom(mut a) = self.atoms_or_part(analyzer) else { + let AtomOrPart::Atom(mut a) = self.atoms_or_part(analyzer, arena) else { // println!("returning none"); return None; }; a.update_max_ty(); Some(a) } - Arena(_) => match &*self.dearenaize(analyzer).borrow() { - e @ Expr(_) => { - let AtomOrPart::Atom(mut a) = e.atoms_or_part(analyzer) else { - // println!("returning none arena"); - return None; - }; - a.update_max_ty(); - Some(a) - } - _ => None, - }, + Arena(_) => { + let (dearenized, idx) = self.dearenaize(arena); + let res = dearenized.atomize(analyzer, arena); + self.rearenaize(dearenized, idx, arena); + res + } } } } diff --git a/crates/graph/src/solvers/brute.rs b/crates/graph/src/solvers/brute.rs index 55c00381..a810c170 100644 --- a/crates/graph/src/solvers/brute.rs +++ b/crates/graph/src/solvers/brute.rs @@ -8,20 +8,24 @@ use crate::{ AnalyzerBackend, GraphBackend, GraphError, Range, RangeEval, SolcRange, }; +use shared::RangeArena; + use ethers_core::types::U256; use std::collections::BTreeMap; pub trait SolcSolver { - fn simplify(&mut self, analyzer: &impl AnalyzerBackend); + fn simplify(&mut self, analyzer: &impl AnalyzerBackend, arena: &mut RangeArena>); fn solve( &mut self, analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, ) -> Result; fn recurse_check( &mut self, idx: usize, solved_atomics: &mut Vec, analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, ) -> Result; fn check( &mut self, @@ -29,6 +33,7 @@ pub trait SolcSolver { lmr: (Elem, Elem, Elem), solved_atomics: &mut Vec, analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, ) -> Result<(bool, Option), GraphError>; } @@ -84,6 +89,7 @@ impl BruteBinSearchSolver { pub fn maybe_new( deps: Vec, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, GraphError> { let mut atomic_idxs = vec![]; @@ -91,14 +97,14 @@ impl BruteBinSearchSolver { let mut atomic_ranges = BTreeMap::default(); deps.iter().try_for_each(|dep| { let mut range = dep.range(analyzer)?.unwrap(); - if range.unsat(analyzer) { + if range.unsat(analyzer, arena) { panic!( "initial range for {} not sat", dep.display_name(analyzer).unwrap() ); } - let r: SolcRange = range.flattened_range(analyzer)?.into_owned().into(); - atomic_idxs.extend(r.dependent_on(analyzer)); + let r: SolcRange = range.flattened_range(analyzer, arena)?.into_owned().into(); + atomic_idxs.extend(r.dependent_on(analyzer, arena)); ranges.insert(*dep, r); Ok(()) })?; @@ -150,7 +156,10 @@ impl BruteBinSearchSolver { atomic_ranges.insert(atomic.clone(), range); Ok(()) })?; - if let Some((dep, unsat_range)) = ranges.iter().find(|(_, range)| range.unsat(analyzer)) { + if let Some((dep, unsat_range)) = ranges + .iter() + .find(|(_, range)| range.unsat(analyzer, arena)) + { panic!( "Initial ranges not sat for dep {}: {} {}", dep.display_name(analyzer).unwrap(), @@ -177,7 +186,7 @@ impl BruteBinSearchSolver { successful_passes: 0, }; - s.reset_lmrs(analyzer); + s.reset_lmrs(analyzer, arena); Ok(Some(s)) } @@ -185,47 +194,63 @@ impl BruteBinSearchSolver { &self, atomic: &Atomic, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) -> (Elem, Elem, Elem) { let range = &self.atomic_ranges[atomic]; - let mut min = range.evaled_range_min(analyzer).unwrap(); - min.cache_minimize(analyzer).unwrap(); - // println!("min: {}", min.minimize(analyzer).unwrap().to_range_string(false, analyzer).s); - let mut max = range.evaled_range_max(analyzer).unwrap(); - max.cache_maximize(analyzer).unwrap(); + let mut min = range.evaled_range_min(analyzer, arena).unwrap(); + min.cache_minimize(analyzer, arena).unwrap(); + // println!("min: {}", min.minimize(analyzer).unwrap().to_range_string(false, analyzer, arena).s); + let mut max = range.evaled_range_max(analyzer, arena).unwrap(); + max.cache_maximize(analyzer, arena).unwrap(); let mut mid = (min.clone() + max.clone()) / Elem::from(Concrete::from(U256::from(2))); - mid.cache_maximize(analyzer).unwrap(); + mid.cache_maximize(analyzer, arena).unwrap(); (min, mid, max) } - pub fn reset_lmrs(&mut self, analyzer: &mut impl GraphBackend) { + pub fn reset_lmrs( + &mut self, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) { self.lmrs = vec![]; (0..self.atomic_ranges.len()).for_each(|i| { - self.lmrs.push(self.lmr(&self.atomics[i], analyzer).into()); + self.lmrs + .push(self.lmr(&self.atomics[i], analyzer, arena).into()); }); } - pub fn reset_lmr(&mut self, i: usize, analyzer: &mut impl GraphBackend) { - self.lmrs[i] = self.lmr(&self.atomics[i], analyzer).into(); + pub fn reset_lmr( + &mut self, + i: usize, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) { + self.lmrs[i] = self.lmr(&self.atomics[i], analyzer, arena).into(); } - pub fn raise_lmr(&mut self, i: usize, analyzer: &impl GraphBackend) -> bool { + pub fn raise_lmr( + &mut self, + i: usize, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> bool { // move the low to low + mid / 2 // reset the mid let mut curr_lmr = self.lmrs[i].clone(); curr_lmr.low = (curr_lmr.low + curr_lmr.mid) / Elem::from(Concrete::from(U256::from(2))) - .minimize(analyzer) + .minimize(analyzer, arena) .unwrap(); curr_lmr.mid = (curr_lmr.low.clone() + curr_lmr.high.clone()) / Elem::from(Concrete::from(U256::from(2))) - .minimize(analyzer) + .minimize(analyzer, arena) .unwrap(); - let new_mid_conc = curr_lmr.mid.maximize(analyzer).unwrap(); - let old_mid_conc = self.lmrs[i].mid.maximize(analyzer).unwrap(); + let new_mid_conc = curr_lmr.mid.maximize(analyzer, arena).unwrap(); + let old_mid_conc = self.lmrs[i].mid.maximize(analyzer, arena).unwrap(); if matches!( - new_mid_conc.range_ord(&old_mid_conc, analyzer), + new_mid_conc.range_ord(&old_mid_conc, arena), Some(std::cmp::Ordering::Equal) ) { return false; @@ -234,27 +259,32 @@ impl BruteBinSearchSolver { true } - pub fn lower_lmr(&mut self, i: usize, analyzer: &impl GraphBackend) -> bool { + pub fn lower_lmr( + &mut self, + i: usize, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> bool { // println!("lowering mid"); // move the high to high + mid / 2 // reset the mid let mut curr_lmr = self.lmrs[i].clone(); - curr_lmr.high = (curr_lmr.mid.minimize(analyzer).unwrap() - + curr_lmr.high.minimize(analyzer).unwrap()) + curr_lmr.high = (curr_lmr.mid.minimize(analyzer, arena).unwrap() + + curr_lmr.high.minimize(analyzer, arena).unwrap()) / Elem::from(Concrete::from(U256::from(2))) - .minimize(analyzer) + .minimize(analyzer, arena) .unwrap(); - curr_lmr.mid = (curr_lmr.low.minimize(analyzer).unwrap() - + curr_lmr.high.minimize(analyzer).unwrap()) + curr_lmr.mid = (curr_lmr.low.minimize(analyzer, arena).unwrap() + + curr_lmr.high.minimize(analyzer, arena).unwrap()) / Elem::from(Concrete::from(U256::from(2))) - .minimize(analyzer) + .minimize(analyzer, arena) .unwrap(); - let new_high_conc = curr_lmr.high.minimize(analyzer).unwrap(); - let old_high_conc = self.lmrs[i].high.minimize(analyzer).unwrap(); + let new_high_conc = curr_lmr.high.minimize(analyzer, arena).unwrap(); + let old_high_conc = self.lmrs[i].high.minimize(analyzer, arena).unwrap(); if matches!( - new_high_conc.range_ord(&old_high_conc, analyzer), + new_high_conc.range_ord(&old_high_conc, arena), Some(std::cmp::Ordering::Equal) ) { return false; @@ -270,19 +300,23 @@ impl BruteBinSearchSolver { } impl SolcSolver for BruteBinSearchSolver { - fn simplify(&mut self, _analyzer: &impl AnalyzerBackend) {} + fn simplify( + &mut self, + _analyzer: &impl AnalyzerBackend, + _arena: &mut RangeArena>, + ) { + } fn solve( &mut self, analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, ) -> Result { // pick a value for a variable. check if it satisfies all dependendies // if is sat, try to reduce using bin search? Not sure how that will // affect other dependencies If it doesnt, // raise or lower - println!("-------------------------"); - println!("DL SOLVER CHECK"); let atoms = self .ranges .iter() @@ -290,20 +324,19 @@ impl SolcSolver for BruteBinSearchSolver { // println!("dep: {}", dep.display_name(analyzer).unwrap()); // println!("atom: {atom:#?}"); - if let Some(atom) = range.min.atomize(analyzer) { + if let Some(atom) = range.min.atomize(analyzer, arena) { Some(atom) } else { - range.max.atomize(analyzer) + range.max.atomize(analyzer, arena) } }) .collect::>(); - let mut dl_solver = DLSolver::new(atoms, analyzer); + let mut dl_solver = DLSolver::new(atoms, analyzer, arena); let mut atomic_solves: BTreeMap<_, _>; - match dl_solver.solve_partial(analyzer)? { + match dl_solver.solve_partial(analyzer, arena)? { SolveStatus::Unsat => { - println!("TRUE UNSAT"); return Ok(AtomicSolveStatus::Unsat); } SolveStatus::Sat { @@ -318,7 +351,11 @@ impl SolcSolver for BruteBinSearchSolver { .iter() .find(|atomic| atomic.idxs.contains(&dep))? .clone(), - solve.maximize(analyzer).unwrap().maybe_concrete()?.val, + solve + .maximize(analyzer, arena) + .unwrap() + .maybe_concrete()? + .val, )) }) .collect(); @@ -331,7 +368,11 @@ impl SolcSolver for BruteBinSearchSolver { .iter() .find(|atomic| atomic.idxs.contains(&dep))? .clone(), - solve.maximize(analyzer).unwrap().maybe_concrete()?.val, + solve + .maximize(analyzer, arena) + .unwrap() + .maybe_concrete()? + .val, )) }) .collect::>(), @@ -346,24 +387,25 @@ impl SolcSolver for BruteBinSearchSolver { .iter() .find(|atomic| atomic.idxs.contains(&dep))? .clone(), - solve.maximize(analyzer).unwrap().maybe_concrete()?.val, + solve + .maximize(analyzer, arena) + .unwrap() + .maybe_concrete()? + .val, )) }) .collect() } } // println!("solved for: {:#?}", atomic_solves); - println!("-------------------------"); if atomic_solves.len() == self.atomics.len() { - println!("DONE HERE"); - return Ok(AtomicSolveStatus::Sat(atomic_solves)); } else { atomic_solves.iter().for_each(|(atomic, val)| { self.intermediate_ranges.iter_mut().for_each(|(_dep, r)| { atomic.idxs.iter().for_each(|idx| { - r.replace_dep(idx.0.into(), Elem::from(val.clone()), analyzer) + r.replace_dep(idx.0.into(), Elem::from(val.clone()), analyzer, arena) }); }); }); @@ -380,22 +422,25 @@ impl SolcSolver for BruteBinSearchSolver { .keys() .filter_map(|k| self.atomics.iter().position(|r| r == k)) .collect(); - while self.recurse_check(self.start_idx, &mut solved_for, analyzer)? {} + while self.recurse_check(self.start_idx, &mut solved_for, analyzer, arena)? {} if self.successful_passes == self.atomics.len() { let mapping = self .intermediate_atomic_ranges .iter() - .filter(|(_, range)| range.is_const(analyzer).unwrap()) - .map(|(name, range)| { - ( - name.clone(), - range - .evaled_range_min(analyzer) - .unwrap() - .maybe_concrete() - .unwrap() - .val, - ) + .filter_map(|(name, range)| { + if !range.is_const(analyzer, arena).ok()? { + None + } else { + Some(( + name.clone(), + range + .evaled_range_min(analyzer, arena) + .unwrap() + .maybe_concrete() + .unwrap() + .val, + )) + } }) .collect::>(); if mapping.len() == self.intermediate_atomic_ranges.len() { @@ -405,12 +450,17 @@ impl SolcSolver for BruteBinSearchSolver { .iter() .for_each(|(atomic, range)| { atomic.idxs.iter().for_each(|idx| { - new_range.replace_dep(idx.0.into(), range.min.clone(), analyzer); + new_range.replace_dep( + idx.0.into(), + range.min.clone(), + analyzer, + arena, + ); }); }); - new_range.cache_eval(analyzer).unwrap(); + new_range.cache_eval(analyzer, arena).unwrap(); // println!("{}, original range: [{}, {}], new range: [{}, {}]", dep.display_name(analyzer).unwrap(), range.min, range.max, new_range.min_cached.clone().unwrap(), new_range.max_cached.clone().unwrap()); - new_range.sat(analyzer) + new_range.sat(analyzer, arena) }); if all_good { Ok(AtomicSolveStatus::Sat(mapping)) @@ -431,6 +481,7 @@ impl SolcSolver for BruteBinSearchSolver { i: usize, solved_atomics: &mut Vec, analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, ) -> Result { // println!("recurse check for: {}", self.atomics[i].idxs[0].display_name(analyzer).unwrap()); if i >= self.lmrs.len() { @@ -448,7 +499,13 @@ impl SolcSolver for BruteBinSearchSolver { let lmr = self.lmrs[i].clone(); // println!("solving: {i}, {}, successful passes: {}", atomic.idxs[0].display_name(analyzer).unwrap(), self.successful_passes); // println!("initial range: [{min_s},{max_s}], is_const: {}", atomic.idxs[0].is_const(analyzer)?); - match self.check(i, (lmr.low, lmr.mid, lmr.high), solved_atomics, analyzer)? { + match self.check( + i, + (lmr.low, lmr.mid, lmr.high), + solved_atomics, + analyzer, + arena, + )? { (true, Some(HintOrRanges::Ranges(new_ranges))) => { // sat, try solving next var with new intermediate ranges solved_atomics.push(i); @@ -461,13 +518,13 @@ impl SolcSolver for BruteBinSearchSolver { self.successful_passes = 0; *solved_atomics = vec![]; // unsat, try raising - if self.raise_lmr(i, analyzer) { - self.recurse_check(i, solved_atomics, analyzer) + if self.raise_lmr(i, analyzer, arena) { + self.recurse_check(i, solved_atomics, analyzer, arena) } else { // we couldn't solve, try increasing global start if self.increase_start() { self.intermediate_ranges = self.ranges.clone(); - self.recurse_check(self.start_idx, solved_atomics, analyzer) + self.recurse_check(self.start_idx, solved_atomics, analyzer, arena) } else { Ok(false) } @@ -477,13 +534,13 @@ impl SolcSolver for BruteBinSearchSolver { // unsat, try lowering self.successful_passes = 0; *solved_atomics = vec![]; - if self.lower_lmr(i, analyzer) { - self.recurse_check(i, solved_atomics, analyzer) + if self.lower_lmr(i, analyzer, arena) { + self.recurse_check(i, solved_atomics, analyzer, arena) } else { // we couldn't solve, try increasing global start if self.increase_start() { self.intermediate_ranges = self.ranges.clone(); - self.recurse_check(self.start_idx, solved_atomics, analyzer) + self.recurse_check(self.start_idx, solved_atomics, analyzer, arena) } else { Ok(false) } @@ -493,13 +550,13 @@ impl SolcSolver for BruteBinSearchSolver { // unsat, try lowering self.successful_passes = 0; *solved_atomics = vec![]; - if self.lower_lmr(i, analyzer) { - self.recurse_check(i, solved_atomics, analyzer) + if self.lower_lmr(i, analyzer, arena) { + self.recurse_check(i, solved_atomics, analyzer, arena) } else { // we couldn't solve, try increasing global start if self.increase_start() { self.intermediate_ranges = self.ranges.clone(); - self.recurse_check(self.start_idx, solved_atomics, analyzer) + self.recurse_check(self.start_idx, solved_atomics, analyzer, arena) } else { Ok(false) } @@ -515,6 +572,7 @@ impl SolcSolver for BruteBinSearchSolver { (low, mid, high): (Elem, Elem, Elem), solved_atomics: &mut Vec, analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, ) -> Result<(bool, Option), GraphError> { let solved_dep = &self.atomics[solved_for_idx].clone(); @@ -528,6 +586,7 @@ impl SolcSolver for BruteBinSearchSolver { mut high_done: bool, solved_atomics: &mut Vec, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) -> Result<(bool, Option), GraphError> { let res = if !low_done { check_for_lmr( @@ -537,6 +596,7 @@ impl SolcSolver for BruteBinSearchSolver { low.clone(), solved_atomics, analyzer, + arena, ) } else if !mid_done { check_for_lmr( @@ -546,6 +606,7 @@ impl SolcSolver for BruteBinSearchSolver { mid.clone(), solved_atomics, analyzer, + arena, ) } else { check_for_lmr( @@ -555,6 +616,7 @@ impl SolcSolver for BruteBinSearchSolver { high.clone(), solved_atomics, analyzer, + arena, ) }; @@ -576,6 +638,7 @@ impl SolcSolver for BruteBinSearchSolver { high_done, solved_atomics, analyzer, + arena, ) } } @@ -590,8 +653,9 @@ impl SolcSolver for BruteBinSearchSolver { conc: Elem, solved_atomics: &mut Vec, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) -> Result<(bool, Option), GraphError> { - // println!("checking: {}, conc: {}, {}", this.atomics[solved_for_idx].idxs[0].display_name(analyzer).unwrap(), conc.maximize(analyzer)?.to_range_string(true, analyzer).s, conc.minimize(analyzer)?.to_range_string(false, analyzer).s); + // println!("checking: {}, conc: {}, {}", this.atomics[solved_for_idx].idxs[0].display_name(analyzer).unwrap(), conc.maximize(analyzer, arena)?.to_range_string(true, analyzer, arena).s, conc.minimize(analyzer)?.to_range_string(false, analyzer, arena).s); solved_atomics.push(solved_for_idx); let mut new_ranges = BTreeMap::default(); this.intermediate_atomic_ranges.insert( @@ -604,25 +668,25 @@ impl SolcSolver for BruteBinSearchSolver { .filter_map(|(_, range)| { if let Some(atom) = range .min - .simplify_minimize(analyzer) + .simplify_minimize(analyzer, arena) .unwrap() - .atomize(analyzer) + .atomize(analyzer, arena) { Some(atom) } else { range .max - .simplify_maximize(analyzer) + .simplify_maximize(analyzer, arena) .unwrap() - .atomize(analyzer) + .atomize(analyzer, arena) } }) .collect::>(); - let mut dl_solver = DLSolver::new(atoms, analyzer); + let mut dl_solver = DLSolver::new(atoms, analyzer, arena); let mut atomic_solves: BTreeMap<_, _>; - match dl_solver.solve_partial(analyzer)? { + match dl_solver.solve_partial(analyzer, arena)? { SolveStatus::Unsat => { println!("TRUE UNSAT"); return Ok((false, None)); @@ -639,7 +703,11 @@ impl SolcSolver for BruteBinSearchSolver { .iter() .find(|atomic| atomic.idxs.contains(&dep))? .clone(), - solve.maximize(analyzer).unwrap().maybe_concrete()?.val, + solve + .maximize(analyzer, arena) + .unwrap() + .maybe_concrete()? + .val, )) }) .collect(); @@ -652,7 +720,11 @@ impl SolcSolver for BruteBinSearchSolver { .iter() .find(|atomic| atomic.idxs.contains(&dep))? .clone(), - solve.maximize(analyzer).unwrap().maybe_concrete()?.val, + solve + .maximize(analyzer, arena) + .unwrap() + .maybe_concrete()? + .val, )) }) .collect::>(), @@ -667,7 +739,11 @@ impl SolcSolver for BruteBinSearchSolver { .iter() .find(|atomic| atomic.idxs.contains(&dep))? .clone(), - solve.maximize(analyzer).unwrap().maybe_concrete()?.val, + solve + .maximize(analyzer, arena) + .unwrap() + .maybe_concrete()? + .val, )) }) .collect() @@ -677,7 +753,7 @@ impl SolcSolver for BruteBinSearchSolver { atomic_solves.iter().for_each(|(atomic, val)| { this.intermediate_ranges.iter_mut().for_each(|(_dep, r)| { atomic.idxs.iter().for_each(|idx| { - r.replace_dep(idx.0.into(), Elem::from(val.clone()), analyzer) + r.replace_dep(idx.0.into(), Elem::from(val.clone()), analyzer, arena) }); }); }); @@ -734,7 +810,7 @@ impl SolcSolver for BruteBinSearchSolver { // check if the new range is dependent on the solved variable let is_dependent_on_solved = new_range - .dependent_on(analyzer) + .dependent_on(analyzer, arena) .iter() .any(|dep| solved_dep.idxs.contains(dep)); @@ -747,15 +823,15 @@ impl SolcSolver for BruteBinSearchSolver { // println!("new range for {} dependent_on: {:?}, replacing {:?}, is dependent on solved: {is_dependent_on_solved}", dep.display_name(analyzer).unwrap(), new_range.dependent_on(), solved_dep.idxs); // println!("dep {}:\n\tinitial range: [{}, {}],\n\tcurr range: [{}, {}]", // dep.display_name(analyzer).unwrap(), - // dep.evaled_range_min(analyzer)?.unwrap().to_range_string(false, analyzer).s, - // dep.evaled_range_max(analyzer)?.unwrap().to_range_string(true, analyzer).s, - // new_range.evaled_range_min(analyzer)?.to_range_string(false, analyzer).s, - // new_range.evaled_range_max(analyzer)?.to_range_string(true, analyzer).s, + // dep.evaled_range_min(analyzer, arena)?.unwrap().to_range_string(false, analyzer, arena).s, + // dep.evaled_range_max(analyzer, arena)?.unwrap().to_range_string(true, analyzer, arena).s, + // new_range.evaled_range_min(analyzer, arena)?.to_range_string(false, analyzer, arena).s, + // new_range.evaled_range_max(analyzer, arena)?.to_range_string(true, analyzer, arena).s, // // new_range.range_min() // ); // println!("dep {} range: {:#?} {:#?}", dep.display_name(analyzer).unwrap(), new_range.min, new_range.max); - if new_range.unsat(analyzer) { + if new_range.unsat(analyzer, arena) { return Ok((false, None)); // panic!("initial range unsat???") } @@ -764,21 +840,21 @@ impl SolcSolver for BruteBinSearchSolver { .idxs .iter() .for_each(|atomic_alias| { - new_range.replace_dep(atomic_alias.0.into(), conc.clone(), analyzer); + new_range.replace_dep(atomic_alias.0.into(), conc.clone(), analyzer, arena); }); - new_range.cache_eval(analyzer)?; + new_range.cache_eval(analyzer, arena)?; // println!("new range: [{}, {}], [{}, {}]", - // new_range.evaled_range_min(analyzer)?.to_range_string(false, analyzer).s, - // new_range.evaled_range_max(analyzer)?.to_range_string(true, analyzer).s, - // new_range.min.to_range_string(false, analyzer).s, - // new_range.max.to_range_string(true, analyzer).s, + // new_range.evaled_range_min(analyzer, arena)?.to_range_string(false, analyzer, arena).s, + // new_range.evaled_range_max(analyzer, arena)?.to_range_string(true, analyzer, arena).s, + // new_range.min.to_range_string(false, analyzer, arena).s, + // new_range.max.to_range_string(true, analyzer, arena).s, // ); - if new_range.unsat(analyzer) { + if new_range.unsat(analyzer, arena) { // figure out *where* we need to increase or decrease // work on the unreplace range for now - let min_is_dependent = !range.min.dependent_on(analyzer).is_empty(); - let max_is_dependent = !range.max.dependent_on(analyzer).is_empty(); + let min_is_dependent = !range.min.dependent_on(analyzer, arena).is_empty(); + let max_is_dependent = !range.max.dependent_on(analyzer, arena).is_empty(); match (min_is_dependent, max_is_dependent) { (true, true) => { @@ -799,18 +875,18 @@ impl SolcSolver for BruteBinSearchSolver { } // println!("new unsat range: [{}, {}]", - // new_range.evaled_range_min(analyzer)?.to_range_string(false, analyzer).s, - // new_range.evaled_range_max(analyzer)?.to_range_string(true, analyzer).s, + // new_range.evaled_range_min(analyzer, arena)?.to_range_string(false, analyzer, arena).s, + // new_range.evaled_range_max(analyzer, arena)?.to_range_string(true, analyzer, arena).s, // ); // compare new range to prev range to see if they moved down or up // panic!("here"); let min_change = new_range - .evaled_range_min(analyzer)? - .range_ord(&range.evaled_range_min(analyzer)?, analyzer); + .evaled_range_min(analyzer, arena)? + .range_ord(&range.evaled_range_min(analyzer, arena)?, arena); let max_change = new_range - .evaled_range_max(analyzer)? - .range_ord(&range.evaled_range_max(analyzer)?, analyzer); + .evaled_range_max(analyzer, arena)? + .range_ord(&range.evaled_range_max(analyzer, arena)?, arena); match (min_change, max_change) { (Some(std::cmp::Ordering::Less), Some(std::cmp::Ordering::Greater)) => { // panic!("initial range must have been unsat to start"); @@ -859,6 +935,7 @@ impl SolcSolver for BruteBinSearchSolver { false, solved_atomics, analyzer, + arena, ) } } diff --git a/crates/graph/src/solvers/dl.rs b/crates/graph/src/solvers/dl.rs index 56783a0e..68782961 100644 --- a/crates/graph/src/solvers/dl.rs +++ b/crates/graph/src/solvers/dl.rs @@ -1,10 +1,13 @@ use crate::{ nodes::{Concrete, ContextVarNode}, range::elem::*, + range::range_string::ToRangeString, solvers::{AtomOrPart, Atomize, OpType, SolverAtom}, GraphBackend, GraphError, }; +use shared::RangeArena; + use ethers_core::types::{I256, U256}; use itertools::Itertools; use petgraph::{ @@ -62,7 +65,11 @@ pub struct DLSolveResult { } impl DLSolver { - pub fn new(mut constraints: Vec, analyzer: &mut impl GraphBackend) -> Self { + pub fn new( + mut constraints: Vec, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, + ) -> Self { constraints.iter_mut().for_each(|c| { c.update_max_ty(); }); @@ -74,7 +81,7 @@ impl DLSolver { root_node, ..Default::default() }; - s.add_constraints(vec![], analyzer); + s.add_constraints(vec![], analyzer, arena); s } @@ -102,11 +109,49 @@ impl DLSolver { &mut self, constraints: Vec, analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) -> BTreeMap>> { // println!("adding constriants: {constraints:#?}"); + let constraints: Vec<_> = constraints + .into_iter() + .flat_map(|mut constraint| { + if let AtomOrPart::Part(c) = &*constraint.lhs { + if let Some(mut c) = c.maybe_concrete() { + c.val = c.val.max_size(); + constraint.lhs = AtomOrPart::Part(Elem::Concrete(c)).into() + } + } + + if let AtomOrPart::Part(c) = &*constraint.rhs { + if let Some(mut c) = c.maybe_concrete() { + c.val = c.val.max_size(); + constraint.rhs = AtomOrPart::Part(Elem::Concrete(c)).into() + } + } + + if constraint.op == RangeOp::And { + vec![ + SolverAtom { + ty: OpType::Const, + lhs: constraint.lhs, + op: RangeOp::Eq, + rhs: Rc::new(AtomOrPart::Part(Elem::from(Concrete::from(true)))), + }, + SolverAtom { + ty: OpType::Const, + lhs: constraint.rhs, + op: RangeOp::Eq, + rhs: Rc::new(AtomOrPart::Part(Elem::from(Concrete::from(true)))), + }, + ] + } else { + vec![constraint] + } + }) + .collect(); let mut dep_to_solve_ty: BTreeMap> = BTreeMap::default(); self.constraints.iter().for_each(|constraint| { - let deps = constraint.dependent_on(analyzer); + let deps = constraint.dependent_on(analyzer, arena); deps.into_iter().for_each(|dep| { if let Some(entry) = dep_to_solve_ty.get_mut(&dep) { if constraint.ty == OpType::Const { @@ -129,9 +174,8 @@ impl DLSolver { .collect(); // println!("unique constraints: {constraints:#?}"); - constraints.iter().for_each(|constraint| { - let deps = constraint.dependent_on(analyzer); + let deps = constraint.dependent_on(analyzer, arena); deps.into_iter().for_each(|dep| { if let Some(entry) = dep_to_solve_ty.get_mut(&dep) { if constraint.ty == OpType::Const { @@ -181,7 +225,7 @@ impl DLSolver { let still_unknown_constraints: Vec<_> = constraints .into_iter() .filter(|constraint| { - let deps = constraint.dependent_on(analyzer); + let deps = constraint.dependent_on(analyzer, arena); !deps.iter().all(|dep| const_solves.contains_key(dep)) }) .cloned() @@ -196,7 +240,7 @@ impl DLSolver { let res = still_unknown_constraints .into_iter() .filter(|constraint| { - let deps = constraint.dependent_on(analyzer); + let deps = constraint.dependent_on(analyzer, arena); deps.iter().all(|dep| { dep_to_solve_ty .get(dep) @@ -208,14 +252,15 @@ impl DLSolver { .collect::>() .iter() .map(|constraint| { - ( - constraint.clone(), - Self::dl_atom_normalize(constraint.clone().clone(), analyzer), - ) + let t = Self::dl_atom_normalize(constraint.clone().clone(), analyzer, arena); + t.map(|t| (constraint.clone(), t)) }) - .collect::>>>(); + .collect::>>>>(); // println!("normalized map: {res:#?}"); - res + match res { + Some(t) => t, + None => Default::default(), + } } pub fn dl_solvable_constraints(&self) -> Vec>> { @@ -224,12 +269,13 @@ impl DLSolver { pub fn solve_partial( &mut self, - analyzer: &impl GraphBackend, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) -> Result { // println!("constraints {:#?}", self.constraints); let mut dep_to_solve_ty: BTreeMap> = BTreeMap::default(); self.constraints.iter().for_each(|constraint| { - let deps = constraint.dependent_on(analyzer); + let deps = constraint.dependent_on(analyzer, arena); deps.into_iter().for_each(|dep| { if let Some(entry) = dep_to_solve_ty.get_mut(&dep) { if constraint.ty == OpType::Const { @@ -250,7 +296,7 @@ impl DLSolver { atoms.iter().any(|atom| { atom.op == RangeOp::Neq && atom.lhs == atom.rhs - && !atom.lhs.dependent_on(analyzer).is_empty() + && !atom.lhs.dependent_on(analyzer, arena).is_empty() }) }) { return Ok(SolveStatus::Unsat); @@ -291,7 +337,7 @@ impl DLSolver { .clone() .into_iter() .filter(|constraint| { - let deps = constraint.dependent_on(analyzer); + let deps = constraint.dependent_on(analyzer, arena); !deps.iter().all(|dep| const_solves.contains_key(dep)) }) .collect(); @@ -342,10 +388,10 @@ impl DLSolver { // check if basics are unsat, if so the extra constraints wont help that // so its truly unsat // println!("basic: {basic:#?}"); - let basic_solve = self.dl_solve(basic.clone(), analyzer)?; - // if matches!(basic_solve.status, SolveStatus::Unsat) { - // return Ok(SolveStatus::Unsat); - // } + let basic_solve = self.dl_solve(basic.clone(), analyzer, arena)?; + if matches!(basic_solve.status, SolveStatus::Unsat) { + return Ok(SolveStatus::Unsat); + } // println!("basic solve: {basic_solve:?}"); @@ -397,7 +443,7 @@ impl DLSolver { .collect(); // add the constant paths flattened.extend(basic.clone()); - let solve = self.dl_solve(flattened, analyzer)?; + let solve = self.dl_solve(flattened, analyzer, arena)?; // remove the added constraints, keeping the basic graph in tact self.remove_added(&solve); // now that we checked that @@ -434,11 +480,22 @@ impl DLSolver { }); } + #[tracing::instrument(level = "trace", skip_all)] pub fn dl_solve( &mut self, normalized_constraints: Vec, - analyzer: &impl GraphBackend, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) -> Result { + if self.graph.node_count() == 0 { + let root_node = self.graph.add_node(AtomOrPart::Part(Elem::Null)); + self.root_node = root_node; + } + + // println!("constraints:"); + // normalized_constraints.iter().for_each(|c| { + // println!("{}", c.into_expr_elem()); + // }); let mut added_atoms = vec![]; let mut added_deps = vec![]; if normalized_constraints.is_empty() { @@ -463,8 +520,8 @@ impl DLSolver { }; let rhs_atom = constraint.rhs.expect_atom(); - let rhs_lhs_deps = rhs_atom.lhs.dependent_on(analyzer); - let rhs_rhs_deps = rhs_atom.rhs.dependent_on(analyzer); + let rhs_lhs_deps = rhs_atom.lhs.dependent_on(analyzer, arena); + let rhs_rhs_deps = rhs_atom.rhs.dependent_on(analyzer, arena); let ((dyn_elem, dep), const_elem) = match (!rhs_lhs_deps.is_empty(), !rhs_rhs_deps.is_empty()) { (true, true) => { @@ -476,7 +533,7 @@ impl DLSolver { if matches!(rhs_atom.op, RangeOp::Sub(_)) { let const_elem = (rhs_atom.rhs.into_elem() * Elem::from(Concrete::from(I256::from(-1)))) - .maximize(analyzer) + .maximize(analyzer, arena) .unwrap(); ( (rhs_atom.lhs, Some(rhs_lhs_deps[0])), @@ -490,7 +547,7 @@ impl DLSolver { if matches!(rhs_atom.op, RangeOp::Sub(_)) { let const_elem = (rhs_atom.lhs.into_elem() * Elem::from(Concrete::from(I256::from(-1)))) - .maximize(analyzer) + .maximize(analyzer, arena) .unwrap(); ( (rhs_atom.rhs, Some(rhs_rhs_deps[0])), @@ -537,7 +594,7 @@ impl DLSolver { ); }); - if find_negative_cycle(&self.graph, root_node, analyzer).is_some() { + if find_negative_cycle(&self.graph, root_node, analyzer, arena).is_some() { return Ok(DLSolveResult { status: SolveStatus::Unsat, added_atoms, @@ -545,13 +602,13 @@ impl DLSolver { }); } - let (mut dists, _) = bellman_ford_initialize_relax(&self.graph, root_node, analyzer); + let (mut dists, _) = bellman_ford_initialize_relax(&self.graph, root_node, analyzer, arena); dists = dists .into_iter() .map(|dist| { (dist * Elem::from(Concrete::from(I256::from(-1)))) - .maximize(analyzer) + .maximize(analyzer, arena) .unwrap() }) .collect(); @@ -584,10 +641,17 @@ impl DLSolver { /// Normalizes a DL atom into x <= y - k, where x and y are variables and k is a constant. /// Needed for running negative cycle check. Additionally, if we have an `OR`, we + #[tracing::instrument(level = "trace", skip_all)] pub fn dl_atom_normalize( constraint: SolverAtom, analyzer: &mut impl GraphBackend, - ) -> Vec> { + arena: &mut RangeArena>, + ) -> Option>> { + constraint.assert_nonnull(); + tracing::trace!( + "constraint: {}, {constraint:#?}", + constraint.to_range_string(false, analyzer, arena).s + ); let zero_part = AtomOrPart::Part(Elem::from(Concrete::from(U256::zero()))); let false_part = AtomOrPart::Part(Elem::from(Concrete::from(false))); let true_part = AtomOrPart::Part(Elem::from(Concrete::from(true))); @@ -599,26 +663,26 @@ impl DLSolver { (true, true) => { if constraint.lhs == constraint.rhs { // true == true || false == false, just disregard this atom - return vec![vec![]]; + return Some(vec![vec![]]); } else { panic!("During normalization of a DL atom, got true == false"); } } (true, false) => { // lhs is just a boolean, drop it - return Self::dl_atom_normalize(constraint.rhs.as_solver_atom(), analyzer); + return Self::dl_atom_normalize(constraint.rhs.as_solver_atom(), analyzer, arena); } (false, true) => { // rhs is just a boolean, drop it - return Self::dl_atom_normalize(constraint.lhs.as_solver_atom(), analyzer); + return Self::dl_atom_normalize(constraint.lhs.as_solver_atom(), analyzer, arena); } _ => {} } // x <==> y // x + x + y => AtomOrPart::Atom(Atom { lhs x, op: +, rhs: AtomOrPart::Atom(Atom { lhs: x, op: +, rhs: y})}) - let lhs_symbs = constraint.lhs.dependent_on(analyzer); - let rhs_symbs = constraint.rhs.dependent_on(analyzer); + let lhs_symbs = constraint.lhs.dependent_on(analyzer, arena); + let rhs_symbs = constraint.rhs.dependent_on(analyzer, arena); let constraint = match (!lhs_symbs.is_empty(), !rhs_symbs.is_empty()) { (true, true) => { // TODO: in theory could have x x + y @@ -711,7 +775,7 @@ impl DLSolver { }; // println!("normalizing: {}", constraint.into_expr_elem()); - match constraint.op { + let res = match constraint.op { RangeOp::Eq => { // convert `x == y` into `x <= y - 0 || y <= x - 0` let mut res = Self::dl_atom_normalize( @@ -727,7 +791,8 @@ impl DLSolver { })), }, analyzer, - ); + arena, + )?; assert!(res.len() == 1); res[0].extend( @@ -744,10 +809,11 @@ impl DLSolver { })), }, analyzer, - ) + arena, + )? .remove(0), ); - res + Some(res) } RangeOp::Neq => { // convert `x != y` into `x <= y - 1 || y <= x - 1` @@ -766,7 +832,8 @@ impl DLSolver { })), }, analyzer, - ); + arena, + )?; assert!(res.len() == 1); @@ -786,10 +853,11 @@ impl DLSolver { })), }, analyzer, - ) + arena, + )? .remove(0), ); - res + Some(res) } RangeOp::Lt => { // x < y @@ -798,7 +866,7 @@ impl DLSolver { .rhs .into_elem() .wrapping_sub(Elem::from(Concrete::from(U256::one()))) - .atoms_or_part(analyzer); + .atoms_or_part(analyzer, arena); Self::dl_atom_normalize( SolverAtom { ty: OpType::DL, @@ -807,6 +875,7 @@ impl DLSolver { rhs: Rc::new(new_rhs), }, analyzer, + arena, ) } RangeOp::Gte => Self::dl_atom_normalize( @@ -817,6 +886,7 @@ impl DLSolver { rhs: constraint.lhs, }, analyzer, + arena, ), RangeOp::Gt => Self::dl_atom_normalize( SolverAtom { @@ -826,21 +896,24 @@ impl DLSolver { rhs: constraint.lhs, }, analyzer, + arena, ), RangeOp::Or => { - let mut res = Self::dl_atom_normalize(constraint.lhs.as_solver_atom(), analyzer); + let mut res = + Self::dl_atom_normalize(constraint.lhs.as_solver_atom(), analyzer, arena)?; res.extend(Self::dl_atom_normalize( constraint.rhs.as_solver_atom(), analyzer, - )); - res + arena, + )?); + Some(res) } RangeOp::Lte => { if constraint.lhs.is_atom() { // some form of (x + k <= y) let lhs_atom = constraint.lhs.expect_atom(); - let atom_lhs_is_symb = !lhs_atom.lhs.dependent_on(analyzer).is_empty(); - let atom_rhs_is_symb = !lhs_atom.rhs.dependent_on(analyzer).is_empty(); + let atom_lhs_is_symb = !lhs_atom.lhs.dependent_on(analyzer, arena).is_empty(); + let atom_rhs_is_symb = !lhs_atom.rhs.dependent_on(analyzer, arena).is_empty(); match lhs_atom.op { RangeOp::Sub(_) => { @@ -862,6 +935,7 @@ impl DLSolver { })), }, analyzer, + arena, ) } _ => { @@ -880,6 +954,7 @@ impl DLSolver { })), }, analyzer, + arena, ) } } @@ -894,6 +969,7 @@ impl DLSolver { rhs: constraint.rhs, }, analyzer, + arena, ) } else if lhs_atom.rhs == zero_part.into() { Self::dl_atom_normalize( @@ -904,9 +980,28 @@ impl DLSolver { rhs: constraint.rhs, }, analyzer, + arena, + ) + } else if lhs_atom.lhs.dependent_on(analyzer, arena).is_empty() { + // (k + x <= y) + // ==> (x <= y - k) + Self::dl_atom_normalize( + SolverAtom { + ty: constraint.ty, + lhs: lhs_atom.rhs, + op: constraint.op, + rhs: Rc::new(AtomOrPart::Atom(SolverAtom { + ty: constraint.ty, + lhs: constraint.rhs, + op: RangeOp::Sub(true), + rhs: lhs_atom.lhs, + })), + }, + analyzer, + arena, ) } else { - // (k + x <= y) || (x + k <= y) + // (x + k <= y) // ==> (x <= y - k) Self::dl_atom_normalize( SolverAtom { @@ -921,6 +1016,7 @@ impl DLSolver { })), }, analyzer, + arena, ) } } @@ -933,7 +1029,8 @@ impl DLSolver { rhs: constraint.rhs.clone(), }, analyzer, - ); + arena, + )?; let mut rhs = Self::dl_atom_normalize( SolverAtom { @@ -943,8 +1040,9 @@ impl DLSolver { rhs: constraint.rhs.clone(), }, analyzer, - ); - match (res.len() > 1, rhs.len() > 1) { + arena, + )?; + let res = match (res.len() > 1, rhs.len() > 1) { (true, true) => { res.extend(rhs); res @@ -961,41 +1059,9 @@ impl DLSolver { res[0].extend(rhs.remove(0)); res } - } + }; + Some(res) } - // RangeOp::Eq => { - // // (atom.lhs == atom.rhs) <= rhs - // // try just swapping - // // rhs >= - // let new_lhs_atom = SolverAtom { - // ty: constraint.ty, - // lhs: lhs_atom.lhs, - // op: RangeOp::Sub(true), - // rhs: lhs_atom.rhs - // }; - // Self::dl_atom_normalize(SolverAtom { - // ty: constraint.ty, - // lhs: Box::new(AtomOrPart::Atom(new_lhs_atom)), - // op: constraint.op, - // rhs: constraint.rhs.clone(), - // }) - // } - // RangeOp::Neq => { - // // (atom.lhs != atom.rhs) <= rhs - // // (atom.lhs - atom.rhs) <= rhs - // let new_lhs_atom = SolverAtom { - // ty: constraint.ty, - // lhs: lhs_atom.lhs, - // op: RangeOp::Sub(true), - // rhs: lhs_atom.rhs - // }; - // Self::dl_atom_normalize(SolverAtom { - // ty: constraint.ty, - // lhs: Box::new(AtomOrPart::Atom(new_lhs_atom)), - // op: constraint.op, - // rhs: constraint.rhs.clone(), - // }) - // } other => panic!("other op: {}, {constraint:#?}", other.to_string()), } } else if constraint.rhs.is_part() { @@ -1014,29 +1080,36 @@ impl DLSolver { rhs: Rc::new(new_rhs), }, analyzer, + arena, ) } else { - vec![vec![constraint]] + Some(vec![vec![constraint]]) } } - _other => { - // println!("other: {}, {}", other.to_string(), constraint.into_expr_elem()); - Self::dl_atom_normalize(constraint, analyzer) + other => { + tracing::trace!( + "unable to dl normalize -- other: {}, {}", + other.to_string(), + constraint.into_expr_elem() + ); + None } - } + }; + res } } pub fn find_negative_cycle( g: &DLGraph, source: NodeIndex, - analyzer: &impl GraphBackend, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) -> Option>> { let ix = |i| g.to_index(i); let mut path = Vec::>::new(); // Step 1: initialize and relax - let (distance, predecessor) = bellman_ford_initialize_relax(g, source, analyzer); + let (distance, predecessor) = bellman_ford_initialize_relax(g, source, analyzer, arena); // Step 2: Check for negative weight cycle 'outer: for i in g.node_identifiers() { @@ -1044,10 +1117,10 @@ pub fn find_negative_cycle( let j = edge.target(); let w = edge.weight(); let dist = (distance[ix(i)].clone() + w.into_elem()) - .maximize(analyzer) + .maximize(analyzer, arena) .unwrap(); let lt = matches!( - dist.range_ord(&distance[ix(j)], analyzer), + dist.range_ord(&distance[ix(j)], arena), Some(std::cmp::Ordering::Less) ); if lt { @@ -1104,7 +1177,8 @@ pub fn find_negative_cycle( fn bellman_ford_initialize_relax( g: &DLGraph, source: NodeIndex, - analyzer: &impl GraphBackend, + analyzer: &mut impl GraphBackend, + arena: &mut RangeArena>, ) -> (Vec>, Vec>>) { // Step 1: initialize graph let mut predecessor = vec![None; g.node_bound()]; @@ -1120,10 +1194,10 @@ fn bellman_ford_initialize_relax( let j = edge.target(); let w = edge.weight(); let dist = (distance[ix(i)].clone() + w.into_elem()) - .maximize(analyzer) + .maximize(analyzer, arena) .unwrap(); let lt = matches!( - dist.range_ord(&distance[ix(j)], analyzer), + dist.range_ord(&distance[ix(j)], arena), Some(std::cmp::Ordering::Less) ); if lt { diff --git a/crates/graph/src/var_type.rs b/crates/graph/src/var_type.rs index b56d1df0..23112944 100644 --- a/crates/graph/src/var_type.rs +++ b/crates/graph/src/var_type.rs @@ -9,6 +9,7 @@ use crate::{ }, AnalyzerBackend, AsDotStr, GraphBackend, GraphError, Node, }; +use shared::RangeArena; use shared::NodeIdx; @@ -22,7 +23,11 @@ pub enum VarType { } impl AsDotStr for VarType { - fn as_dot_str(&self, analyzer: &impl GraphBackend) -> String { + fn as_dot_str( + &self, + analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, + ) -> String { self.as_string(analyzer).unwrap() } } @@ -488,15 +493,19 @@ impl VarType { } } - pub fn is_const(&self, analyzer: &impl GraphBackend) -> Result { + pub fn is_const( + &self, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + ) -> Result { match self { Self::Concrete(_) => Ok(true), Self::User(TypeNode::Func(_), _) => Ok(false), _ => { if let Some(range) = self.ref_range(analyzer)? { - let min = range.evaled_range_min(analyzer)?; - let max = range.evaled_range_max(analyzer)?; - Ok(min.range_eq(&max, analyzer)) + let min = range.evaled_range_min(analyzer, arena)?; + let max = range.evaled_range_max(analyzer, arena)?; + Ok(min.range_eq(&max, arena)) } else { Ok(false) } @@ -514,11 +523,12 @@ impl VarType { pub fn evaled_range( &self, analyzer: &impl GraphBackend, + arena: &mut RangeArena>, ) -> Result, Elem)>, GraphError> { Ok(self.ref_range(analyzer)?.map(|range| { ( - range.evaled_range_min(analyzer).unwrap(), - range.evaled_range_max(analyzer).unwrap(), + range.evaled_range_min(analyzer, arena).unwrap(), + range.evaled_range_max(analyzer, arena).unwrap(), ) })) } @@ -533,13 +543,13 @@ impl VarType { // Self::BuiltIn(node, Some(r)) => { // if let Builtin::Bytes(size) = node.underlying(analyzer)? { // if r.is_const(analyzer)? && index.is_const(analyzer)? { - // let Some(min) = r.evaled_range_min(analyzer)?.maybe_concrete() else { + // let Some(min) = r.evaled_range_min(analyzer, arena)?.maybe_concrete() else { // return Ok(None); // }; // let Concrete::Bytes(_, val) = min.val else { // return Ok(None); // }; - // let Some(idx) = index.evaled_range_min(analyzer)?.unwrap().maybe_concrete() + // let Some(idx) = index.evaled_range_min(analyzer, arena)?.unwrap().maybe_concrete() // else { // return Ok(None); // }; @@ -570,7 +580,7 @@ impl VarType { // _ => false, // }, // c @ Elem::Concrete(..) if is_const => { - // let index_val = index.evaled_range_min(analyzer).unwrap().unwrap(); + // let index_val = index.evaled_range_min(analyzer, arena).unwrap().unwrap(); // index_val.range_eq(c) // } // _ => false, @@ -589,7 +599,7 @@ impl VarType { // Self::Concrete(node) => { // if index.is_const(analyzer)? { // let idx = index - // .evaled_range_min(analyzer) + // .evaled_range_min(analyzer, arena) // .unwrap() // .unwrap() // .concrete() diff --git a/crates/pyrometer/benches/parse.rs b/crates/pyrometer/benches/parse.rs index 1f2f0e58..d4e5a37f 100644 --- a/crates/pyrometer/benches/parse.rs +++ b/crates/pyrometer/benches/parse.rs @@ -1,6 +1,6 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use pyrometer::{Analyzer, SourcePath}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::env::{self}; use std::fs; @@ -23,17 +23,18 @@ criterion_main!(benches); /// /// A vector of tuples representing the targets. fn get_targets(bench_contracts_root: PathBuf) -> Vec<(String, PathBuf, usize)> { - let mut targets = vec![]; - targets.push(( - "ctoken".to_string(), - bench_contracts_root.join("flat_ctoken.sol"), - 50, // range of tens ms - )); - targets.push(( - "comptroller".to_string(), - bench_contracts_root.join("flat_comptroller.sol"), - 10, // range of singles seconds. 10 samples is lowest - )); + let targets = vec![ + ( + "ctoken".to_string(), + bench_contracts_root.join("flat_ctoken.sol"), + 50, // range of tens ms + ), + ( + "comptroller".to_string(), + bench_contracts_root.join("flat_comptroller.sol"), + 10, // range of singles seconds. 10 samples is lowest + ), + ]; targets } @@ -59,8 +60,8 @@ fn bench(c: &mut Criterion) { parsing_group.sample_size(sample_size); let sol = fs::read_to_string(path.clone()).expect("Could not find file"); let bench_id = BenchmarkId::new("parse", sample_size); - parsing_group.bench_with_input(bench_id, &(path, &sol), |b, (path, &ref sol)| { - b.iter(|| parse(path, sol.clone())); + parsing_group.bench_with_input(bench_id, &(path, &sol), |b, (path, sol)| { + b.iter(|| parse(path, sol.to_string())); }); parsing_group.finish(); } @@ -72,8 +73,10 @@ fn bench(c: &mut Criterion) { /// /// * `path` - A `PathBuf` representing the path to the source code file. /// * `sol` - A string containing the Solidity source code. -fn parse(path: &PathBuf, sol: String) { +fn parse(path: &Path, sol: String) { let mut analyzer = Analyzer::default(); - let current_path = SourcePath::SolidityFile(path.clone()); - let _maybe_entry = analyzer.parse(&sol, ¤t_path, true); + let mut arena_base = Default::default(); + let arena = &mut arena_base; + let current_path = SourcePath::SolidityFile(path.to_path_buf()); + let _maybe_entry = analyzer.parse(arena, &sol, ¤t_path, true); } diff --git a/crates/pyrometer/src/analyzer.rs b/crates/pyrometer/src/analyzer.rs index 0bae6119..8dee588d 100644 --- a/crates/pyrometer/src/analyzer.rs +++ b/crates/pyrometer/src/analyzer.rs @@ -22,11 +22,9 @@ use solang_parser::{ }; use std::{ - cell::RefCell, collections::BTreeMap, fs, path::{Path, PathBuf}, - rc::Rc, }; /// A path to either a single solidity file or a Solc Standard JSON file @@ -138,6 +136,8 @@ pub struct Analyzer { pub join_stats: JoinStats, /// An arena of ranges pub range_arena: RangeArena>, + /// Parsed functions + pub handled_funcs: Vec, } impl Default for Analyzer { @@ -166,13 +166,14 @@ impl Default for Analyzer { fn_calls_fns: Default::default(), join_stats: JoinStats::default(), range_arena: RangeArena { - ranges: vec![Rc::new(RefCell::new(Elem::Null))], + ranges: vec![Elem::Null], map: { let mut map: AHashMap, usize> = Default::default(); map.insert(Elem::Null, 0); map }, }, + handled_funcs: Vec::default(), }; a.builtin_fn_inputs = builtin_fns::builtin_fns_inputs(&mut a); @@ -198,7 +199,11 @@ impl Default for Analyzer { } impl Analyzer { - pub fn stats(&self, duration: std::time::Duration) -> String { + pub fn stats( + &self, + duration: std::time::Duration, + arena: &RangeArena>, + ) -> String { let num_nodes = self.graph.node_count(); let num_contracts = self.number_of_contracts(); let num_funcs = self.number_of_functions(); @@ -233,7 +238,7 @@ impl Analyzer { format!(""), format!( " Unique Range Elements: {}", - self.range_arena.ranges.len() + arena.ranges.len() ), format!(""), format!( @@ -340,6 +345,7 @@ impl Analyzer { pub fn complicated_parse( &mut self, + arena: &mut RangeArena>, expr: &Expression, parent: Option, ) -> Option { @@ -368,7 +374,7 @@ impl Analyzer { }; let full_stmt = solang_parser::pt::Statement::Return(expr.loc(), Some(expr.clone())); - self.parse_ctx_statement(&full_stmt, false, Some(ctx)); + self.parse_ctx_statement(arena, &full_stmt, false, Some(ctx)); let edges = self.add_if_err(ctx.successful_edges(self).into_expr_err(expr.loc()))?; if edges.len() == 1 { let res = edges[0].return_nodes(self).into_expr_err(expr.loc()); @@ -481,8 +487,13 @@ impl Analyzer { } #[tracing::instrument(level = "trace", skip_all)] - pub fn parse(&mut self, src: &str, current_path: &SourcePath, entry: bool) -> Option { - // tracing::trace!("parsing: {:?}", current_path); + pub fn parse( + &mut self, + arena: &mut RangeArena>, + src: &str, + current_path: &SourcePath, + entry: bool, + ) -> Option { let file_no = self.file_no; self.sources .push((current_path.clone(), src.to_string(), Some(file_no), None)); @@ -491,11 +502,16 @@ impl Analyzer { let parent = self.add_node(Node::SourceUnit(graph::nodes::SourceUnit::new(file_no))); self.add_edge(parent, self.entry, Edge::Source); - let final_pass_part = - self.parse_source_unit(source_unit, file_no, parent.into(), current_path); + let final_pass_part = self.parse_source_unit( + arena, + source_unit, + file_no, + parent.into(), + current_path, + ); self.final_pass_items.push(final_pass_part); if entry { - self.final_pass(); + self.final_pass(arena); } Some(parent) @@ -508,8 +524,13 @@ impl Analyzer { } } - pub fn final_pass(&mut self) { + pub fn final_pass(&mut self, arena: &mut RangeArena>) { let elems = self.final_pass_items.clone(); + elems.iter().for_each(|final_pass_item| { + final_pass_item.funcs.iter().for_each(|func| { + func.set_params_and_ret(self, arena).unwrap(); + }); + }); elems.iter().for_each(|final_pass_item| { final_pass_item .inherits @@ -517,20 +538,22 @@ impl Analyzer { .for_each(|(contract, inherits)| { contract.inherit(inherits.to_vec(), self); }); - final_pass_item.funcs.iter().for_each(|func| { - // add params now that parsing is done - func.set_params_and_ret(self).unwrap(); - }); + // final_pass_item.funcs.iter().for_each(|func| { + // // add params now that parsing is done + // func.set_params_and_ret(self, arena).unwrap(); + // }); final_pass_item .usings .iter() .for_each(|(using, scope_node)| { - self.parse_using(using, *scope_node); + self.parse_using(arena, using, *scope_node); }); final_pass_item.vars.iter().for_each(|(var, parent)| { let loc = var.underlying(self).unwrap().loc; - let res = var.parse_initializer(self, *parent).into_expr_err(loc); + let res = var + .parse_initializer(self, arena, *parent) + .into_expr_err(loc); let _ = self.add_if_err(res); }); }); @@ -540,7 +563,6 @@ impl Analyzer { .funcs .iter() .for_each(|func| self.analyze_fn_calls(*func)); - let mut handled_funcs = vec![]; let mut func_mapping = BTreeMap::default(); let mut call_dep_graph: StableGraph = StableGraph::default(); let fn_calls_fns = std::mem::take(&mut self.fn_calls_fns); @@ -566,9 +588,9 @@ impl Analyzer { call_dep_graph.add_edge(func_idx, call_idx, 0); }); } else { - handled_funcs.push(func); + self.handled_funcs.push(*func); if let Some(body) = &func.underlying(self).unwrap().body.clone() { - self.parse_ctx_statement(body, false, Some(*func)); + self.parse_ctx_statement(arena, body, false, Some(*func)); } } }); @@ -583,18 +605,18 @@ impl Analyzer { indices.iter().for_each(|idx| { let func = call_dep_graph.node_weight(*idx).unwrap(); - if !handled_funcs.contains(&func) { - handled_funcs.push(func); + if !self.handled_funcs.contains(func) { + self.handled_funcs.push(*func); if let Some(body) = &func.underlying(self).unwrap().body.clone() { - self.parse_ctx_statement(body, false, Some(*func)); + self.parse_ctx_statement(arena, body, false, Some(*func)); } } }); final_pass_item.funcs.into_iter().for_each(|func| { - if !handled_funcs.contains(&&func) { + if !self.handled_funcs.contains(&func) { if let Some(body) = &func.underlying(self).unwrap().body.clone() { - self.parse_ctx_statement(body, false, Some(func)); + self.parse_ctx_statement(arena, body, false, Some(func)); } } }); @@ -606,6 +628,7 @@ impl Analyzer { #[tracing::instrument(level = "trace", skip_all)] pub fn parse_source_unit( &mut self, + arena: &mut RangeArena>, source_unit: SourceUnit, file_no: usize, parent: SourceUnitNode, @@ -621,6 +644,7 @@ impl Analyzer { .enumerate() .for_each(|(unit_part, source_unit_part)| { let (sup, funcs, usings, inherits, vars) = self.parse_source_unit_part( + arena, source_unit_part, file_no, unit_part, @@ -639,6 +663,7 @@ impl Analyzer { #[tracing::instrument(level = "trace", skip_all)] pub fn parse_source_unit_part( &mut self, + arena: &mut RangeArena>, sup: &SourceUnitPart, file_no: usize, unit_part: usize, @@ -668,7 +693,7 @@ impl Analyzer { match sup { ContractDefinition(def) => { let (node, funcs, con_usings, unhandled_inherits, unhandled_vars) = - self.parse_contract_def(def, parent); + self.parse_contract_def(arena, def, parent); s_node.add_contract(node, self).unwrap(); self.add_edge(node, sup_node, Edge::Contract); func_nodes.extend(funcs); @@ -677,7 +702,7 @@ impl Analyzer { vars.extend(unhandled_vars); } StructDefinition(def) => { - let node = self.parse_struct_def(def); + let node = self.parse_struct_def(arena, def); s_node.add_struct(node, self).unwrap(); self.add_edge(node, sup_node, Edge::Struct); } @@ -686,11 +711,11 @@ impl Analyzer { self.add_edge(node, sup_node, Edge::Enum); } ErrorDefinition(def) => { - let node = self.parse_err_def(def); + let node = self.parse_err_def(arena, def); self.add_edge(node, sup_node, Edge::Error); } VariableDefinition(def) => { - let (node, maybe_func, needs_final_pass) = self.parse_var_def(def, false); + let (node, maybe_func, needs_final_pass) = self.parse_var_def(arena, def, false); s_node.add_constant(node, self).unwrap(); if let Some(func) = maybe_func { let func = self.handle_func(func, None); @@ -712,7 +737,7 @@ impl Analyzer { self.add_edge(node, sup_node, Edge::Func); } TypeDefinition(def) => { - let node = self.parse_ty_def(def); + let node = self.parse_ty_def(arena, def); self.add_edge(node, sup_node, Edge::Ty); } EventDefinition(_def) => todo!(), @@ -721,7 +746,7 @@ impl Analyzer { StraySemicolon(_loc) => todo!(), PragmaDirective(_, _, _) => {} ImportDirective(import) => { - self.parse_import(import, current_path, parent); + self.parse_import(arena, import, current_path, parent); } } @@ -731,6 +756,7 @@ impl Analyzer { #[tracing::instrument(level = "trace", skip_all)] pub fn parse_import( &mut self, + arena: &mut RangeArena>, import: &Import, current_path: &SourcePath, parent: SourceUnitNode, @@ -959,7 +985,7 @@ impl Analyzer { *optional_file_no = Some(file_no); } - let maybe_entry = self.parse(&sol, &remapped, false); + let maybe_entry = self.parse(arena, &sol, &remapped, false); // take self.sources entry with the same path as remapped and update the entry node if let Some((_, _, _, optional_entry)) = self.sources.iter_mut().find(|(path, _, _, _)| { @@ -977,6 +1003,7 @@ impl Analyzer { #[tracing::instrument(level = "trace", skip_all)] pub fn parse_contract_def( &mut self, + arena: &mut RangeArena>, contract_def: &ContractDefinition, source: SourceUnitNode, ) -> ( @@ -1028,7 +1055,7 @@ impl Analyzer { let mut vars = vec![]; contract_def.parts.iter().for_each(|cpart| match cpart { StructDefinition(def) => { - let node = self.parse_struct_def(def); + let node = self.parse_struct_def(arena, def); self.add_edge(node, con_node, Edge::Struct); } EnumDefinition(def) => { @@ -1036,11 +1063,11 @@ impl Analyzer { self.add_edge(node, con_node, Edge::Enum); } ErrorDefinition(def) => { - let node = self.parse_err_def(def); + let node = self.parse_err_def(arena, def); self.add_edge(node, con_node, Edge::Error); } VariableDefinition(def) => { - let (node, maybe_func, needs_final_pass) = self.parse_var_def(def, true); + let (node, maybe_func, needs_final_pass) = self.parse_var_def(arena, def, true); if let Some(func) = maybe_func { func_nodes.push(self.handle_func(func, Some(con_node))); } @@ -1056,7 +1083,7 @@ impl Analyzer { func_nodes.push(node); } TypeDefinition(def) => { - let node = self.parse_ty_def(def); + let node = self.parse_ty_def(arena, def); self.add_edge(node, con_node, Edge::Ty); } EventDefinition(_def) => {} @@ -1068,7 +1095,12 @@ impl Analyzer { } #[tracing::instrument(level = "trace", skip_all)] - pub fn parse_using(&mut self, using_def: &Using, scope_node: NodeIdx) { + pub fn parse_using( + &mut self, + arena: &mut RangeArena>, + using_def: &Using, + scope_node: NodeIdx, + ) { tracing::trace!("Parsing \"using\" {:?}", using_def); let Some(ref using_def_ty) = using_def.ty else { self.add_expr_err(ExprErr::Todo( @@ -1077,7 +1109,7 @@ impl Analyzer { )); return; }; - let maybe_cvar_idx = self.parse_expr(using_def_ty, None); + let maybe_cvar_idx = self.parse_expr(arena, using_def_ty, None); let ty_idx = match VarType::try_from_idx(self, maybe_cvar_idx) { Some(v_ty) => v_ty.ty_idx(), None => { @@ -1208,7 +1240,11 @@ impl Analyzer { } #[tracing::instrument(level = "trace", skip_all)] - pub fn parse_struct_def(&mut self, struct_def: &StructDefinition) -> StructNode { + pub fn parse_struct_def( + &mut self, + arena: &mut RangeArena>, + struct_def: &StructDefinition, + ) -> StructNode { tracing::trace!("Parsing struct {:?}", struct_def.name); let strukt = Struct::from(struct_def.clone()); @@ -1227,7 +1263,7 @@ impl Analyzer { }; struct_def.fields.iter().for_each(|field| { - let f = Field::new(self, field.clone()); + let f = Field::new(self, arena, field.clone()); let field_node = self.add_node(f); self.add_edge(field_node, strukt_node, Edge::Field); }); @@ -1235,11 +1271,15 @@ impl Analyzer { } #[tracing::instrument(level = "trace", skip_all)] - pub fn parse_err_def(&mut self, err_def: &ErrorDefinition) -> ErrorNode { + pub fn parse_err_def( + &mut self, + arena: &mut RangeArena>, + err_def: &ErrorDefinition, + ) -> ErrorNode { tracing::trace!("Parsing error {:?}", err_def); let err_node = ErrorNode(self.add_node(Error::from(err_def.clone())).index()); err_def.fields.iter().for_each(|field| { - let param = ErrorParam::new(self, field.clone()); + let param = ErrorParam::new(self, arena, field.clone()); let field_node = self.add_node(param); self.add_edge(field_node, err_node, Edge::ErrorParam); }); @@ -1314,11 +1354,12 @@ impl Analyzer { pub fn parse_var_def( &mut self, + arena: &mut RangeArena>, var_def: &VariableDefinition, in_contract: bool, ) -> (VarNode, Option, bool) { tracing::trace!("Parsing variable definition: {:?}", var_def.name); - let var = Var::new(self, var_def.clone(), in_contract); + let var = Var::new(self, arena, var_def.clone(), in_contract); let mut func = None; if var.is_public() { func = Some(Function::from(var_def.clone())); @@ -1330,9 +1371,13 @@ impl Analyzer { (var_node, func, needs_final_pass) } - pub fn parse_ty_def(&mut self, ty_def: &TypeDefinition) -> TyNode { + pub fn parse_ty_def( + &mut self, + arena: &mut RangeArena>, + ty_def: &TypeDefinition, + ) -> TyNode { tracing::trace!("Parsing type definition"); - let ty = Ty::new(self, ty_def.clone()); + let ty = Ty::new(self, arena, ty_def.clone()); let name = ty.name.name.clone(); let ty_node: TyNode = if let Some(user_ty_node) = self.user_types.get(&name).cloned() { let unresolved = self.node_mut(user_ty_node); diff --git a/crates/pyrometer/src/analyzer_backend.rs b/crates/pyrometer/src/analyzer_backend.rs index 97de48d9..b4834d50 100644 --- a/crates/pyrometer/src/analyzer_backend.rs +++ b/crates/pyrometer/src/analyzer_backend.rs @@ -1,22 +1,38 @@ use crate::Analyzer; use graph::{ + elem::Elem, nodes::{ - BlockNode, Builtin, Concrete, ConcreteNode, Function, FunctionNode, FunctionParam, - FunctionParamNode, FunctionReturn, MsgNode, + BlockNode, Builtin, Concrete, ConcreteNode, ContextVar, Function, FunctionNode, + FunctionParam, FunctionParamNode, FunctionReturn, MsgNode, }, AnalyzerBackend, Edge, Node, VarType, }; -use shared::{AnalyzerLike, GraphLike, JoinStats, NodeIdx}; +use shared::{AnalyzerLike, GraphLike, JoinStats, NodeIdx, RangeArena}; use solc_expressions::{ExprErr, IntoExprErr}; use ahash::AHashMap; use ethers_core::types::U256; -use solang_parser::{helpers::CodeLocation, pt::Expression}; +use solang_parser::{ + helpers::CodeLocation, + pt::{Expression, Loc}, +}; use std::collections::BTreeMap; -impl AnalyzerBackend for Analyzer {} +impl AnalyzerBackend for Analyzer { + fn add_concrete_var( + &mut self, + ctx: graph::nodes::ContextNode, + concrete: Concrete, + loc: Loc, + ) -> Result { + let cnode = self.add_node(Node::Concrete(concrete)); + let var = ContextVar::new_from_concrete(loc, ctx, cnode.into(), self); + let cnode = self.add_node(Node::ContextVar(var.into_expr_err(loc)?)); + Ok(cnode.into()) + } +} impl AnalyzerLike for Analyzer { type Expr = Expression; @@ -96,11 +112,16 @@ impl AnalyzerLike for Analyzer { &mut self.user_types } - fn parse_expr(&mut self, expr: &Expression, parent: Option) -> NodeIdx { + fn parse_expr( + &mut self, + arena: &mut RangeArena>, + expr: &Expression, + parent: Option, + ) -> NodeIdx { use Expression::*; match expr { Type(_loc, ty) => { - if let Some(builtin) = Builtin::try_from_ty(ty.clone(), self) { + if let Some(builtin) = Builtin::try_from_ty(ty.clone(), self, arena) { if let Some(idx) = self.builtins.get(&builtin) { *idx } else { @@ -108,7 +129,7 @@ impl AnalyzerLike for Analyzer { self.builtins.insert(builtin, idx); idx } - } else if let Some(idx) = self.complicated_parse(expr, parent) { + } else if let Some(idx) = self.complicated_parse(arena, expr, parent) { self.add_if_err(idx.expect_single().into_expr_err(expr.loc())) .unwrap_or(0.into()) } else { @@ -125,7 +146,7 @@ impl AnalyzerLike for Analyzer { } } ArraySubscript(_loc, ty_expr, None) => { - let inner_ty = self.parse_expr(ty_expr, parent); + let inner_ty = self.parse_expr(arena, ty_expr, parent); if let Some(var_type) = VarType::try_from_idx(self, inner_ty) { let dyn_b = Builtin::Array(var_type); if let Some(idx) = self.builtins.get(&dyn_b) { @@ -140,8 +161,8 @@ impl AnalyzerLike for Analyzer { } } ArraySubscript(loc, ty_expr, Some(idx_expr)) => { - let inner_ty = self.parse_expr(ty_expr, parent); - let idx = self.parse_expr(idx_expr, parent); + let inner_ty = self.parse_expr(arena, ty_expr, parent); + let idx = self.parse_expr(arena, idx_expr, parent); if let Some(var_type) = VarType::try_from_idx(self, inner_ty) { let res = ConcreteNode::from(idx) .underlying(self) @@ -179,7 +200,7 @@ impl AnalyzerLike for Analyzer { self.add_node(Node::Concrete(Concrete::Uint(256, val))) } _ => { - if let Some(idx) = self.complicated_parse(expr, parent) { + if let Some(idx) = self.complicated_parse(arena, expr, parent) { self.add_if_err(idx.expect_single().into_expr_err(expr.loc())) .unwrap_or(0.into()) } else { @@ -247,4 +268,12 @@ impl AnalyzerLike for Analyzer { fn join_stats_mut(&mut self) -> &mut JoinStats { &mut self.join_stats } + + fn handled_funcs(&self) -> &[FunctionNode] { + &self.handled_funcs + } + + fn handled_funcs_mut(&mut self) -> &mut Vec { + &mut self.handled_funcs + } } diff --git a/crates/pyrometer/src/graph_backend.rs b/crates/pyrometer/src/graph_backend.rs index bb859a23..3512cbdd 100644 --- a/crates/pyrometer/src/graph_backend.rs +++ b/crates/pyrometer/src/graph_backend.rs @@ -1,13 +1,10 @@ use crate::Analyzer; use graph::elem::Elem; -use graph::elem::RangeElem; use graph::nodes::Concrete; use shared::RangeArena; -use std::cell::RefCell; -use std::collections::hash_map::DefaultHasher; -use std::hash::Hash; -use std::hash::Hasher; -use std::rc::Rc; +// use std::collections::hash_map::DefaultHasher; +// use std::hash::Hash; +// use std::hash::Hasher; use graph::{ as_dot_str, nodes::ContextNode, AnalyzerBackend, AsDotStr, ContextEdge, Edge, GraphBackend, @@ -42,78 +39,28 @@ impl GraphLike for Analyzer { &mut self.range_arena } - fn range_arena_idx(&self, elem: &Self::RangeElem) -> Option { - if let Elem::Arena(idx) = elem { - Some(*idx) - } else { - self.range_arena().map.get(elem).copied() - } - } - - fn range_arena_idx_or_upsert(&mut self, elem: Self::RangeElem) -> usize { - // tracing::trace!("arenaizing: {}", elem); - if let Elem::Arena(idx) = elem { - return idx; - } - - if let Some(idx) = self.range_arena_idx(&elem) { - let existing = &self.range_arena().ranges[idx]; - let Ok(existing) = existing.try_borrow_mut() else { - return idx; - }; - let (min_cached, max_cached) = existing.is_min_max_cached(self); - let mut existing_count = 0; - if min_cached { - existing_count += 1; - } - if max_cached { - existing_count += 1; - } - if existing.is_flatten_cached(self) { - existing_count += 1; - } - - let (min_cached, max_cached) = elem.is_min_max_cached(self); - let mut new_count = 0; - if min_cached { - new_count += 1; - } - if max_cached { - new_count += 1; - } - if elem.is_flatten_cached(self) { - new_count += 1; - } - - drop(existing); - - if new_count >= existing_count { - self.range_arena_mut().ranges[idx] = Rc::new(RefCell::new(elem)); - } - - idx - } else { - let idx = self.range_arena().ranges.len(); - self.range_arena_mut() - .ranges - .push(Rc::new(RefCell::new(elem.clone()))); - self.range_arena_mut().map.insert(elem, idx); - idx - } - } + // fn range_arena_idx(&self, elem: &Self::RangeElem) -> Option { + // if let Elem::Arena(idx) = elem { + // Some(*idx) + // } else { + // self.range_arena().map.get(elem).copied() + // } + // } } -fn calculate_hash(t: &T) -> u64 { - let mut s = DefaultHasher::new(); - t.hash(&mut s); - s.finish() -} +// fn calculate_hash(t: &T) -> u64 { +// let mut s = DefaultHasher::new(); +// t.hash(&mut s); +// s.finish() +// } impl GraphBackend for Analyzer {} impl GraphDot for Analyzer { + type T = Elem; fn cluster_str( &self, + arena: &mut RangeArena>, node: NodeIdx, cluster_num: &mut usize, is_killed: bool, @@ -177,10 +124,15 @@ impl GraphDot for Analyzer { let child_node_str = children .iter() .filter_map(|child| { + if handled_nodes.lock().unwrap().contains(child) { + return None; + } + let post_str = match self.node(*child) { Node::Context(c) => { *cluster_num += 2; if let Some(inner) = self.cluster_str( + arena, *child, cluster_num, c.killed.is_some(), @@ -203,6 +155,7 @@ impl GraphDot for Analyzer { let r_ctx = ContextNode::from(*r_fork); *cluster_num += 1; let l_fork = if let Some(inner) = self.cluster_str( + arena, *l_fork, cluster_num, l_ctx.is_killed(self).ok()?, @@ -218,6 +171,7 @@ impl GraphDot for Analyzer { *cluster_num += 2; let r_fork = if let Some(inner) = self.cluster_str( + arena, *r_fork, cluster_num, r_ctx.is_killed(self).ok()?, @@ -239,6 +193,7 @@ impl GraphDot for Analyzer { let func = child_iter.next()?; let func_ctx = ContextNode::from(*func); if let Some(inner) = self.cluster_str( + arena, *func, cluster_num, func_ctx.is_killed(self).ok()?, @@ -262,12 +217,18 @@ impl GraphDot for Analyzer { children .iter() .map(|child| { - // if !handled_nodes.lock().unwrap().contains(child) { - // return None - // } else { - // handled_nodes.lock().unwrap().insert(*child); - // } - mermaid_node(self, &indent, *child, true, Some(&curr_cluster_name)) + if !handled_nodes.lock().unwrap().contains(child) { + handled_nodes.lock().unwrap().insert(*child); + } + mermaid_node( + self, + arena, + &indent, + *child, + true, + true, + Some(&curr_cluster_name), + ) }) .collect::>() .join("\n") @@ -276,25 +237,30 @@ impl GraphDot for Analyzer { }; if as_mermaid { - if !post_str.is_empty() { - Some(post_str) - } else { - if !handled_nodes.lock().unwrap().contains(child) { - return None; + if handled_nodes.lock().unwrap().contains(child) { + return if !post_str.is_empty() { + Some(post_str) } else { - handled_nodes.lock().unwrap().insert(*child); - } - Some(mermaid_node( + None + }; + } else { + handled_nodes.lock().unwrap().insert(*child); + } + Some(format!( + "{}\n{indent}{post_str}", + mermaid_node( self, + arena, &indent, *child, true, + true, Some(&curr_cluster_name), - )) - } + ) + )) } else { { - if !handled_nodes.lock().unwrap().contains(child) { + if handled_nodes.lock().unwrap().contains(child) { return None; } else { handled_nodes.lock().unwrap().insert(*child); @@ -303,7 +269,7 @@ impl GraphDot for Analyzer { Some(format!( "{indent}{} [label = \"{}\", color = \"{}\"]\n{}", petgraph::graph::GraphIndex::index(child), - as_dot_str(*child, g).replace('\"', "\'"), + as_dot_str(*child, g, arena).replace('\"', "\'"), self.node(*child).dot_str_color(), post_str )) @@ -324,7 +290,7 @@ impl GraphDot for Analyzer { if as_mermaid { format!("{indent}{from:} -->|\"{edge_str}\"| {to:}\n{indent}class {to} linkSource{edge_idx}\n{indent}class {from} linkTarget{edge_idx}") } else { - format!("{indent}{from:} -> {to:} [label = \"{edge_str}\"]",) + format!("{indent}{from:} -> {to:} [label = \"{edge_str}\"]",) } }) .collect::>() @@ -338,7 +304,15 @@ impl GraphDot for Analyzer { { handled_nodes.lock().unwrap().insert(node); } - mermaid_node(self, &indent, node, true, Some(&curr_cluster_name)) + mermaid_node( + self, + arena, + &indent, + node, + true, + true, + Some(&curr_cluster_name), + ) } }; @@ -360,30 +334,27 @@ impl GraphDot for Analyzer { )) } else { Some(format!( - "{preindent}subgraph cluster_{} {{\n{}\n{}\n{}\n{}\n}}", + "{preindent}subgraph cluster_{} {{\n{indent}{}\n{indent}{} [label = \"{}\", color = \"{}\"]\n{}\n{}\n}}", cluster_num, if is_killed && curr_cluster % 2 == 0 { - format!("{indent}bgcolor=\"#7a0b0b\"") + "bgcolor=\"#7a0b0b\"" } else if is_killed { - format!("{indent}bgcolor=\"#e04646\"") + "bgcolor=\"#e04646\"" } else if curr_cluster % 2 == 0 { - format!("{indent}bgcolor=\"#545e87\"") + "bgcolor=\"#545e87\"" } else { - format!("{indent}bgcolor=\"#1a1b26\"") + "bgcolor=\"#1a1b26\"" }, - format!( - "{indent}{} [label = \"{}\", color = \"{}\"]", - node.index(), - as_dot_str(node, g).replace('\"', "\'"), - self.node(node).dot_str_color() - ), + node.index(), + as_dot_str(node, g, arena).replace('\"', "\'"), + self.node(node).dot_str_color(), child_node_str, edge_str, )) } } - fn dot_str(&self) -> String + fn dot_str(&self, arena: &mut RangeArena>) -> String where Self: std::marker::Sized, Self: AnalyzerBackend, @@ -420,6 +391,7 @@ impl GraphDot for Analyzer { Node::Function(_) => { cluster_num += 2; Some(self.cluster_str( + arena, *node, &mut cluster_num, false, @@ -432,7 +404,7 @@ impl GraphDot for Analyzer { n => Some(format!( " {} [label = \"{}\", color = \"{}\"]", petgraph::graph::GraphIndex::index(node), - as_dot_str(*node, self).replace('\"', "\'"), + as_dot_str(*node, self, arena).replace('\"', "\'"), n.dot_str_color() )), } @@ -469,7 +441,7 @@ impl GraphDot for Analyzer { dot_str.join("\n") } - fn dot_str_no_tmps(&self) -> String + fn dot_str_no_tmps(&self, arena: &mut RangeArena>) -> String where Self: std::marker::Sized, Self: GraphLike + AnalyzerBackend, @@ -514,7 +486,7 @@ impl GraphDot for Analyzer { let inner = match node_ref { Node::ContextVar(cvar) => { let range_str = if let Some(r) = cvar.ty.ref_range(self).unwrap() { - r.as_dot_str(self) + r.as_dot_str(self, &mut arena.clone()) // format!("[{}, {}]", r.min.eval(self).to_range_string(self).s, r.max.eval(self).to_range_string(self).s) } else { "".to_string() @@ -527,7 +499,7 @@ impl GraphDot for Analyzer { range_str ) } - _ => as_dot_str(idx, &G { graph: &new_graph }), + _ => as_dot_str(idx, &G { graph: &new_graph }, &mut arena.clone()), }; format!( "label = \"{}\", color = \"{}\"", @@ -543,7 +515,7 @@ impl GraphDot for Analyzer { dot_str.join("\n") } - fn mermaid_str(&self) -> String + fn mermaid_str(&self, arena: &mut RangeArena>) -> String where Self: std::marker::Sized, Self: AnalyzerBackend, @@ -596,6 +568,7 @@ flowchart BT Node::Function(_) => { cluster_num += 2; Some(self.cluster_str( + arena, *node, &mut cluster_num, false, @@ -611,7 +584,7 @@ flowchart BT Some(format!( " {}(\"{}\")\n style {} stroke:{}", petgraph::graph::GraphIndex::index(node), - as_dot_str(*node, self).replace('\"', "\'"), + as_dot_str(*node, self, arena).replace('\"', "\'"), petgraph::graph::GraphIndex::index(node), n.dot_str_color() )) @@ -671,29 +644,23 @@ impl GraphLike for G<'_> { fn range_arena_mut(&mut self) -> &mut RangeArena> { panic!("Should not call this") } - - fn range_arena_idx(&self, elem: &Self::RangeElem) -> Option { - panic!("Should not call this") - } - - fn range_arena_idx_or_upsert(&mut self, _elem: Self::RangeElem) -> usize { - panic!("Should not call this") - } } impl GraphBackend for G<'_> {} pub fn mermaid_node( g: &impl GraphBackend, + arena: &mut RangeArena>, indent: &str, node: NodeIdx, style: bool, + loc: bool, class: Option<&str>, ) -> String { let mut node_str = format!( "{indent}{}(\"{}\")", petgraph::graph::GraphIndex::index(&node), - as_dot_str(node, g).replace('\"', "\'"), + as_dot_str(node, g, arena).replace('\"', "\'"), ); if style { @@ -704,6 +671,22 @@ pub fn mermaid_node( )); } + if loc { + match g.node(node) { + Node::ContextVar(..) => { + if let solang_parser::pt::Loc::File(f, s, e) = + graph::nodes::ContextVarNode::from(node).loc(g).unwrap() + { + node_str.push_str(&format!( + "\n{indent}class {} loc_{f}_{s}_{e}", + petgraph::graph::GraphIndex::index(&node) + )); + } + } + _ => {} + } + } + if let Some(class) = class { node_str.push_str(&format!( "\n{indent}class {} {class}", diff --git a/crates/pyrometer/tests/helpers.rs b/crates/pyrometer/tests/helpers.rs index 3a14b641..02f28c97 100644 --- a/crates/pyrometer/tests/helpers.rs +++ b/crates/pyrometer/tests/helpers.rs @@ -2,9 +2,14 @@ use analyzers::FunctionVarsBoundAnalyzer; use analyzers::ReportConfig; use analyzers::ReportDisplay; use ariadne::sources; -use graph::{nodes::FunctionNode, Edge}; +use graph::{ + elem::Elem, + nodes::{Concrete, FunctionNode}, + Edge, +}; use pyrometer::{Analyzer, SourcePath}; use shared::NodeIdx; +use shared::RangeArena; use shared::Search; use std::collections::BTreeMap; use std::collections::HashMap; @@ -13,8 +18,10 @@ use std::path::PathBuf; pub fn assert_no_parse_errors(path_str: String) { let sol = std::fs::read_to_string(path_str.clone()).unwrap(); let mut analyzer = Analyzer::default(); + let mut arena_base = Default::default(); + let arena = &mut arena_base; let current_path = SourcePath::SolidityFile(PathBuf::from(path_str.clone())); - let _ = analyzer.parse(&sol, ¤t_path, true); + let _ = analyzer.parse(arena, &sol, ¤t_path, true); assert!( analyzer.expr_errs.is_empty(), "Analyzer encountered parse errors in {}", @@ -24,22 +31,30 @@ pub fn assert_no_parse_errors(path_str: String) { pub fn assert_no_ctx_killed(path_str: String, sol: &str) { let mut analyzer = Analyzer::default(); + let mut arena_base = Default::default(); + let arena = &mut arena_base; let current_path = SourcePath::SolidityFile(PathBuf::from(path_str.clone())); - let maybe_entry = analyzer.parse(sol, ¤t_path, true); + let maybe_entry = analyzer.parse(arena, sol, ¤t_path, true); let entry = maybe_entry.unwrap(); - no_ctx_killed(analyzer, entry); + no_ctx_killed(analyzer, arena, entry); } pub fn remapping_assert_no_ctx_killed(path_str: String, remapping_file: String, sol: &str) { let mut analyzer = Analyzer::default(); analyzer.set_remappings_and_root(remapping_file); let current_path = SourcePath::SolidityFile(PathBuf::from(path_str.clone())); - let maybe_entry = analyzer.parse(sol, ¤t_path, true); + let mut arena_base = Default::default(); + let arena = &mut arena_base; + let maybe_entry = analyzer.parse(arena, sol, ¤t_path, true); let entry = maybe_entry.unwrap(); - no_ctx_killed(analyzer, entry); + no_ctx_killed(analyzer, arena, entry); } -pub fn no_ctx_killed(mut analyzer: Analyzer, entry: NodeIdx) { +pub fn no_ctx_killed( + mut analyzer: Analyzer, + arena: &mut RangeArena>, + entry: NodeIdx, +) { assert!( analyzer.expr_errs.is_empty(), "Analyzer encountered parse errors" @@ -78,17 +93,17 @@ pub fn no_ctx_killed(mut analyzer: Analyzer, entry: NodeIdx) { if let Some(ctx) = FunctionNode::from(func).maybe_body_ctx(&mut analyzer) { if ctx.killed_loc(&analyzer).unwrap().is_some() { analyzer - .bounds_for_all(&file_mapping, ctx, config) + .bounds_for_all(arena, &file_mapping, ctx, config) .as_cli_compat(&file_mapping) - .print_reports(&mut source_map, &analyzer); + .print_reports(&mut source_map, &analyzer, arena); panic!("Killed context in test"); } ctx.all_edges(&analyzer).unwrap().iter().for_each(|subctx| { if subctx.killed_loc(&analyzer).unwrap().is_some() { analyzer - .bounds_for_all(&file_mapping, *subctx, config) + .bounds_for_all(arena, &file_mapping, *subctx, config) .as_cli_compat(&file_mapping) - .print_reports(&mut source_map, &analyzer); + .print_reports(&mut source_map, &analyzer, arena); panic!("Killed context in test"); } }); diff --git a/crates/pyrometer/tests/no_killed_ctxs.rs b/crates/pyrometer/tests/no_killed_ctxs.rs index afd170a7..b56de34f 100644 --- a/crates/pyrometer/tests/no_killed_ctxs.rs +++ b/crates/pyrometer/tests/no_killed_ctxs.rs @@ -150,8 +150,7 @@ fn test_interface() { fn test_const_var() { let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); let path_str = format!("{manifest_dir}/tests/test_data/const_var.sol"); - let sol = include_str!("./test_data/const_var.sol"); - assert_no_ctx_killed(path_str, sol); + assert_no_parse_errors(path_str); } #[test] diff --git a/crates/pyrometer/tests/test_data/bitwise.sol b/crates/pyrometer/tests/test_data/bitwise.sol index 8c34d50d..2d198c18 100644 --- a/crates/pyrometer/tests/test_data/bitwise.sol +++ b/crates/pyrometer/tests/test_data/bitwise.sol @@ -137,7 +137,7 @@ contract BitNot { require(~type(uint24).max == 0); require(bit_not(50) == 115792089237316195423570985008687907853269984665640564039457584007913129639885); } - + function int_bit_not(int256 x) public returns (int256) { return ~x; } diff --git a/crates/pyrometer/tests/test_data/cast.sol b/crates/pyrometer/tests/test_data/cast.sol index d2cd7041..b31a7228 100644 --- a/crates/pyrometer/tests/test_data/cast.sol +++ b/crates/pyrometer/tests/test_data/cast.sol @@ -291,4 +291,4 @@ contract FuncCast { bytes memory data = hex"01234567"; } -} \ No newline at end of file +} diff --git a/crates/pyrometer/tests/test_data/const_var.sol b/crates/pyrometer/tests/test_data/const_var.sol index eb5c9da9..a30ec42a 100644 --- a/crates/pyrometer/tests/test_data/const_var.sol +++ b/crates/pyrometer/tests/test_data/const_var.sol @@ -32,4 +32,4 @@ contract ConstVar { bytes16 _bytesString = "0123456789abcdef"; require(bytesString == _bytesString); } -} \ No newline at end of file +} diff --git a/crates/pyrometer/tests/test_data/intrinsics.sol b/crates/pyrometer/tests/test_data/intrinsics.sol index 3f984539..f9754dc9 100644 --- a/crates/pyrometer/tests/test_data/intrinsics.sol +++ b/crates/pyrometer/tests/test_data/intrinsics.sol @@ -253,4 +253,4 @@ contract Other { interface IOther { function dummyFunc() external returns (uint256); -} \ No newline at end of file +} diff --git a/crates/pyrometer/tests/test_data/math.sol b/crates/pyrometer/tests/test_data/math.sol index 963f87c6..014a72d4 100644 --- a/crates/pyrometer/tests/test_data/math.sol +++ b/crates/pyrometer/tests/test_data/math.sol @@ -236,6 +236,44 @@ contract Mul { } } +contract Exp { + function exp(uint256 x, uint256 y) public pure returns (uint256) { + return x ** y; + } + + function int_exp(int256 x, uint256 y) public pure returns (int256) { + return x ** y; + } + + function exp_conc() public pure returns (uint256) { + uint256 a1 = exp(0, 0); + require(a1 == 1); + uint256 a2 = exp(0, 1); + require(a2 == 0); + uint256 a3 = exp(100, 4); + require(a3 == 100000000); + uint256 a4 = exp(100, 8); + require(a4 == 10000000000000000); + uint256 a5 = exp(1000000000, 8); + require(a5 == 1000000000000000000000000000000000000000000000000000000000000000000000000); + uint256 a6 = exp(2, 24); + require(a6 == 16777216); + } + + function int_exp_conc() public pure { + int256 a1 = int_exp(-100, 0); + require(a1 == 1); + int256 a2 = int_exp(-100, 2); + require(a2 == 10000); + int256 a3 = int_exp(-100, 3); + require(a3 == -1000000); + int256 a4 = int_exp(-100, 8); + require(a4 == 10000000000000000); + int256 a5 = int_exp(-2, 23); + require(a5 == -8388608); + } +} + contract Add { function add(uint256 x, uint256 y) public pure returns (uint256) { return x + y; @@ -641,4 +679,4 @@ contract Unchecked { a := mul(a, b) } } -} \ No newline at end of file +} diff --git a/crates/pyrometer/tests/test_data/storage.sol b/crates/pyrometer/tests/test_data/storage.sol index 135f3dad..25749895 100644 --- a/crates/pyrometer/tests/test_data/storage.sol +++ b/crates/pyrometer/tests/test_data/storage.sol @@ -40,4 +40,4 @@ contract Storage { nestedArr[idx][idx2] = 1000; require(nestedArr[idx][idx2] == 1000); } -} \ No newline at end of file +} diff --git a/crates/shared/src/analyzer_like.rs b/crates/shared/src/analyzer_like.rs index 7c901612..e4cd7b25 100644 --- a/crates/shared/src/analyzer_like.rs +++ b/crates/shared/src/analyzer_like.rs @@ -1,4 +1,4 @@ -use crate::{GraphLike, NodeIdx}; +use crate::{GraphLike, NodeIdx, RangeArena}; use ahash::AHashMap; @@ -129,7 +129,12 @@ pub trait AnalyzerLike: GraphLike { fn max_width(&self) -> usize; fn user_types(&self) -> &AHashMap; fn user_types_mut(&mut self) -> &mut AHashMap; - fn parse_expr(&mut self, expr: &Self::Expr, parent: Option) -> NodeIdx; + fn parse_expr( + &mut self, + arena: &mut RangeArena, + expr: &Self::Expr, + parent: Option, + ) -> NodeIdx; fn msg(&mut self) -> Self::MsgNode; fn block(&mut self) -> Self::BlockNode; fn entry(&self) -> NodeIdx; @@ -176,4 +181,6 @@ pub trait AnalyzerLike: GraphLike { } fn join_stats_mut(&mut self) -> &mut JoinStats; + fn handled_funcs(&self) -> &[Self::FunctionNode]; + fn handled_funcs_mut(&mut self) -> &mut Vec; } diff --git a/crates/shared/src/graph_like.rs b/crates/shared/src/graph_like.rs index e1c7ba6c..041f99aa 100644 --- a/crates/shared/src/graph_like.rs +++ b/crates/shared/src/graph_like.rs @@ -7,10 +7,8 @@ use petgraph::{ }; use std::{ - cell::RefCell, collections::BTreeSet, hash::Hash, - rc::Rc, sync::{Arc, Mutex}, }; @@ -20,7 +18,7 @@ pub type RangeArenaIdx = usize; #[derive(Default, Clone, Debug)] pub struct RangeArena { - pub ranges: Vec>>, + pub ranges: Vec, pub map: AHashMap, } @@ -28,7 +26,7 @@ pub struct RangeArena { pub trait GraphLike { type Node; type Edge: Ord + PartialEq + Heirarchical + Copy; - type RangeElem: Hash + PartialEq + Eq + PartialOrd + Clone + std::fmt::Display; + type RangeElem: Hash + PartialEq + Eq + PartialOrd + Clone + std::fmt::Display + Default; /// Get a mutable reference to the graph fn graph_mut(&mut self) -> &mut Graph; /// Get a reference to the graph @@ -63,16 +61,26 @@ pub trait GraphLike { fn range_arena(&self) -> &RangeArena; fn range_arena_mut(&mut self) -> &mut RangeArena; + fn try_take_range_arena(&mut self) -> Option> { + let arena = self.range_arena_mut(); + if !arena.ranges.is_empty() { + Some(std::mem::take(arena)) + } else { + None + } + } - fn range_arena_idx(&self, elem: &Self::RangeElem) -> Option; - - fn range_arena_idx_or_upsert(&mut self, elem: Self::RangeElem) -> usize; + fn take_range_arena(&mut self) -> RangeArena { + let arena = self.range_arena_mut(); + std::mem::take(arena) + } } /// A trait that constructs dot-like visualization strings (either mermaid or graphviz) pub trait GraphDot: GraphLike { + type T: Hash; /// Open a dot using graphviz - fn open_dot(&self) + fn open_dot(&self, arena: &mut RangeArena) where Self: std::marker::Sized, Self: AnalyzerLike, @@ -88,7 +96,7 @@ pub trait GraphDot: GraphLike { let temp_svg_filename: String = format!("{}/dot.svg", &temp_dir.to_string_lossy()); let mut file = fs::File::create(temp_path.clone()).unwrap(); - file.write_all(self.dot_str().as_bytes()).unwrap(); + file.write_all(self.dot_str(arena).as_bytes()).unwrap(); Command::new("dot") .arg("-Tsvg") .arg(temp_path) @@ -102,7 +110,7 @@ pub trait GraphDot: GraphLike { .expect("failed to execute process"); } - fn open_mermaid(&self) + fn open_mermaid(&self, arena: &mut RangeArena) where Self: std::marker::Sized, Self: AnalyzerLike, @@ -127,7 +135,7 @@ pub trait GraphDot: GraphLike { let temp_svg_filename: String = format!("{}/mermaid.svg", &temp_dir.to_string_lossy()); let mut file = fs::File::create(temp_path.clone()).unwrap(); - file.write_all(self.mermaid_str().as_bytes()).unwrap(); + file.write_all(self.mermaid_str(arena).as_bytes()).unwrap(); Command::new("mmdc") .arg("-i") .arg(temp_path) @@ -149,6 +157,7 @@ pub trait GraphDot: GraphLike { /// Creates a subgraph for visually identifying contexts and subcontexts fn cluster_str( &self, + arena: &mut RangeArena, node: NodeIdx, cluster_num: &mut usize, is_killed: bool, @@ -161,18 +170,18 @@ pub trait GraphDot: GraphLike { Self: std::marker::Sized; /// Constructs a dot string - fn dot_str(&self) -> String + fn dot_str(&self, arena: &mut RangeArena) -> String where Self: std::marker::Sized, Self: AnalyzerLike; /// Construct a dot string while filtering temporary variables - fn dot_str_no_tmps(&self) -> String + fn dot_str_no_tmps(&self, arena: &mut RangeArena) -> String where Self: std::marker::Sized, Self: GraphLike + AnalyzerLike; - fn mermaid_str(&self) -> String + fn mermaid_str(&self, arena: &mut RangeArena) -> String where Self: std::marker::Sized, Self: AnalyzerLike; diff --git a/crates/solc-expressions/src/array.rs b/crates/solc-expressions/src/array.rs index f0412805..a1d61761 100644 --- a/crates/solc-expressions/src/array.rs +++ b/crates/solc-expressions/src/array.rs @@ -2,13 +2,13 @@ use crate::{ require::Require, variable::Variable, ContextBuilder, ExprErr, ExpressionParser, IntoExprErr, ListAccess, }; -use graph::elem::RangeElem; use graph::{ elem::{Elem, RangeDyn, RangeOp}, - nodes::{Builtin, ContextNode, ContextVar, ContextVarNode, ExprRet, TmpConstruction}, - AnalyzerBackend, ContextEdge, Edge, Node, Range, VarType, + nodes::{Builtin, Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, TmpConstruction}, + AnalyzerBackend, ContextEdge, Edge, Node, VarType, }; +use shared::RangeArena; use solang_parser::{ helpers::CodeLocation, @@ -20,9 +20,14 @@ impl Array for T where T: AnalyzerBackend + Sized { /// Gets the array type #[tracing::instrument(level = "trace", skip_all)] - fn array_ty(&mut self, ty_expr: &Expression, ctx: ContextNode) -> Result<(), ExprErr> { - self.parse_ctx_expr(ty_expr, ctx)?; - self.apply_to_edges(ctx, ty_expr.loc(), &|analyzer, ctx, loc| { + fn array_ty( + &mut self, + arena: &mut RangeArena>, + ty_expr: &Expression, + ctx: ContextNode, + ) -> Result<(), ExprErr> { + self.parse_ctx_expr(arena, ty_expr, ctx)?; + self.apply_to_edges(ctx, ty_expr.loc(), arena, &|analyzer, arena, ctx, loc| { if let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? { if matches!(ret, ExprRet::CtxKilled(_)) { ctx.push_expr(ret, analyzer).into_expr_err(loc)?; @@ -82,14 +87,15 @@ pub trait Array: AnalyzerBackend + Sized { #[tracing::instrument(level = "trace", skip_all)] fn index_into_array( &mut self, + arena: &mut RangeArena>, loc: Loc, ty_expr: &Expression, index_expr: &Expression, ctx: ContextNode, ) -> Result<(), ExprErr> { tracing::trace!("Indexing into array"); - self.parse_ctx_expr(index_expr, ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, index_expr, ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(index_tys) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( loc, @@ -100,8 +106,8 @@ pub trait Array: AnalyzerBackend + Sized { ctx.push_expr(index_tys, analyzer).into_expr_err(loc)?; return Ok(()); } - analyzer.parse_ctx_expr(ty_expr, ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.parse_ctx_expr(arena, ty_expr, ctx)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(inner_tys) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs(loc, "Could not find the array".to_string())); }; @@ -110,6 +116,7 @@ pub trait Array: AnalyzerBackend + Sized { return Ok(()); } analyzer.index_into_array_inner( + arena, ctx, loc, inner_tys.flatten(), @@ -122,6 +129,7 @@ pub trait Array: AnalyzerBackend + Sized { #[tracing::instrument(level = "trace", skip_all)] fn index_into_array_inner( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, inner_paths: ExprRet, @@ -138,7 +146,7 @@ pub trait Array: AnalyzerBackend + Sized { (ExprRet::Single(parent), ExprRet::Single(index)) | (ExprRet::Single(parent), ExprRet::SingleLiteral(index)) => { let index = ContextVarNode::from(index).latest_version(self); let parent = ContextVarNode::from(parent).latest_version(self); - let _ = self.index_into_array_raw(ctx, loc, index, parent, true, false)?; + let _ = self.index_into_array_raw(arena, ctx, loc, index, parent, true, false)?; Ok(()) } e => Err(ExprErr::ArrayIndex(loc, format!("Expected single expr evaluation of index expression, but was: {e:?}. This is a bug. Please report it at github.com/nascentxyz/pyrometer."))), @@ -147,6 +155,7 @@ pub trait Array: AnalyzerBackend + Sized { fn index_into_array_raw( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, index: ContextVarNode, @@ -160,10 +169,11 @@ pub trait Array: AnalyzerBackend + Sized { && parent.is_indexable(self).into_expr_err(loc)? { let len_var = self - .get_length(ctx, loc, parent, true)? + .get_length(arena, ctx, loc, parent, true)? .unwrap() .latest_version(self); self.require( + arena, len_var.latest_version(self), idx.latest_version(self), ctx, @@ -240,11 +250,12 @@ pub trait Array: AnalyzerBackend + Sized { let idx_access_cvar = self.advance_var_in_ctx(ContextVarNode::from(idx_access_node), loc, ctx)?; + idx_access_cvar - .set_range_min(self, min) + .set_range_min(self, arena, min) .into_expr_err(loc)?; idx_access_cvar - .set_range_max(self, max) + .set_range_max(self, arena, max) .into_expr_err(loc)?; if idx_access_cvar @@ -256,7 +267,7 @@ pub trait Array: AnalyzerBackend + Sized { { // if the index access is also an array, produce a length variable // we specify to return the variable because we dont want it on the stack - let _ = self.get_length(ctx, loc, idx_access_node.into(), true)?; + let _ = self.get_length(arena, ctx, loc, idx_access_node.into(), true)?; } idx_access_cvar } else { @@ -278,6 +289,7 @@ pub trait Array: AnalyzerBackend + Sized { fn update_array_if_index_access( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, maybe_index_access: ContextVarNode, @@ -304,8 +316,13 @@ pub trait Array: AnalyzerBackend + Sized { vec![(index.into(), new_value.into())], loc, )); - next_arr.set_range_min(self, min).into_expr_err(loc)?; - next_arr.set_range_max(self, max).into_expr_err(loc)?; + + next_arr + .set_range_min(self, arena, min) + .into_expr_err(loc)?; + next_arr + .set_range_max(self, arena, max) + .into_expr_err(loc)?; } // handle nested arrays, i.e. if: @@ -313,6 +330,7 @@ pub trait Array: AnalyzerBackend + Sized { // z[x][y] = 5; // first pass sets z[x][y] = 5, second pass needs to set z[x] = x self.update_array_if_index_access( + arena, ctx, loc, next_arr.latest_version(self), @@ -325,6 +343,7 @@ pub trait Array: AnalyzerBackend + Sized { fn update_array_if_length_var( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, maybe_length: ContextVarNode, @@ -333,15 +352,18 @@ pub trait Array: AnalyzerBackend + Sized { let next_arr = self.advance_var_in_ctx(backing_arr.latest_version(self), loc, ctx)?; let new_len = Elem::from(backing_arr).set_length(maybe_length.into()); next_arr - .set_range_min(self, new_len.clone()) + .set_range_min(self, arena, new_len.clone()) + .into_expr_err(loc)?; + next_arr + .set_range_max(self, arena, new_len) .into_expr_err(loc)?; - next_arr.set_range_max(self, new_len).into_expr_err(loc)?; } Ok(()) } fn set_var_as_length( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, new_length: ContextVarNode, @@ -350,10 +372,17 @@ pub trait Array: AnalyzerBackend + Sized { let next_arr = self.advance_var_in_ctx(backing_arr.latest_version(self), loc, ctx)?; let new_len = Elem::from(backing_arr).get_length().max(new_length.into()); let min = Elem::from(backing_arr).set_length(new_len); - next_arr.set_range_min(self, min).into_expr_err(loc)?; + let new_len = Elem::from(backing_arr).get_length().min(new_length.into()); let max = Elem::from(backing_arr).set_length(new_len); - next_arr.set_range_max(self, max).into_expr_err(loc)?; + + next_arr + .set_range_min(self, arena, min) + .into_expr_err(loc)?; + next_arr + .set_range_max(self, arena, max) + .into_expr_err(loc)?; + self.add_edge( new_length, next_arr, @@ -364,6 +393,7 @@ pub trait Array: AnalyzerBackend + Sized { fn update_array_from_index_access( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, index: ContextVarNode, @@ -387,8 +417,12 @@ pub trait Array: AnalyzerBackend + Sized { vec![(index.into(), access.into())], loc, )); - next_arr.set_range_min(self, min).into_expr_err(loc)?; - next_arr.set_range_max(self, max).into_expr_err(loc)?; + next_arr + .set_range_min(self, arena, min) + .into_expr_err(loc)?; + next_arr + .set_range_max(self, arena, max) + .into_expr_err(loc)?; } // handle nested arrays @@ -397,6 +431,7 @@ pub trait Array: AnalyzerBackend + Sized { next_arr.index_access_to_index(self), ) { self.update_array_from_index_access( + arena, ctx, loc, parent_nested_index, @@ -410,6 +445,7 @@ pub trait Array: AnalyzerBackend + Sized { fn update_array_min_if_length( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, maybe_length: ContextVarNode, @@ -420,13 +456,16 @@ pub trait Array: AnalyzerBackend + Sized { .get_length() .max(maybe_length.into()); let min = Elem::from(backing_arr).set_length(new_len); - next_arr.set_range_min(self, min).into_expr_err(loc)?; + next_arr + .set_range_min(self, arena, min) + .into_expr_err(loc)?; } Ok(()) } fn update_array_max_if_length( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, maybe_length: ContextVarNode, @@ -437,7 +476,9 @@ pub trait Array: AnalyzerBackend + Sized { .get_length() .min(maybe_length.into()); let max = Elem::from(backing_arr).set_length(new_len); - next_arr.set_range_max(self, max).into_expr_err(loc)?; + next_arr + .set_range_max(self, arena, max) + .into_expr_err(loc)?; } Ok(()) } diff --git a/crates/solc-expressions/src/assign.rs b/crates/solc-expressions/src/assign.rs index c3759746..38c89a6e 100644 --- a/crates/solc-expressions/src/assign.rs +++ b/crates/solc-expressions/src/assign.rs @@ -5,10 +5,11 @@ use crate::{ use graph::{ elem::{Elem, RangeElem}, - nodes::{ContextNode, ContextVarNode, ExprRet}, + nodes::{Concrete, ContextNode, ContextVarNode, ExprRet}, AnalyzerBackend, ContextEdge, Edge, GraphError, Node, }; +use shared::RangeArena; use solang_parser::pt::{Expression, Loc}; impl Assign for T where T: AnalyzerBackend + Sized {} @@ -18,13 +19,14 @@ pub trait Assign: AnalyzerBackend + Sized /// Parse an assignment expression fn assign_exprs( &mut self, + arena: &mut RangeArena>, loc: Loc, lhs_expr: &Expression, rhs_expr: &Expression, ctx: ContextNode, ) -> Result<(), ExprErr> { - self.parse_ctx_expr(rhs_expr, ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, rhs_expr, ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( loc, @@ -36,8 +38,8 @@ pub trait Assign: AnalyzerBackend + Sized ctx.push_expr(rhs_paths, analyzer).into_expr_err(loc)?; return Ok(()); } - analyzer.parse_ctx_expr(lhs_expr, ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.parse_ctx_expr(arena, lhs_expr, ctx)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( loc, @@ -48,7 +50,7 @@ pub trait Assign: AnalyzerBackend + Sized ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; return Ok(()); } - analyzer.match_assign_sides(ctx, loc, &lhs_paths.flatten(), &rhs_paths)?; + analyzer.match_assign_sides(arena, ctx, loc, &lhs_paths.flatten(), &rhs_paths)?; Ok(()) }) }) @@ -57,6 +59,7 @@ pub trait Assign: AnalyzerBackend + Sized /// Match on the [`ExprRet`]s of an assignment expression fn match_assign_sides( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, lhs_paths: &ExprRet, @@ -71,40 +74,40 @@ pub trait Assign: AnalyzerBackend + Sized (ExprRet::Single(lhs), ExprRet::SingleLiteral(rhs)) => { let lhs_cvar = ContextVarNode::from(*lhs).latest_version(self); let rhs_cvar = ContextVarNode::from(*rhs).latest_version(self); - let res = rhs_cvar - .literal_cast_from(&lhs_cvar, self) - .into_expr_err(loc); - let _ = self.add_if_err(res); - ctx.push_expr(self.assign(loc, lhs_cvar, rhs_cvar, ctx)?, self) + // let res = rhs_cvar + // .literal_cast_from(&lhs_cvar, self) + // .into_expr_err(loc); + // let _ = self.add_if_err(res); + ctx.push_expr(self.assign(arena, loc, lhs_cvar, rhs_cvar, ctx)?, self) .into_expr_err(loc)?; Ok(()) } (ExprRet::Single(lhs), ExprRet::Single(rhs)) => { let lhs_cvar = ContextVarNode::from(*lhs).latest_version(self); let rhs_cvar = ContextVarNode::from(*rhs).latest_version(self); - ctx.push_expr(self.assign(loc, lhs_cvar, rhs_cvar, ctx)?, self) + ctx.push_expr(self.assign(arena, loc, lhs_cvar, rhs_cvar, ctx)?, self) .into_expr_err(loc)?; Ok(()) } (l @ ExprRet::Single(_), ExprRet::Multi(rhs_sides)) => rhs_sides .iter() - .try_for_each(|expr_ret| self.match_assign_sides(ctx, loc, l, expr_ret)), + .try_for_each(|expr_ret| self.match_assign_sides(arena, ctx, loc, l, expr_ret)), (ExprRet::Multi(lhs_sides), r @ ExprRet::Single(_) | r @ ExprRet::SingleLiteral(_)) => { lhs_sides .iter() - .try_for_each(|expr_ret| self.match_assign_sides(ctx, loc, expr_ret, r)) + .try_for_each(|expr_ret| self.match_assign_sides(arena, ctx, loc, expr_ret, r)) } (ExprRet::Multi(lhs_sides), ExprRet::Multi(rhs_sides)) => { // try to zip sides if they are the same length if lhs_sides.len() == rhs_sides.len() { lhs_sides.iter().zip(rhs_sides.iter()).try_for_each( |(lhs_expr_ret, rhs_expr_ret)| { - self.match_assign_sides(ctx, loc, lhs_expr_ret, rhs_expr_ret) + self.match_assign_sides(arena, ctx, loc, lhs_expr_ret, rhs_expr_ret) }, ) } else { rhs_sides.iter().try_for_each(|rhs_expr_ret| { - self.match_assign_sides(ctx, loc, lhs_paths, rhs_expr_ret) + self.match_assign_sides(arena, ctx, loc, lhs_paths, rhs_expr_ret) }) } } @@ -115,6 +118,7 @@ pub trait Assign: AnalyzerBackend + Sized /// Perform an assignment fn assign( &mut self, + arena: &mut RangeArena>, loc: Loc, lhs_cvar: ContextVarNode, rhs_cvar: ContextVarNode, @@ -126,16 +130,20 @@ pub trait Assign: AnalyzerBackend + Sized lhs_cvar.display_name(self).unwrap(), ); + rhs_cvar + .cast_from(&lhs_cvar, self, arena) + .into_expr_err(loc)?; + let (new_lower_bound, new_upper_bound) = ( Elem::from(rhs_cvar.latest_version(self)), Elem::from(rhs_cvar.latest_version(self)), ); let needs_forcible = new_lower_bound - .depends_on(lhs_cvar, &mut vec![], self) + .depends_on(lhs_cvar, &mut vec![], self, arena) .into_expr_err(loc)? || new_upper_bound - .depends_on(lhs_cvar, &mut vec![], self) + .depends_on(lhs_cvar, &mut vec![], self, arena) .into_expr_err(loc)?; let new_lhs = if needs_forcible { @@ -209,11 +217,11 @@ pub trait Assign: AnalyzerBackend + Sized } }; - let _ = new_lhs.try_set_range_min(self, new_lower_bound.cast(cast_to_min)); - let _ = new_lhs.try_set_range_max(self, new_upper_bound.cast(cast_to_max)); + let _ = new_lhs.try_set_range_min(self, arena, new_lower_bound.cast(cast_to_min)); + let _ = new_lhs.try_set_range_max(self, arena, new_upper_bound.cast(cast_to_max)); } else { - let _ = new_lhs.try_set_range_min(self, new_lower_bound); - let _ = new_lhs.try_set_range_max(self, new_upper_bound); + let _ = new_lhs.try_set_range_min(self, arena, new_lower_bound); + let _ = new_lhs.try_set_range_max(self, arena, new_upper_bound); } if let Some(rhs_range) = rhs_cvar.ref_range(self).into_expr_err(loc)? { let res = new_lhs @@ -225,14 +233,14 @@ pub trait Assign: AnalyzerBackend + Sized if rhs_cvar.is_indexable(self).into_expr_err(loc)? { // rhs is indexable. get the length attribute, create a new length for the lhs, // and perform assign - let rhs_len_cvar = self.get_length(ctx, loc, rhs_cvar, true)?.unwrap(); - let lhs_len_cvar = self.get_length(ctx, loc, lhs_cvar, true)?.unwrap(); - self.assign(loc, lhs_len_cvar, rhs_len_cvar, ctx)?; + let rhs_len_cvar = self.get_length(arena, ctx, loc, rhs_cvar, true)?.unwrap(); + let lhs_len_cvar = self.get_length(arena, ctx, loc, lhs_cvar, true)?.unwrap(); + self.assign(arena, loc, lhs_len_cvar, rhs_len_cvar, ctx)?; // update the range - self.update_array_if_length_var(ctx, loc, lhs_len_cvar.latest_version(self))?; + self.update_array_if_length_var(arena, ctx, loc, lhs_len_cvar.latest_version(self))?; } - self.update_array_if_index_access(ctx, loc, lhs_cvar, rhs_cvar)?; + self.update_array_if_index_access(arena, ctx, loc, lhs_cvar, rhs_cvar)?; // handle struct assignment if let Ok(fields) = rhs_cvar.struct_to_fields(self) { diff --git a/crates/solc-expressions/src/bin_op.rs b/crates/solc-expressions/src/bin_op.rs index a9c45591..57aad7ff 100644 --- a/crates/solc-expressions/src/bin_op.rs +++ b/crates/solc-expressions/src/bin_op.rs @@ -5,11 +5,11 @@ use crate::{ use graph::{ elem::*, nodes::{ - BuiltInNode, Builtin, Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, - KilledKind, TmpConstruction, + Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, KilledKind, TmpConstruction, }, - AnalyzerBackend, ContextEdge, Edge, Node, Range, RangeEval, SolcRange, VarType, + AnalyzerBackend, ContextEdge, Edge, Node, }; +use shared::RangeArena; use ethers_core::types::U256; use solang_parser::pt::{Expression, Loc}; @@ -21,6 +21,7 @@ pub trait BinOp: AnalyzerBackend + Sized { #[tracing::instrument(level = "trace", skip_all)] fn op_expr( &mut self, + arena: &mut RangeArena>, loc: Loc, lhs_expr: &Expression, rhs_expr: &Expression, @@ -30,8 +31,8 @@ pub trait BinOp: AnalyzerBackend + Sized { ) -> Result<(), ExprErr> { ctx.add_gas_cost(self, shared::gas::BIN_OP_GAS) .into_expr_err(loc)?; - self.parse_ctx_expr(rhs_expr, ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, rhs_expr, ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs(loc, "Binary operation had no right hand side".to_string())) }; @@ -41,8 +42,8 @@ pub trait BinOp: AnalyzerBackend + Sized { } let rhs_paths = rhs_paths.flatten(); let rhs_ctx = ctx; - analyzer.parse_ctx_expr(lhs_expr, ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.parse_ctx_expr(arena, lhs_expr, ctx)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs(loc, format!("Binary operation had no left hand side, Expr: {lhs_expr:#?}, rhs ctx: {}, curr ctx: {}", rhs_ctx.path(analyzer), ctx.path(analyzer)))) }; @@ -51,13 +52,14 @@ pub trait BinOp: AnalyzerBackend + Sized { return Ok(()); } let lhs_paths = lhs_paths.flatten(); - analyzer.op_match(ctx, loc, &lhs_paths, &rhs_paths, op, assign) + analyzer.op_match(arena, ctx, loc, &lhs_paths, &rhs_paths, op, assign) }) }) } fn op_match( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, lhs_paths: &ExprRet, @@ -77,50 +79,62 @@ pub trait BinOp: AnalyzerBackend + Sized { (ExprRet::SingleLiteral(lhs), ExprRet::SingleLiteral(rhs)) => { let lhs_cvar = ContextVarNode::from(*lhs).latest_version(self); let rhs_cvar = ContextVarNode::from(*rhs).latest_version(self); - lhs_cvar.try_increase_size(self).into_expr_err(loc)?; - rhs_cvar.try_increase_size(self).into_expr_err(loc)?; - ctx.push_expr(self.op(loc, lhs_cvar, rhs_cvar, ctx, op, assign)?, self) - .into_expr_err(loc)?; + lhs_cvar.try_increase_size(self, arena).into_expr_err(loc)?; + rhs_cvar.try_increase_size(self, arena).into_expr_err(loc)?; + ctx.push_expr( + self.op(arena, loc, lhs_cvar, rhs_cvar, ctx, op, assign)?, + self, + ) + .into_expr_err(loc)?; Ok(()) } (ExprRet::SingleLiteral(lhs), ExprRet::Single(rhs)) => { ContextVarNode::from(*lhs) - .cast_from(&ContextVarNode::from(*rhs), self) + .cast_from(&ContextVarNode::from(*rhs), self, arena) .into_expr_err(loc)?; let lhs_cvar = ContextVarNode::from(*lhs).latest_version(self); let rhs_cvar = ContextVarNode::from(*rhs).latest_version(self); - ctx.push_expr(self.op(loc, lhs_cvar, rhs_cvar, ctx, op, assign)?, self) - .into_expr_err(loc)?; + ctx.push_expr( + self.op(arena, loc, lhs_cvar, rhs_cvar, ctx, op, assign)?, + self, + ) + .into_expr_err(loc)?; Ok(()) } (ExprRet::Single(lhs), ExprRet::SingleLiteral(rhs)) => { ContextVarNode::from(*rhs) - .cast_from(&ContextVarNode::from(*lhs), self) + .cast_from(&ContextVarNode::from(*lhs), self, arena) .into_expr_err(loc)?; let lhs_cvar = ContextVarNode::from(*lhs).latest_version(self); let rhs_cvar = ContextVarNode::from(*rhs).latest_version(self); - ctx.push_expr(self.op(loc, lhs_cvar, rhs_cvar, ctx, op, assign)?, self) - .into_expr_err(loc)?; + ctx.push_expr( + self.op(arena, loc, lhs_cvar, rhs_cvar, ctx, op, assign)?, + self, + ) + .into_expr_err(loc)?; Ok(()) } (ExprRet::Single(lhs), ExprRet::Single(rhs)) => { let lhs_cvar = ContextVarNode::from(*lhs).latest_version(self); let rhs_cvar = ContextVarNode::from(*rhs).latest_version(self); - ctx.push_expr(self.op(loc, lhs_cvar, rhs_cvar, ctx, op, assign)?, self) - .into_expr_err(loc)?; + ctx.push_expr( + self.op(arena, loc, lhs_cvar, rhs_cvar, ctx, op, assign)?, + self, + ) + .into_expr_err(loc)?; Ok(()) } (lhs @ ExprRet::Single(..), ExprRet::Multi(rhs_sides)) => { rhs_sides .iter() - .map(|expr_ret| self.op_match(ctx, loc, lhs, expr_ret, op, assign)) + .map(|expr_ret| self.op_match(arena, ctx, loc, lhs, expr_ret, op, assign)) .collect::, ExprErr>>()?; Ok(()) } (ExprRet::Multi(lhs_sides), rhs @ ExprRet::Single(..)) => { lhs_sides .iter() - .map(|expr_ret| self.op_match(ctx, loc, expr_ret, rhs, op, assign)) + .map(|expr_ret| self.op_match(arena, ctx, loc, expr_ret, rhs, op, assign)) .collect::, ExprErr>>()?; Ok(()) } @@ -141,6 +155,7 @@ pub trait BinOp: AnalyzerBackend + Sized { #[tracing::instrument(level = "trace", skip_all)] fn op( &mut self, + arena: &mut RangeArena>, loc: Loc, lhs_cvar: ContextVarNode, rhs_cvar: ContextVarNode, @@ -156,34 +171,6 @@ pub trait BinOp: AnalyzerBackend + Sized { assign ); - // if rhs_cvar.is_const(self).unwrap() { - // let int = rhs_cvar.evaled_range_max(self).unwrap().unwrap(); - // match collapse(&Elem::from(lhs_cvar), op, &int, self) { - // MaybeCollapsed::Collapsed(c) => { - // println!("collapsed: {c:?}"); - // } - // MaybeCollapsed::Concretes(_, _) => { - // println!("concretes"); - // } - // MaybeCollapsed::Not(..) => { - // println!("not collapsed"); - // } - // } - // } else if lhs_cvar.is_const(self).unwrap() { - // let int = lhs_cvar.evaled_range_max(self).unwrap().unwrap(); - // match collapse(&int, op, &Elem::from(rhs_cvar), self) { - // MaybeCollapsed::Collapsed(c) => { - // println!("collapsed: {c:?}"); - // } - // MaybeCollapsed::Concretes(_, _) => { - // println!("concretes"); - // } - // MaybeCollapsed::Not(..) => { - // println!("not collapsed"); - // } - // } - // } - let unchecked = match op { RangeOp::Add(u) | RangeOp::Sub(u) | RangeOp::Mul(u) | RangeOp::Div(u) => u, _ => false, @@ -207,7 +194,7 @@ pub trait BinOp: AnalyzerBackend + Sized { ContextVar::new_bin_op_tmp(lhs_cvar, op, rhs_cvar, ctx, loc, self) .into_expr_err(loc)?; if let Ok(Some(existing)) = - self.get_unchanged_tmp_variable(&new_lhs_underlying.display_name, ctx) + self.get_unchanged_tmp_variable(arena, &new_lhs_underlying.display_name, ctx) { self.advance_var_in_ctx_forcible(existing, loc, ctx, true)? } else { @@ -224,458 +211,78 @@ pub trait BinOp: AnalyzerBackend + Sized { } }; - let mut new_rhs = rhs_cvar.latest_version(self); + let new_rhs = rhs_cvar.latest_version(self); let expr = Elem::Expr(RangeExpr::::new( Elem::from(Reference::new(lhs_cvar.latest_version(self).into())), op, Elem::from(Reference::new(rhs_cvar.latest_version(self).into())), )); + let new_lhs = new_lhs.latest_version(self); + new_lhs + .set_range_min(self, arena, expr.clone()) + .into_expr_err(loc)?; + new_lhs + .set_range_max(self, arena, expr) + .into_expr_err(loc)?; // to prevent some recursive referencing, forcibly increase lhs_cvar self.advance_var_in_ctx_forcible(lhs_cvar.latest_version(self), loc, ctx, true)?; - // TODO: If one of lhs_cvar OR rhs_cvar are not symbolic, - // apply the requirement on the symbolic expression side instead of - // ignoring the case where - - // if lhs_cvar.is_symbolic(self) && new_rhs.is_symbolic(self) { if !unchecked { match op { RangeOp::Div(..) | RangeOp::Mod => { - // x / y - - if new_rhs.is_const(self).into_expr_err(loc)? { - // y is constant, do a check if it is 0 - if new_rhs - .evaled_range_min(self) - .into_expr_err(loc)? - .expect("No range?") - .range_eq(&Elem::from(Concrete::from(U256::zero())), self) - { - let res = ctx.kill(self, loc, KilledKind::Revert).into_expr_err(loc); - let _ = self.add_if_err(res); - - return Ok(ExprRet::CtxKilled(KilledKind::Revert)); - } - } else if new_rhs.is_symbolic(self).into_expr_err(loc)? { - // y is symbolic, add - let tmp_rhs = self.advance_var_in_ctx(new_rhs, loc, ctx)?; - let zero_node = self.add_node(Node::Concrete(Concrete::from(U256::zero()))); - let var = ContextVar::new_from_concrete( - Loc::Implicit, - ctx, - zero_node.into(), - self, - ); - let zero_node = self.add_node(Node::ContextVar(var.into_expr_err(loc)?)); - - if self - .require( - tmp_rhs, - zero_node.into(), - ctx, - loc, - RangeOp::Neq, - RangeOp::Neq, - (RangeOp::Eq, RangeOp::Neq), - )? - .is_none() - { - return Ok(ExprRet::CtxKilled(KilledKind::Revert)); - } + if let Some(killed) = + self.checked_require_mod_div(arena, lhs_cvar, new_rhs, loc, ctx)? + { + return Ok(killed); } } RangeOp::Sub(..) => { - let lhs_cvar = lhs_cvar.latest_version(self); - if lhs_cvar.is_const(self).into_expr_err(loc)? { - if !lhs_cvar.is_int(self).into_expr_err(loc)? { - if let (Some(lmax), Some(rmin)) = ( - lhs_cvar.evaled_range_max(self).into_expr_err(loc)?, - rhs_cvar.evaled_range_min(self).into_expr_err(loc)?, - ) { - if matches!( - lmax.range_ord(&rmin, self), - Some(std::cmp::Ordering::Less) - | Some(std::cmp::Ordering::Equal) - ) { - ctx.kill(self, loc, KilledKind::Revert).into_expr_err(loc)?; - - return Ok(ExprRet::CtxKilled(KilledKind::Revert)); - } - } - } - } else if lhs_cvar.is_symbolic(self).into_expr_err(loc)? { - let tmp_lhs = self.advance_var_in_ctx_forcible(lhs_cvar, loc, ctx, true)?; - // let tmp_rhs = self.advance_var_in_ctx_forcible(new_rhs, loc, ctx, true)?; - if self - .require( - tmp_lhs, - new_rhs, - ctx, - loc, - RangeOp::Gte, - RangeOp::Lte, - (RangeOp::Lte, RangeOp::Gte), - )? - .is_none() - { - return Ok(ExprRet::CtxKilled(KilledKind::Revert)); - } - // the new min is max(lhs.min, rhs.min) - let min = Elem::max( - Elem::from(Reference::new(lhs_cvar.into())), - Elem::from(rhs_cvar), - ); - let tmp_lhs = tmp_lhs.latest_version(self); - tmp_lhs.set_range_min(self, min).into_expr_err(loc)?; - - // let tmp_var = ContextVar { - // loc: Some(loc), - // name: format!( - // "tmp{}({} >= {})", - // ctx.new_tmp(self).into_expr_err(loc)?, - // tmp_lhs.name(self).into_expr_err(loc)?, - // new_rhs.name(self).into_expr_err(loc)?, - // ), - // display_name: format!( - // "({} >= {})", - // tmp_lhs.display_name(self).unwrap(), - // new_rhs.display_name(self).unwrap(), - // ), - // storage: None, - // is_tmp: true, - // tmp_of: Some(TmpConstruction::new( - // tmp_lhs, - // RangeOp::Gte, - // Some(new_rhs), - // )), - // is_symbolic: true, - // is_return: false, - // ty: VarType::BuiltIn( - // BuiltInNode::from(self.builtin_or_add(Builtin::Bool)), - // SolcRange::from(Concrete::Bool(true)), - // ), - // }; - - // let cvar = ContextVarNode::from(self.add_node(Node::ContextVar(tmp_var))); - // ctx.add_ctx_dep(cvar, self).into_expr_err(loc)?; + if let Some(killed) = + self.checked_require_sub(arena, lhs_cvar, new_lhs, new_rhs, loc, ctx)? + { + return Ok(killed); } } RangeOp::Add(..) => { - let lhs_cvar = lhs_cvar.latest_version(self); - if lhs_cvar.is_symbolic(self).into_expr_err(loc)? { - let tmp_lhs = self.advance_var_in_ctx(lhs_cvar, loc, ctx)?; - - // the new max is min(lhs.max, (2**256 - rhs.min)) - let max = Elem::min( - Elem::from(Reference::new(lhs_cvar.into())), - Elem::from(Concrete::from(U256::MAX)) - Elem::from(rhs_cvar), - ); - - tmp_lhs.set_range_max(self, max).into_expr_err(loc)?; - - let max_node = self.add_node(Node::Concrete(Concrete::from(U256::MAX))); - let tmp_max = ContextVar::new_from_concrete( - Loc::Implicit, - ctx, - max_node.into(), - self, - ); - let max_node = self.add_node(Node::ContextVar(tmp_max.into_expr_err(loc)?)); - - let tmp_rhs = self.op( - loc, - max_node.into(), - new_rhs, - ctx, - RangeOp::Sub(true), - false, - )?; - - if matches!(tmp_rhs, ExprRet::CtxKilled(_)) { - return Ok(tmp_rhs); - } - - let tmp_rhs = tmp_rhs.expect_single().into_expr_err(loc)?; - - let tmp_lhs = if new_rhs.latest_version(self) == tmp_lhs { - self.advance_var_in_ctx_forcible( - tmp_lhs.latest_version(self), - loc, - ctx, - true, - )? - } else { - tmp_lhs - }; - - if self - .require( - tmp_lhs, - tmp_rhs.into(), - ctx, - loc, - RangeOp::Lte, - RangeOp::Gte, - (RangeOp::Gte, RangeOp::Lte), - )? - .is_none() - { - return Ok(ExprRet::CtxKilled(KilledKind::Revert)); - } + if let Some(killed) = + self.checked_require_add(arena, lhs_cvar, new_lhs, new_rhs, loc, ctx)? + { + return Ok(killed); } } RangeOp::Mul(..) => { - let lhs_cvar = lhs_cvar.latest_version(self); - if lhs_cvar.is_symbolic(self).into_expr_err(loc)? { - let tmp_lhs = self.advance_var_in_ctx(lhs_cvar, loc, ctx)?; - - // the new max is min(lhs.max, (2**256 / max(1, rhs.min))) - let max = Elem::min( - Elem::from(Reference::new(lhs_cvar.into())), - Elem::from(Concrete::from(U256::MAX)) - / Elem::max( - Elem::from(Concrete::from(U256::from(1))), - Elem::from(rhs_cvar), - ), - ); - - tmp_lhs.set_range_max(self, max).into_expr_err(loc)?; - - let max_node = self.add_node(Node::Concrete(Concrete::from(U256::MAX))); - let tmp_max = ContextVar::new_from_concrete( - Loc::Implicit, - ctx, - max_node.into(), - self, - ); - let max_node = self.add_node(Node::ContextVar(tmp_max.into_expr_err(loc)?)); - - let tmp_rhs = self.op( - loc, - max_node.into(), - new_rhs, - ctx, - RangeOp::Div(true), - false, - )?; - - if matches!(tmp_rhs, ExprRet::CtxKilled(_)) { - return Ok(tmp_rhs); - } - - let tmp_rhs = tmp_rhs.expect_single().into_expr_err(loc)?; - - let tmp_lhs = if new_rhs.latest_version(self) == tmp_lhs { - self.advance_var_in_ctx_forcible( - tmp_lhs.latest_version(self), - loc, - ctx, - true, - )? - } else { - tmp_lhs - }; - - if self - .require( - tmp_lhs, - tmp_rhs.into(), - ctx, - loc, - RangeOp::Lte, - RangeOp::Gte, - (RangeOp::Gte, RangeOp::Lte), - )? - .is_none() - { - return Ok(ExprRet::CtxKilled(KilledKind::Revert)); - } - - // let tmp_rhs = ContextVarNode::from(tmp_rhs).latest_version(self); - - // let tmp_var = ContextVar { - // loc: Some(loc), - // name: format!( - // "tmp{}({} <= (2**256 - 1) / {})", - // ctx.new_tmp(self).into_expr_err(loc)?, - // tmp_lhs.name(self).into_expr_err(loc)?, - // new_rhs.name(self).into_expr_err(loc)?, - // ), - // display_name: format!( - // "({} <= (2**256 - 1) / {})", - // tmp_lhs.display_name(self).unwrap(), - // new_rhs.display_name(self).unwrap(), - // ), - // storage: None, - // is_tmp: true, - // tmp_of: Some(TmpConstruction::new( - // tmp_lhs, - // RangeOp::Lte, - // Some(tmp_rhs), - // )), - // is_symbolic: true, - // is_return: false, - // ty: VarType::BuiltIn( - // BuiltInNode::from(self.builtin_or_add(Builtin::Bool)), - // SolcRange::from(Concrete::Bool(true)), - // ), - // }; - - // let cvar = ContextVarNode::from(self.add_node(Node::ContextVar(tmp_var))); - // ctx.add_ctx_dep(cvar, self).into_expr_err(loc)?; + if let Some(killed) = + self.checked_require_mul(arena, lhs_cvar, new_lhs, new_rhs, loc, ctx)? + { + return Ok(killed); } } RangeOp::Exp => { - if new_rhs.is_const(self).into_expr_err(loc)? { - if matches!( - new_rhs - .evaled_range_min(self) - .into_expr_err(loc)? - .expect("No range") - .range_ord(&Elem::from(Concrete::from(U256::zero())), self), - Some(std::cmp::Ordering::Less) - ) { - ctx.kill(self, loc, KilledKind::Revert).into_expr_err(loc)?; - return Ok(ExprRet::CtxKilled(KilledKind::Revert)); - } - } else if new_rhs.is_symbolic(self).into_expr_err(loc)? { - let tmp_rhs = self.advance_var_in_ctx(rhs_cvar, loc, ctx)?; - // the new min is max(lhs.min, rhs.min) - let min = Elem::max( - Elem::from(Reference::new(rhs_cvar.into())), - // .range_min(self) - // .into_expr_err(loc)? - // .expect("No range minimum?"), - Elem::from(Concrete::from(U256::zero())), - ); - - tmp_rhs.set_range_min(self, min).into_expr_err(loc)?; - - let zero_node = self.add_node(Node::Concrete(Concrete::from(U256::zero()))); - let tmp_zero = ContextVar::new_from_concrete( - Loc::Implicit, - ctx, - zero_node.into(), - self, - ); - let zero_node = - self.add_node(Node::ContextVar(tmp_zero.into_expr_err(loc)?)); - - if self - .require( - tmp_rhs, - zero_node.into(), - ctx, - loc, - RangeOp::Gte, - RangeOp::Lte, - (RangeOp::Lte, RangeOp::Gte), - )? - .is_none() - { - return Ok(ExprRet::CtxKilled(KilledKind::Revert)); - } - - let tmp_var = ContextVar { - loc: Some(loc), - name: format!( - "tmp{}({} >= 0)", - ctx.new_tmp(self).into_expr_err(loc)?, - tmp_rhs.name(self).into_expr_err(loc)?, - ), - display_name: format!( - "({} >= 0)", - tmp_rhs.display_name(self).into_expr_err(loc)?, - ), - storage: None, - is_tmp: true, - tmp_of: Some(TmpConstruction::new( - tmp_rhs, - RangeOp::Gte, - Some(zero_node.into()), - )), - dep_on: { - let mut deps = - tmp_rhs.dependent_on(self, true).into_expr_err(loc)?; - deps.push(zero_node.into()); - Some(deps) - }, - is_symbolic: true, - is_return: false, - ty: VarType::BuiltIn( - BuiltInNode::from(self.builtin_or_add(Builtin::Bool)), - SolcRange::from(Concrete::Bool(true)), - ), - }; - - let cvar = ContextVarNode::from(self.add_node(Node::ContextVar(tmp_var))); - ctx.add_ctx_dep(cvar, self).into_expr_err(loc)?; - new_rhs = tmp_rhs; + if let Some(killed) = + self.checked_require_exp(arena, lhs_cvar, new_lhs, new_rhs, loc, ctx)? + { + return Ok(killed); } } _ => {} } - } else { - - // self.advance_var_in_ctx_forcible(rhs_cvar.latest_version(self), loc, ctx, true)?; } - // let lhs_range = if let Some(lhs_range) = new_lhs.range(self).into_expr_err(loc)? { - // lhs_range - // } else { - // new_rhs - // .range(self) - // .into_expr_err(loc)? - // .expect("Neither lhs nor rhs had a usable range") - // }; - - // let func = SolcRange::dyn_fn_from_op(op); - // let new_range = func(lhs_range, new_rhs); - new_lhs - .latest_version(self) - .set_range_min(self, expr.clone()) - .into_expr_err(loc)?; - new_lhs - .latest_version(self) - .set_range_max(self, expr) - .into_expr_err(loc)?; - - // last ditch effort to prevent exponentiation from having a minimum of 1 instead of 0. - // if the lhs is 0 check if the rhs is also 0, otherwise set minimum to 0. - if matches!(op, RangeOp::Exp) { - if let (Some(old_lhs_range), Some(rhs_range)) = ( - lhs_cvar - .latest_version(self) - .ref_range(self) - .into_expr_err(loc)?, - new_rhs.ref_range(self).into_expr_err(loc)?, - ) { - let zero = Elem::from(Concrete::from(U256::zero())); - let zero_range = SolcRange::new(zero.clone(), zero.clone(), vec![]); - // We have to check if the the lhs and the right hand side contain the zero range. - // If they both do, we have to set the minimum to zero due to 0**0 = 1, but 0**x = 0. - // This is technically a slight widening of the interval and could be improved. - if old_lhs_range.contains(&zero_range, self) - && rhs_range.contains(&zero_range, self) - { - new_lhs.set_range_min(self, zero).into_expr_err(loc)?; - } - } - } Ok(ExprRet::Single(new_lhs.latest_version(self).into())) } #[tracing::instrument(level = "trace", skip_all)] fn bit_not( &mut self, + arena: &mut RangeArena>, loc: Loc, lhs_expr: &Expression, ctx: ContextNode, ) -> Result<(), ExprErr> { - self.parse_ctx_expr(lhs_expr, ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, lhs_expr, ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( loc, @@ -687,13 +294,14 @@ pub trait BinOp: AnalyzerBackend + Sized { ctx.push_expr(lhs, analyzer).into_expr_err(loc)?; return Ok(()); } - analyzer.bit_not_inner(ctx, loc, lhs.flatten()) + analyzer.bit_not_inner(arena, ctx, loc, lhs.flatten()) }) } #[tracing::instrument(level = "trace", skip_all)] fn bit_not_inner( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, lhs_expr: ExprRet, @@ -708,9 +316,9 @@ pub trait BinOp: AnalyzerBackend + Sized { // TODO: try to pop from the stack and if there is a single element there // use it as a type hint, then place it back on the stack ContextVarNode::from(lhs) - .try_increase_size(self) + .try_increase_size(self, arena) .into_expr_err(loc)?; - self.bit_not_inner(ctx, loc, ExprRet::Single(lhs))?; + self.bit_not_inner(arena, ctx, loc, ExprRet::Single(lhs))?; Ok(()) } ExprRet::Single(lhs) => { @@ -745,9 +353,11 @@ pub trait BinOp: AnalyzerBackend + Sized { let out_var = ContextVarNode::from(self.add_node(Node::ContextVar(out_var))); out_var - .set_range_min(self, expr.clone()) + .set_range_min(self, arena, expr.clone()) + .into_expr_err(loc)?; + out_var + .set_range_max(self, arena, expr) .into_expr_err(loc)?; - out_var.set_range_max(self, expr).into_expr_err(loc)?; self.advance_var_in_ctx_forcible(lhs_cvar, loc, ctx, true)?; ctx.push_expr(ExprRet::Single(out_var.into()), self) @@ -764,4 +374,374 @@ pub trait BinOp: AnalyzerBackend + Sized { )), } } + + fn checked_require_mod_div( + &mut self, + arena: &mut RangeArena>, + lhs: ContextVarNode, + rhs: ContextVarNode, + loc: Loc, + ctx: ContextNode, + ) -> Result, ExprErr> { + // x / y || x % y + // revert if div or mod by 0 + if rhs.is_const(self, arena).into_expr_err(loc)? + && rhs + .evaled_range_min(self, arena) + .into_expr_err(loc)? + .expect("No range?") + .range_eq(&Elem::from(Concrete::from(U256::zero())), arena) + { + let res = ctx.kill(self, loc, KilledKind::Revert).into_expr_err(loc); + let _ = self.add_if_err(res); + + return Ok(Some(ExprRet::CtxKilled(KilledKind::Revert))); + } + + // otherwise, require rhs != 0 + let tmp_rhs = self.advance_var_in_ctx(rhs, loc, ctx)?; + let zero_node = self.add_concrete_var(ctx, Concrete::from(U256::zero()), loc)?; + + if self + .require( + arena, + tmp_rhs, + zero_node, + ctx, + loc, + RangeOp::Neq, + RangeOp::Neq, + (RangeOp::Eq, RangeOp::Neq), + )? + .is_none() + { + return Ok(Some(ExprRet::CtxKilled(KilledKind::Revert))); + } + Ok(None) + } + + fn checked_require_sub( + &mut self, + arena: &mut RangeArena>, + lhs: ContextVarNode, + new_lhs: ContextVarNode, + rhs: ContextVarNode, + loc: Loc, + ctx: ContextNode, + ) -> Result, ExprErr> { + // x - y >= type(x).min + let new_lhs = new_lhs.latest_version(self); + let tmp_lhs = self.advance_var_in_ctx_forcible(new_lhs, loc, ctx, true)?; + + // in checked subtraction, we have to make sure x - y >= type(x).min ==> x >= type(x).min + y + // get the lhs min + let min_conc = lhs.ty_min_concrete(self).into_expr_err(loc)?.unwrap(); + let min: ContextVarNode = self.add_concrete_var(ctx, min_conc, loc)?; + + // require lhs - rhs >= type(lhs).min + if self + .require( + arena, + tmp_lhs.latest_version(self), + min, + ctx, + loc, + RangeOp::Gte, + RangeOp::Lte, + (RangeOp::Lte, RangeOp::Gte), + )? + .is_none() + { + return Ok(Some(ExprRet::CtxKilled(KilledKind::Revert))); + } + + // If x and y are signed ints, we have to check that x - -y <= type(x).max + // because it could overflow in the positive direction + let lhs_is_int = lhs.is_int(self).into_expr_err(loc)?; + let rhs_is_int = rhs.is_int(self).into_expr_err(loc)?; + if lhs_is_int && rhs_is_int { + let rhs_min = rhs + .evaled_range_min(self, arena) + .into_expr_err(loc)? + .expect("No range?"); + if rhs_min.is_negative(false, self, arena).into_expr_err(loc)? { + // rhs can be negative, require that lhs <= type(x).max + -rhs + // get the lhs max + let max_conc = lhs.ty_max_concrete(self).into_expr_err(loc)?.unwrap(); + let max: ContextVarNode = self.add_concrete_var(ctx, max_conc, loc)?; + + if self + .require( + arena, + tmp_lhs.latest_version(self), + max, + ctx, + loc, + RangeOp::Lte, + RangeOp::Gte, + (RangeOp::Gte, RangeOp::Lte), + )? + .is_none() + { + return Ok(Some(ExprRet::CtxKilled(KilledKind::Revert))); + } + } + } + Ok(None) + } + + fn checked_require_add( + &mut self, + arena: &mut RangeArena>, + lhs: ContextVarNode, + new_lhs: ContextVarNode, + rhs: ContextVarNode, + loc: Loc, + ctx: ContextNode, + ) -> Result, ExprErr> { + // lhs + rhs <= type(lhs).max + let new_lhs = new_lhs.latest_version(self); + let tmp_lhs = self.advance_var_in_ctx_forcible(new_lhs, loc, ctx, true)?; + + // get type(lhs).max + let max_conc = lhs.ty_max_concrete(self).into_expr_err(loc)?.unwrap(); + let max = self.add_concrete_var(ctx, max_conc, loc)?; + + // require lhs + rhs <= type(lhs).max + if self + .require( + arena, + tmp_lhs.latest_version(self), + max, + ctx, + loc, + RangeOp::Lte, + RangeOp::Gte, + (RangeOp::Gte, RangeOp::Lte), + )? + .is_none() + { + return Ok(Some(ExprRet::CtxKilled(KilledKind::Revert))); + } + + // If x and y are signed ints, we have to check that x + -y >= type(x).min + // because it could overflow in the negative direction + let lhs_is_int = lhs.is_int(self).into_expr_err(loc)?; + let rhs_is_int = rhs.is_int(self).into_expr_err(loc)?; + if lhs_is_int && rhs_is_int { + let rhs_min_is_negative = rhs + .evaled_range_min(self, arena) + .into_expr_err(loc)? + .expect("No range?") + .is_negative(false, self, arena) + .into_expr_err(loc)?; + if rhs_min_is_negative { + // rhs can be negative, require that lhs + rhs >= type(x).min + // get the lhs min + let min_conc = lhs.ty_min_concrete(self).into_expr_err(loc)?.unwrap(); + let min = self.add_concrete_var(ctx, min_conc, loc)?; + + if self + .require( + arena, + new_lhs.latest_version(self), + min, + ctx, + loc, + RangeOp::Gte, + RangeOp::Lte, + (RangeOp::Lte, RangeOp::Gte), + )? + .is_none() + { + return Ok(Some(ExprRet::CtxKilled(KilledKind::Revert))); + } + } + } + + Ok(None) + } + + fn checked_require_mul( + &mut self, + arena: &mut RangeArena>, + lhs: ContextVarNode, + new_lhs: ContextVarNode, + rhs: ContextVarNode, + loc: Loc, + ctx: ContextNode, + ) -> Result, ExprErr> { + // lhs * rhs <= type(lhs).max + let new_lhs = new_lhs.latest_version(self); + let tmp_lhs = self.advance_var_in_ctx_forcible(new_lhs, loc, ctx, true)?; + + // get type(lhs).max + let max_conc = lhs.ty_max_concrete(self).into_expr_err(loc)?.unwrap(); + let max = self.add_concrete_var(ctx, max_conc, loc)?; + + // require lhs * rhs <= type(lhs).max + if self + .require( + arena, + tmp_lhs.latest_version(self), + max, + ctx, + loc, + RangeOp::Lte, + RangeOp::Gte, + (RangeOp::Gte, RangeOp::Lte), + )? + .is_none() + { + return Ok(Some(ExprRet::CtxKilled(KilledKind::Revert))); + } + + // If x and y are signed ints, we have to check that x * -y >= type(x).min + // because it could overflow in the negative direction + let lhs_is_int = lhs.is_int(self).into_expr_err(loc)?; + let rhs_is_int = rhs.is_int(self).into_expr_err(loc)?; + if lhs_is_int || rhs_is_int { + let rhs_min_is_negative = rhs + .evaled_range_min(self, arena) + .into_expr_err(loc)? + .expect("No range?") + .is_negative(false, self, arena) + .into_expr_err(loc)?; + let lhs_min_is_negative = lhs + .evaled_range_min(self, arena) + .into_expr_err(loc)? + .expect("No range?") + .is_negative(false, self, arena) + .into_expr_err(loc)?; + let rhs_max_is_positive = !rhs + .evaled_range_max(self, arena) + .into_expr_err(loc)? + .expect("No range?") + .is_negative(true, self, arena) + .into_expr_err(loc)?; + let lhs_max_is_positive = !lhs + .evaled_range_max(self, arena) + .into_expr_err(loc)? + .expect("No range?") + .is_negative(true, self, arena) + .into_expr_err(loc)?; + + let can_go_very_negative = lhs_min_is_negative && rhs_max_is_positive + || rhs_min_is_negative && lhs_max_is_positive; + if can_go_very_negative { + // signs can be opposite so require that lhs * rhs >= type(x).min + // get the lhs min + let min_conc = lhs.ty_min_concrete(self).into_expr_err(loc)?.unwrap(); + let min = self.add_concrete_var(ctx, min_conc, loc)?; + + if self + .require( + arena, + new_lhs.latest_version(self), + min, + ctx, + loc, + RangeOp::Gte, + RangeOp::Lte, + (RangeOp::Lte, RangeOp::Gte), + )? + .is_none() + { + return Ok(Some(ExprRet::CtxKilled(KilledKind::Revert))); + } + } + } + + Ok(None) + } + + fn checked_require_exp( + &mut self, + arena: &mut RangeArena>, + lhs: ContextVarNode, + new_lhs: ContextVarNode, + rhs: ContextVarNode, + loc: Loc, + ctx: ContextNode, + ) -> Result, ExprErr> { + // exponent must be greater or equal to zero + let zero = rhs.ty_zero_concrete(self).into_expr_err(loc)?.unwrap(); + let zero = self.add_concrete_var(ctx, zero, loc)?; + if self + .require( + arena, + rhs, + zero, + ctx, + loc, + RangeOp::Gte, + RangeOp::Lte, + (RangeOp::Lte, RangeOp::Gte), + )? + .is_none() + { + return Ok(Some(ExprRet::CtxKilled(KilledKind::Revert))); + } + + // lhs ** rhs <= type(lhs).max + let new_lhs = new_lhs.latest_version(self); + let tmp_lhs = self.advance_var_in_ctx_forcible(new_lhs, loc, ctx, true)?; + + // get type(lhs).max + let max_conc = lhs.ty_max_concrete(self).into_expr_err(loc)?.unwrap(); + let max = self.add_concrete_var(ctx, max_conc, loc)?; + + // require lhs ** rhs <= type(lhs).max + if self + .require( + arena, + tmp_lhs.latest_version(self), + max, + ctx, + loc, + RangeOp::Lte, + RangeOp::Gte, + (RangeOp::Gte, RangeOp::Lte), + )? + .is_none() + { + return Ok(Some(ExprRet::CtxKilled(KilledKind::Revert))); + } + + // If x is signed int, we have to check that x ** y >= type(x).min + // because it could overflow in the negative direction + let lhs_is_int = lhs.is_int(self).into_expr_err(loc)?; + if lhs_is_int { + let lhs_min_is_negative = lhs + .evaled_range_min(self, arena) + .into_expr_err(loc)? + .expect("No range?") + .is_negative(false, self, arena) + .into_expr_err(loc)?; + if lhs_min_is_negative { + // rhs can be negative, require that lhs + rhs >= type(x).min + // get the lhs min + let min_conc = lhs.ty_min_concrete(self).into_expr_err(loc)?.unwrap(); + let min = self.add_concrete_var(ctx, min_conc, loc)?; + + if self + .require( + arena, + new_lhs.latest_version(self), + min, + ctx, + loc, + RangeOp::Gte, + RangeOp::Lte, + (RangeOp::Lte, RangeOp::Gte), + )? + .is_none() + { + return Ok(Some(ExprRet::CtxKilled(KilledKind::Revert))); + } + } + } + + Ok(None) + } } diff --git a/crates/solc-expressions/src/cmp.rs b/crates/solc-expressions/src/cmp.rs index fe29b227..e66b53f7 100644 --- a/crates/solc-expressions/src/cmp.rs +++ b/crates/solc-expressions/src/cmp.rs @@ -3,10 +3,12 @@ use crate::{ContextBuilder, ExprErr, ExpressionParser, IntoExprErr}; use graph::{ elem::*, nodes::{ - BuiltInNode, Builtin, ContextNode, ContextVar, ContextVarNode, ExprRet, TmpConstruction, + BuiltInNode, Builtin, Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, + TmpConstruction, }, AnalyzerBackend, GraphError, Node, Range, SolcRange, VarType, }; +use shared::RangeArena; use solang_parser::pt::{Expression, Loc}; use std::cmp::Ordering; @@ -15,9 +17,15 @@ impl Cmp for T where T: AnalyzerBackend /// Handles comparator operations, i.e: `!` pub trait Cmp: AnalyzerBackend + Sized { #[tracing::instrument(level = "trace", skip_all)] - fn not(&mut self, loc: Loc, lhs_expr: &Expression, ctx: ContextNode) -> Result<(), ExprErr> { - self.parse_ctx_expr(lhs_expr, ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + fn not( + &mut self, + arena: &mut RangeArena>, + loc: Loc, + lhs_expr: &Expression, + ctx: ContextNode, + ) -> Result<(), ExprErr> { + self.parse_ctx_expr(arena, lhs_expr, ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( loc, @@ -29,12 +37,18 @@ pub trait Cmp: AnalyzerBackend + Sized { ctx.push_expr(lhs, analyzer).into_expr_err(loc)?; return Ok(()); } - analyzer.not_inner(ctx, loc, lhs.flatten()) + analyzer.not_inner(arena, ctx, loc, lhs.flatten()) }) } #[tracing::instrument(level = "trace", skip_all)] - fn not_inner(&mut self, ctx: ContextNode, loc: Loc, lhs_expr: ExprRet) -> Result<(), ExprErr> { + fn not_inner( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + loc: Loc, + lhs_expr: ExprRet, + ) -> Result<(), ExprErr> { match lhs_expr { ExprRet::CtxKilled(kind) => { ctx.kill(self, loc, kind).into_expr_err(loc)?; @@ -50,10 +64,10 @@ pub trait Cmp: AnalyzerBackend + Sized { RangeOp::Not, Elem::Null, )); - elem.arenaize(self); + elem.arenaize(self, arena); let mut range = SolcRange::new(elem.clone(), elem, vec![]); - range.cache_eval(self).into_expr_err(loc)?; + range.cache_eval(self, arena).into_expr_err(loc)?; let out_var = ContextVar { loc: Some(loc), name: format!( @@ -95,15 +109,16 @@ pub trait Cmp: AnalyzerBackend + Sized { #[tracing::instrument(level = "trace", skip_all)] fn cmp( &mut self, + arena: &mut RangeArena>, loc: Loc, lhs_expr: &Expression, op: RangeOp, rhs_expr: &Expression, ctx: ContextNode, ) -> Result<(), ExprErr> { - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { - analyzer.parse_ctx_expr(rhs_expr, ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { + analyzer.parse_ctx_expr(arena, rhs_expr, ctx)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( loc, @@ -117,8 +132,8 @@ pub trait Cmp: AnalyzerBackend + Sized { return Ok(()); } - analyzer.parse_ctx_expr(lhs_expr, ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.parse_ctx_expr(arena, lhs_expr, ctx)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( @@ -131,7 +146,7 @@ pub trait Cmp: AnalyzerBackend + Sized { ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; return Ok(()); } - analyzer.cmp_inner(ctx, loc, &lhs_paths.flatten(), op, &rhs_paths) + analyzer.cmp_inner(arena, ctx, loc, &lhs_paths.flatten(), op, &rhs_paths) }) }) }) @@ -140,6 +155,7 @@ pub trait Cmp: AnalyzerBackend + Sized { #[tracing::instrument(level = "trace", skip_all)] fn cmp_inner( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, lhs_paths: &ExprRet, @@ -152,14 +168,15 @@ pub trait Cmp: AnalyzerBackend + Sized { ContextVarNode::from(*lhs) .literal_cast_from(&ContextVarNode::from(*rhs), self) .into_expr_err(loc)?; - self.cmp_inner(ctx, loc, &ExprRet::Single(*rhs), op, rhs_paths) + self.cmp_inner(arena, ctx, loc, &ExprRet::Single(*rhs), op, rhs_paths) } (ExprRet::SingleLiteral(lhs), ExprRet::SingleLiteral(rhs)) => { let lhs_cvar = ContextVarNode::from(*lhs).latest_version(self); let rhs_cvar = ContextVarNode::from(*rhs).latest_version(self); - lhs_cvar.try_increase_size(self).into_expr_err(loc)?; - rhs_cvar.try_increase_size(self).into_expr_err(loc)?; + lhs_cvar.try_increase_size(self, arena).into_expr_err(loc)?; + rhs_cvar.try_increase_size(self, arena).into_expr_err(loc)?; self.cmp_inner( + arena, ctx, loc, &ExprRet::Single(lhs_cvar.into()), @@ -171,7 +188,7 @@ pub trait Cmp: AnalyzerBackend + Sized { ContextVarNode::from(*rhs) .literal_cast_from(&ContextVarNode::from(*lhs), self) .into_expr_err(loc)?; - self.cmp_inner(ctx, loc, lhs_paths, op, &ExprRet::Single(*rhs)) + self.cmp_inner(arena, ctx, loc, lhs_paths, op, &ExprRet::Single(*rhs)) } (ExprRet::Single(lhs), ExprRet::Single(rhs)) => { let lhs_cvar = ContextVarNode::from(*lhs); @@ -243,13 +260,13 @@ pub trait Cmp: AnalyzerBackend + Sized { (l @ ExprRet::Single(_lhs), ExprRet::Multi(rhs_sides)) => { rhs_sides .iter() - .try_for_each(|expr_ret| self.cmp_inner(ctx, loc, l, op, expr_ret))?; + .try_for_each(|expr_ret| self.cmp_inner(arena, ctx, loc, l, op, expr_ret))?; Ok(()) } (ExprRet::Multi(lhs_sides), r @ ExprRet::Single(_)) => { lhs_sides .iter() - .try_for_each(|expr_ret| self.cmp_inner(ctx, loc, expr_ret, op, r))?; + .try_for_each(|expr_ret| self.cmp_inner(arena, ctx, loc, expr_ret, op, r))?; Ok(()) } (ExprRet::Multi(lhs_sides), ExprRet::Multi(rhs_sides)) => { @@ -257,13 +274,13 @@ pub trait Cmp: AnalyzerBackend + Sized { if lhs_sides.len() == rhs_sides.len() { lhs_sides.iter().zip(rhs_sides.iter()).try_for_each( |(lhs_expr_ret, rhs_expr_ret)| { - self.cmp_inner(ctx, loc, lhs_expr_ret, op, rhs_expr_ret) + self.cmp_inner(arena, ctx, loc, lhs_expr_ret, op, rhs_expr_ret) }, )?; Ok(()) } else { rhs_sides.iter().try_for_each(|rhs_expr_ret| { - self.cmp_inner(ctx, loc, lhs_paths, op, rhs_expr_ret) + self.cmp_inner(arena, ctx, loc, lhs_paths, op, rhs_expr_ret) })?; Ok(()) } @@ -282,10 +299,10 @@ pub trait Cmp: AnalyzerBackend + Sized { // lhs_cvar: ContextVarNode, // ) -> Result { // if let Some(lhs_range) = lhs_cvar.ref_range(self).into_expr_err(loc)? { - // let lhs_min = lhs_range.evaled_range_min(self).into_expr_err(loc)?; + // let lhs_min = lhs_range.evaled_range_min(self, arena).into_expr_err(loc)?; // // invert - // if lhs_min.range_eq(&lhs_range.evaled_range_max(self).into_expr_err(loc)?, self) { + // if lhs_min.range_eq(&lhs_range.minimize(self, arena).into_expr_err(loc)?, self) { // let val = Elem::Expr(RangeExpr::new( // lhs_range.range_min().into_owned(), // RangeOp::Not, @@ -314,6 +331,7 @@ pub trait Cmp: AnalyzerBackend + Sized { fn range_eval( &self, + arena: &mut RangeArena>, _ctx: ContextNode, lhs_cvar: ContextVarNode, rhs_cvar: ContextVarNode, @@ -326,17 +344,17 @@ pub trait Cmp: AnalyzerBackend + Sized { // if lhs_max < rhs_min, we know this cmp will evaluate to // true - let lhs_max = lhs_range.evaled_range_max(self)?; - let rhs_min = rhs_range.evaled_range_min(self)?; - if let Some(Ordering::Less) = lhs_max.range_ord(&rhs_min, self) { + let lhs_max = lhs_range.evaled_range_max(self, arena)?; + let rhs_min = rhs_range.evaled_range_min(self, arena)?; + if let Some(Ordering::Less) = lhs_max.range_ord(&rhs_min, arena) { return Ok(true.into()); } // Similarly if lhs_min >= rhs_max, we know this cmp will evaluate to // false - let lhs_min = lhs_range.evaled_range_min(self)?; - let rhs_max = rhs_range.evaled_range_max(self)?; - match lhs_min.range_ord(&rhs_max, self) { + let lhs_min = lhs_range.evaled_range_min(self, arena)?; + let rhs_max = rhs_range.evaled_range_max(self, arena)?; + match lhs_min.range_ord(&rhs_max, arena) { Some(Ordering::Greater) => { return Ok(false.into()); } @@ -349,17 +367,17 @@ pub trait Cmp: AnalyzerBackend + Sized { RangeOp::Gt => { // if lhs_min > rhs_max, we know this cmp will evaluate to // true - let lhs_min = lhs_range.evaled_range_min(self)?; - let rhs_max = rhs_range.evaled_range_max(self)?; - if let Some(Ordering::Greater) = lhs_min.range_ord(&rhs_max, self) { + let lhs_min = lhs_range.evaled_range_min(self, arena)?; + let rhs_max = rhs_range.evaled_range_max(self, arena)?; + if let Some(Ordering::Greater) = lhs_min.range_ord(&rhs_max, arena) { return Ok(true.into()); } // if lhs_max <= rhs_min, we know this cmp will evaluate to // false - let lhs_max = lhs_range.evaled_range_max(self)?; - let rhs_min = rhs_range.evaled_range_min(self)?; - match lhs_max.range_ord(&rhs_min, self) { + let lhs_max = lhs_range.evaled_range_max(self, arena)?; + let rhs_min = rhs_range.evaled_range_min(self, arena)?; + match lhs_max.range_ord(&rhs_min, arena) { Some(Ordering::Less) => { return Ok(false.into()); } @@ -372,9 +390,9 @@ pub trait Cmp: AnalyzerBackend + Sized { RangeOp::Lte => { // if lhs_max <= rhs_min, we know this cmp will evaluate to // true - let lhs_max = lhs_range.evaled_range_max(self)?; - let rhs_min = rhs_range.evaled_range_min(self)?; - match lhs_max.range_ord(&rhs_min, self) { + let lhs_max = lhs_range.evaled_range_max(self, arena)?; + let rhs_min = rhs_range.evaled_range_min(self, arena)?; + match lhs_max.range_ord(&rhs_min, arena) { Some(Ordering::Less) => { return Ok(true.into()); } @@ -386,18 +404,18 @@ pub trait Cmp: AnalyzerBackend + Sized { // Similarly if lhs_min > rhs_max, we know this cmp will evaluate to // false - let lhs_min = lhs_range.evaled_range_min(self)?; - let rhs_max = rhs_range.evaled_range_max(self)?; - if let Some(Ordering::Greater) = lhs_min.range_ord(&rhs_max, self) { + let lhs_min = lhs_range.evaled_range_min(self, arena)?; + let rhs_max = rhs_range.evaled_range_max(self, arena)?; + if let Some(Ordering::Greater) = lhs_min.range_ord(&rhs_max, arena) { return Ok(false.into()); } } RangeOp::Gte => { // if lhs_min >= rhs_max, we know this cmp will evaluate to // true - let lhs_min = lhs_range.evaled_range_min(self)?; - let rhs_max = rhs_range.evaled_range_max(self)?; - match lhs_min.range_ord(&rhs_max, self) { + let lhs_min = lhs_range.evaled_range_min(self, arena)?; + let rhs_max = rhs_range.evaled_range_max(self, arena)?; + match lhs_min.range_ord(&rhs_max, arena) { Some(Ordering::Greater) => { return Ok(true.into()); } @@ -409,30 +427,30 @@ pub trait Cmp: AnalyzerBackend + Sized { // if lhs_max < rhs_min, we know this cmp will evaluate to // false - let lhs_max = lhs_range.evaled_range_max(self)?; - let rhs_min = rhs_range.evaled_range_min(self)?; - if let Some(Ordering::Less) = lhs_max.range_ord(&rhs_min, self) { + let lhs_max = lhs_range.evaled_range_max(self, arena)?; + let rhs_min = rhs_range.evaled_range_min(self, arena)?; + if let Some(Ordering::Less) = lhs_max.range_ord(&rhs_min, arena) { return Ok(false.into()); } } RangeOp::Eq => { // if all elems are equal we know its true // we dont know anything else - let lhs_min = lhs_range.evaled_range_min(self)?; - let lhs_max = lhs_range.evaled_range_max(self)?; - let rhs_min = rhs_range.evaled_range_min(self)?; - let rhs_max = rhs_range.evaled_range_max(self)?; + let lhs_min = lhs_range.evaled_range_min(self, arena)?; + let lhs_max = lhs_range.evaled_range_max(self, arena)?; + let rhs_min = rhs_range.evaled_range_min(self, arena)?; + let rhs_max = rhs_range.evaled_range_max(self, arena)?; if let ( Some(Ordering::Equal), Some(Ordering::Equal), Some(Ordering::Equal), ) = ( // check lhs_min == lhs_max, ensures lhs is const - lhs_min.range_ord(&lhs_max, self), + lhs_min.range_ord(&lhs_max, arena), // check lhs_min == rhs_min, checks if lhs == rhs - lhs_min.range_ord(&rhs_min, self), + lhs_min.range_ord(&rhs_min, arena), // check rhs_min == rhs_max, ensures rhs is const - rhs_min.range_ord(&rhs_max, self), + rhs_min.range_ord(&rhs_max, arena), ) { return Ok(true.into()); } @@ -440,21 +458,21 @@ pub trait Cmp: AnalyzerBackend + Sized { RangeOp::Neq => { // if all elems are equal we know its true // we dont know anything else - let lhs_min = lhs_range.evaled_range_min(self)?; - let lhs_max = lhs_range.evaled_range_max(self)?; - let rhs_min = rhs_range.evaled_range_min(self)?; - let rhs_max = rhs_range.evaled_range_max(self)?; + let lhs_min = lhs_range.evaled_range_min(self, arena)?; + let lhs_max = lhs_range.evaled_range_max(self, arena)?; + let rhs_min = rhs_range.evaled_range_min(self, arena)?; + let rhs_max = rhs_range.evaled_range_max(self, arena)?; if let ( Some(Ordering::Equal), Some(Ordering::Equal), Some(Ordering::Equal), ) = ( // check lhs_min == lhs_max, ensures lhs is const - lhs_min.range_ord(&lhs_max, self), + lhs_min.range_ord(&lhs_max, arena), // check lhs_min == rhs_min, checks if lhs == rhs - lhs_min.range_ord(&rhs_min, self), + lhs_min.range_ord(&rhs_min, arena), // check rhs_min == rhs_max, ensures rhs is const - rhs_min.range_ord(&rhs_max, self), + rhs_min.range_ord(&rhs_max, arena), ) { return Ok(false.into()); } diff --git a/crates/solc-expressions/src/cond_op.rs b/crates/solc-expressions/src/cond_op.rs index 95b06017..c2d0f178 100644 --- a/crates/solc-expressions/src/cond_op.rs +++ b/crates/solc-expressions/src/cond_op.rs @@ -3,10 +3,11 @@ use crate::{ }; use graph::{ - nodes::{Context, ContextNode}, + elem::Elem, + nodes::{Concrete, Context, ContextNode}, AnalyzerBackend, ContextEdge, Edge, Node, }; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; use solang_parser::pt::CodeLocation; use solang_parser::pt::{Expression, Loc, Statement}; @@ -19,13 +20,14 @@ pub trait CondOp: AnalyzerBackend + Requir /// Handles a conditional operation like `if .. else ..` fn cond_op_stmt( &mut self, + arena: &mut RangeArena>, loc: Loc, if_expr: &Expression, true_stmt: &Statement, false_stmt: &Option>, ctx: ContextNode, ) -> Result<(), ExprErr> { - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let tctx = Context::new_subctx(ctx, None, loc, Some("true"), None, false, analyzer, None) .into_expr_err(loc)?; @@ -56,10 +58,12 @@ pub trait CondOp: AnalyzerBackend + Requir ); // we want to check if the true branch is possible to take - analyzer.true_fork_if_cvar(if_expr.clone(), true_subctx)?; + analyzer.true_fork_if_cvar(arena, if_expr.clone(), true_subctx)?; let mut true_killed = false; if true_subctx.is_killed(analyzer).into_expr_err(loc)? - || true_subctx.unreachable(analyzer).into_expr_err(loc)? + || true_subctx + .unreachable(analyzer, arena) + .into_expr_err(loc)? { // it was killed, therefore true branch is unreachable. // since it is unreachable, we want to not create @@ -68,10 +72,12 @@ pub trait CondOp: AnalyzerBackend + Requir } // we want to check if the false branch is possible to take - analyzer.false_fork_if_cvar(if_expr.clone(), false_subctx)?; + analyzer.false_fork_if_cvar(arena, if_expr.clone(), false_subctx)?; let mut false_killed = false; if false_subctx.is_killed(analyzer).into_expr_err(loc)? - || false_subctx.unreachable(analyzer).into_expr_err(loc)? + || false_subctx + .unreachable(analyzer, arena) + .into_expr_err(loc)? { // it was killed, therefore true branch is unreachable. // since it is unreachable, we want to not create @@ -90,12 +96,17 @@ pub trait CondOp: AnalyzerBackend + Requir // the true context has been killed, delete child, process the false fork expression // in the parent context and parse the false body ctx.delete_child(analyzer).into_expr_err(loc)?; - analyzer.false_fork_if_cvar(if_expr.clone(), ctx)?; + analyzer.false_fork_if_cvar(arena, if_expr.clone(), ctx)?; if let Some(false_stmt) = false_stmt { - return analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, _loc| { - analyzer.parse_ctx_statement(false_stmt, false, Some(ctx)); - Ok(()) - }); + return analyzer.apply_to_edges( + ctx, + loc, + arena, + &|analyzer, arena, ctx, _loc| { + analyzer.parse_ctx_statement(arena, false_stmt, false, Some(ctx)); + Ok(()) + }, + ); } } (false, true) => { @@ -103,9 +114,10 @@ pub trait CondOp: AnalyzerBackend + Requir // the false context has been killed, delete child, process the true fork expression // in the parent context and parse the true body ctx.delete_child(analyzer).into_expr_err(loc)?; - analyzer.true_fork_if_cvar(if_expr.clone(), ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, _loc| { + analyzer.true_fork_if_cvar(arena, if_expr.clone(), ctx)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, _loc| { analyzer.parse_ctx_statement( + arena, true_stmt, ctx.unchecked(analyzer).into_expr_err(loc)?, Some(ctx), @@ -116,20 +128,27 @@ pub trait CondOp: AnalyzerBackend + Requir (false, false) => { // println!("NEITHER KILLED"); // both branches are reachable. process each body - analyzer.apply_to_edges(true_subctx, loc, &|analyzer, ctx, _loc| { - analyzer.parse_ctx_statement( - true_stmt, - ctx.unchecked(analyzer).into_expr_err(loc)?, - Some(ctx), - ); - Ok(()) - })?; + analyzer.apply_to_edges( + true_subctx, + loc, + arena, + &|analyzer, arena, ctx, _loc| { + analyzer.parse_ctx_statement( + arena, + true_stmt, + ctx.unchecked(analyzer).into_expr_err(loc)?, + Some(ctx), + ); + Ok(()) + }, + )?; if let Some(false_stmt) = false_stmt { return analyzer.apply_to_edges( false_subctx, loc, - &|analyzer, ctx, _loc| { - analyzer.parse_ctx_statement(false_stmt, false, Some(ctx)); + arena, + &|analyzer, arena, ctx, _loc| { + analyzer.parse_ctx_statement(arena, false_stmt, false, Some(ctx)); Ok(()) }, ); @@ -146,6 +165,7 @@ pub trait CondOp: AnalyzerBackend + Requir #[tracing::instrument(level = "trace", skip_all)] fn cond_op_expr( &mut self, + arena: &mut RangeArena>, loc: Loc, if_expr: &Expression, true_expr: &Expression, @@ -153,7 +173,7 @@ pub trait CondOp: AnalyzerBackend + Requir ctx: ContextNode, ) -> Result<(), ExprErr> { tracing::trace!("conditional operator"); - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let tctx = Context::new_subctx(ctx, None, loc, Some("true"), None, false, analyzer, None) .into_expr_err(loc)?; @@ -183,14 +203,14 @@ pub trait CondOp: AnalyzerBackend + Requir Edge::Context(ContextEdge::Subcontext), ); - analyzer.true_fork_if_cvar(if_expr.clone(), true_subctx)?; - analyzer.apply_to_edges(true_subctx, loc, &|analyzer, ctx, _loc| { - analyzer.parse_ctx_expr(true_expr, ctx) + analyzer.true_fork_if_cvar(arena, if_expr.clone(), true_subctx)?; + analyzer.apply_to_edges(true_subctx, loc, arena, &|analyzer, arena, ctx, _loc| { + analyzer.parse_ctx_expr(arena, true_expr, ctx) })?; - analyzer.false_fork_if_cvar(if_expr.clone(), false_subctx)?; - analyzer.apply_to_edges(false_subctx, loc, &|analyzer, ctx, _loc| { - analyzer.parse_ctx_expr(false_expr, ctx) + analyzer.false_fork_if_cvar(arena, if_expr.clone(), false_subctx)?; + analyzer.apply_to_edges(false_subctx, loc, arena, &|analyzer, arena, ctx, _loc| { + analyzer.parse_ctx_expr(arena, false_expr, ctx) }) }) } @@ -198,25 +218,32 @@ pub trait CondOp: AnalyzerBackend + Requir /// Creates the true_fork cvar (updates bounds assuming its true) fn true_fork_if_cvar( &mut self, + arena: &mut RangeArena>, if_expr: Expression, true_fork_ctx: ContextNode, ) -> Result<(), ExprErr> { - self.apply_to_edges(true_fork_ctx, if_expr.loc(), &|analyzer, ctx, _loc| { - analyzer.handle_require(&[if_expr.clone()], ctx)?; - Ok(()) - }) + self.apply_to_edges( + true_fork_ctx, + if_expr.loc(), + arena, + &|analyzer, arena, ctx, _loc| { + analyzer.handle_require(arena, &[if_expr.clone()], ctx)?; + Ok(()) + }, + ) } /// Creates the false_fork cvar (inverts the expression and sets the bounds assuming its false) fn false_fork_if_cvar( &mut self, + arena: &mut RangeArena>, if_expr: Expression, false_fork_ctx: ContextNode, ) -> Result<(), ExprErr> { let loc = if_expr.loc(); let inv_if_expr = self.inverse_expr(if_expr); - self.apply_to_edges(false_fork_ctx, loc, &|analyzer, ctx, _loc| { - analyzer.handle_require(&[inv_if_expr.clone()], ctx)?; + self.apply_to_edges(false_fork_ctx, loc, arena, &|analyzer, arena, ctx, _loc| { + analyzer.handle_require(arena, &[inv_if_expr.clone()], ctx)?; Ok(()) }) } diff --git a/crates/solc-expressions/src/context_builder/expr.rs b/crates/solc-expressions/src/context_builder/expr.rs index d5e03f0e..ded16451 100644 --- a/crates/solc-expressions/src/context_builder/expr.rs +++ b/crates/solc-expressions/src/context_builder/expr.rs @@ -9,6 +9,7 @@ use graph::{ nodes::{Builtin, Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet}, AnalyzerBackend, ContextEdge, Edge, Node, }; +use shared::RangeArena; use ethers_core::types::I256; use solang_parser::{ @@ -26,15 +27,20 @@ pub trait ExpressionParser: AnalyzerBackend + Sized + ExprTyParser { /// Perform setup for parsing an expression - fn parse_ctx_expr(&mut self, expr: &Expression, ctx: ContextNode) -> Result<(), ExprErr> { + fn parse_ctx_expr( + &mut self, + arena: &mut RangeArena>, + expr: &Expression, + ctx: ContextNode, + ) -> Result<(), ExprErr> { if !ctx.killed_or_ret(self).unwrap() { let edges = ctx.live_edges(self).into_expr_err(expr.loc())?; if edges.is_empty() { - self.parse_ctx_expr_inner(expr, ctx) + self.parse_ctx_expr_inner(arena, expr, ctx) } else { edges .iter() - .try_for_each(|fork_ctx| self.parse_ctx_expr(expr, *fork_ctx))?; + .try_for_each(|fork_ctx| self.parse_ctx_expr(arena, expr, *fork_ctx))?; Ok(()) } } else { @@ -44,7 +50,12 @@ pub trait ExpressionParser: #[tracing::instrument(level = "trace", skip_all, fields(ctx = %ctx.path(self).replace('.', "\n\t.")))] /// Perform parsing of an expression - fn parse_ctx_expr_inner(&mut self, expr: &Expression, ctx: ContextNode) -> Result<(), ExprErr> { + fn parse_ctx_expr_inner( + &mut self, + arena: &mut RangeArena>, + expr: &Expression, + ctx: ContextNode, + ) -> Result<(), ExprErr> { use Expression::*; // tracing::trace!( // "ctx: {}, current stack: {:?}, \nexpr: {:?}\n", @@ -70,7 +81,7 @@ pub trait ExpressionParser: HexNumberLiteral(loc, b, _unit) => self.hex_num_literal(ctx, *loc, b, false), HexLiteral(hexes) => self.hex_literals(ctx, hexes), RationalNumberLiteral(loc, integer, fraction, exp, unit) => { - self.rational_number_literal(ctx, *loc, integer, fraction, exp, unit) + self.rational_number_literal(arena, ctx, *loc, integer, fraction, exp, unit) } Negate(_loc, expr) => match &**expr { NumberLiteral(loc, int, exp, unit) => { @@ -78,8 +89,8 @@ pub trait ExpressionParser: } HexNumberLiteral(loc, b, _unit) => self.hex_num_literal(ctx, *loc, b, true), e => { - self.parse_ctx_expr(e, ctx)?; - self.apply_to_edges(ctx, e.loc(), &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, e, ctx)?; + self.apply_to_edges(ctx, e.loc(), arena, &|analyzer, arena, ctx, loc| { tracing::trace!("Negate variable pop"); let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? @@ -119,11 +130,12 @@ pub trait ExpressionParser: analyzer.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); ContextVarNode::from(node) - .cast_from(&ContextVarNode::from(zero), analyzer) + .cast_from(&ContextVarNode::from(zero), analyzer, arena) .into_expr_err(loc)?; let lhs_paths = ExprRet::Single(zero); analyzer.op_match( + arena, ctx, loc, &lhs_paths, @@ -140,9 +152,10 @@ pub trait ExpressionParser: // Binary ops Power(loc, lhs_expr, rhs_expr) => { - self.op_expr(*loc, lhs_expr, rhs_expr, ctx, RangeOp::Exp, false) + self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::Exp, false) } Add(loc, lhs_expr, rhs_expr) => self.op_expr( + arena, *loc, lhs_expr, rhs_expr, @@ -151,6 +164,7 @@ pub trait ExpressionParser: false, ), AssignAdd(loc, lhs_expr, rhs_expr) => self.op_expr( + arena, *loc, lhs_expr, rhs_expr, @@ -159,6 +173,7 @@ pub trait ExpressionParser: true, ), Subtract(loc, lhs_expr, rhs_expr) => self.op_expr( + arena, *loc, lhs_expr, rhs_expr, @@ -167,6 +182,7 @@ pub trait ExpressionParser: false, ), AssignSubtract(loc, lhs_expr, rhs_expr) => self.op_expr( + arena, *loc, lhs_expr, rhs_expr, @@ -175,6 +191,7 @@ pub trait ExpressionParser: true, ), Multiply(loc, lhs_expr, rhs_expr) => self.op_expr( + arena, *loc, lhs_expr, rhs_expr, @@ -183,6 +200,7 @@ pub trait ExpressionParser: false, ), AssignMultiply(loc, lhs_expr, rhs_expr) => self.op_expr( + arena, *loc, lhs_expr, rhs_expr, @@ -190,62 +208,76 @@ pub trait ExpressionParser: RangeOp::Mul(ctx.unchecked(self).into_expr_err(*loc)?), true, ), - Divide(loc, lhs_expr, rhs_expr) => { - self.op_expr(*loc, lhs_expr, rhs_expr, ctx, RangeOp::Div(false), false) - } - AssignDivide(loc, lhs_expr, rhs_expr) => { - self.op_expr(*loc, lhs_expr, rhs_expr, ctx, RangeOp::Div(false), true) - } + Divide(loc, lhs_expr, rhs_expr) => self.op_expr( + arena, + *loc, + lhs_expr, + rhs_expr, + ctx, + RangeOp::Div(false), + false, + ), + AssignDivide(loc, lhs_expr, rhs_expr) => self.op_expr( + arena, + *loc, + lhs_expr, + rhs_expr, + ctx, + RangeOp::Div(false), + true, + ), Modulo(loc, lhs_expr, rhs_expr) => { - self.op_expr(*loc, lhs_expr, rhs_expr, ctx, RangeOp::Mod, false) + self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::Mod, false) } AssignModulo(loc, lhs_expr, rhs_expr) => { - self.op_expr(*loc, lhs_expr, rhs_expr, ctx, RangeOp::Mod, true) + self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::Mod, true) } ShiftLeft(loc, lhs_expr, rhs_expr) => { - self.op_expr(*loc, lhs_expr, rhs_expr, ctx, RangeOp::Shl, false) + self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::Shl, false) } AssignShiftLeft(loc, lhs_expr, rhs_expr) => { - self.op_expr(*loc, lhs_expr, rhs_expr, ctx, RangeOp::Shl, true) + self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::Shl, true) } ShiftRight(loc, lhs_expr, rhs_expr) => { - self.op_expr(*loc, lhs_expr, rhs_expr, ctx, RangeOp::Shr, false) + self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::Shr, false) } AssignShiftRight(loc, lhs_expr, rhs_expr) => { - self.op_expr(*loc, lhs_expr, rhs_expr, ctx, RangeOp::Shr, true) + self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::Shr, true) } ConditionalOperator(loc, if_expr, true_expr, false_expr) => { - self.cond_op_expr(*loc, if_expr, true_expr, false_expr, ctx) + self.cond_op_expr(arena, *loc, if_expr, true_expr, false_expr, ctx) } // Bitwise ops BitwiseAnd(loc, lhs_expr, rhs_expr) => { - self.op_expr(*loc, lhs_expr, rhs_expr, ctx, RangeOp::BitAnd, false) + self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::BitAnd, false) } AssignAnd(loc, lhs_expr, rhs_expr) => { - self.op_expr(*loc, lhs_expr, rhs_expr, ctx, RangeOp::BitAnd, true) + self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::BitAnd, true) } BitwiseXor(loc, lhs_expr, rhs_expr) => { - self.op_expr(*loc, lhs_expr, rhs_expr, ctx, RangeOp::BitXor, false) + self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::BitXor, false) } AssignXor(loc, lhs_expr, rhs_expr) => { - self.op_expr(*loc, lhs_expr, rhs_expr, ctx, RangeOp::BitXor, true) + self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::BitXor, true) } BitwiseOr(loc, lhs_expr, rhs_expr) => { - self.op_expr(*loc, lhs_expr, rhs_expr, ctx, RangeOp::BitOr, false) + self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::BitOr, false) } AssignOr(loc, lhs_expr, rhs_expr) => { - self.op_expr(*loc, lhs_expr, rhs_expr, ctx, RangeOp::BitOr, true) + self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::BitOr, true) } - BitwiseNot(loc, lhs_expr) => self.bit_not(*loc, lhs_expr, ctx), + BitwiseNot(loc, lhs_expr) => self.bit_not(arena, *loc, lhs_expr, ctx), // assign - Assign(loc, lhs_expr, rhs_expr) => self.assign_exprs(*loc, lhs_expr, rhs_expr, ctx), - List(loc, params) => self.list(ctx, *loc, params), + Assign(loc, lhs_expr, rhs_expr) => { + self.assign_exprs(arena, *loc, lhs_expr, rhs_expr, ctx) + } + List(loc, params) => self.list(arena, ctx, *loc, params), // array - ArraySubscript(_loc, ty_expr, None) => self.array_ty(ty_expr, ctx), + ArraySubscript(_loc, ty_expr, None) => self.array_ty(arena, ty_expr, ctx), ArraySubscript(loc, ty_expr, Some(index_expr)) => { - self.index_into_array(*loc, ty_expr, index_expr, ctx) + self.index_into_array(arena, *loc, ty_expr, index_expr, ctx) } ArraySlice(loc, _lhs_expr, _maybe_middle_expr, _maybe_rhs) => Err(ExprErr::Todo( *loc, @@ -257,17 +289,17 @@ pub trait ExpressionParser: )), // Comparator - Equal(loc, lhs, rhs) => self.cmp(*loc, lhs, RangeOp::Eq, rhs, ctx), - NotEqual(loc, lhs, rhs) => self.cmp(*loc, lhs, RangeOp::Neq, rhs, ctx), - Less(loc, lhs, rhs) => self.cmp(*loc, lhs, RangeOp::Lt, rhs, ctx), - More(loc, lhs, rhs) => self.cmp(*loc, lhs, RangeOp::Gt, rhs, ctx), - LessEqual(loc, lhs, rhs) => self.cmp(*loc, lhs, RangeOp::Lte, rhs, ctx), - MoreEqual(loc, lhs, rhs) => self.cmp(*loc, lhs, RangeOp::Gte, rhs, ctx), + Equal(loc, lhs, rhs) => self.cmp(arena, *loc, lhs, RangeOp::Eq, rhs, ctx), + NotEqual(loc, lhs, rhs) => self.cmp(arena, *loc, lhs, RangeOp::Neq, rhs, ctx), + Less(loc, lhs, rhs) => self.cmp(arena, *loc, lhs, RangeOp::Lt, rhs, ctx), + More(loc, lhs, rhs) => self.cmp(arena, *loc, lhs, RangeOp::Gt, rhs, ctx), + LessEqual(loc, lhs, rhs) => self.cmp(arena, *loc, lhs, RangeOp::Lte, rhs, ctx), + MoreEqual(loc, lhs, rhs) => self.cmp(arena, *loc, lhs, RangeOp::Gte, rhs, ctx), // Logical - Not(loc, expr) => self.not(*loc, expr, ctx), - And(loc, lhs, rhs) => self.cmp(*loc, lhs, RangeOp::And, rhs, ctx), - Or(loc, lhs, rhs) => self.cmp(*loc, lhs, RangeOp::Or, rhs, ctx), + Not(loc, expr) => self.not(arena, *loc, expr, ctx), + And(loc, lhs, rhs) => self.cmp(arena, *loc, lhs, RangeOp::And, rhs, ctx), + Or(loc, lhs, rhs) => self.cmp(arena, *loc, lhs, RangeOp::Or, rhs, ctx), // Function calls FunctionCallBlock(loc, _func_expr, _input_exprs) => { @@ -279,7 +311,7 @@ pub trait ExpressionParser: )) } NamedFunctionCall(loc, func_expr, input_args) => { - self.named_fn_call_expr(ctx, loc, func_expr, input_args) + self.named_fn_call_expr(arena, ctx, loc, func_expr, input_args) } FunctionCall(loc, func_expr, input_exprs) => { let updated_func_expr = match **func_expr { @@ -296,14 +328,14 @@ pub trait ExpressionParser: _ => func_expr.clone(), }; - self.fn_call_expr(ctx, loc, &updated_func_expr, input_exprs) + self.fn_call_expr(arena, ctx, loc, &updated_func_expr, input_exprs) } // member New(loc, expr) => { match &**expr { Expression::FunctionCall(_loc, func, inputs) => { // parse the type - self.new_call(loc, func, inputs, ctx) + self.new_call(arena, loc, func, inputs, ctx) } _ => panic!("Bad new call"), } @@ -323,7 +355,7 @@ pub trait ExpressionParser: Ok(()) } MemberAccess(loc, member_expr, ident) => { - self.member_access(*loc, member_expr, ident, ctx) + self.member_access(arena, *loc, member_expr, ident, ctx) } Delete(loc, expr) => { @@ -352,8 +384,8 @@ pub trait ExpressionParser: } } - self.parse_ctx_expr(expr, ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, expr, ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { tracing::trace!("Delete variable pop"); let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -373,15 +405,15 @@ pub trait ExpressionParser: } // de/increment stuff - PreIncrement(loc, expr) => self.pre_increment(expr, *loc, ctx), - PostIncrement(loc, expr) => self.post_increment(expr, *loc, ctx), - PreDecrement(loc, expr) => self.pre_decrement(expr, *loc, ctx), - PostDecrement(loc, expr) => self.post_decrement(expr, *loc, ctx), + PreIncrement(loc, expr) => self.pre_increment(arena, expr, *loc, ctx), + PostIncrement(loc, expr) => self.post_increment(arena, expr, *loc, ctx), + PreDecrement(loc, expr) => self.pre_decrement(arena, expr, *loc, ctx), + PostDecrement(loc, expr) => self.post_decrement(arena, expr, *loc, ctx), // Misc. - Variable(ident) => self.variable(ident, ctx, None), + Variable(ident) => self.variable(arena, ident, ctx, None), Type(loc, ty) => { - if let Some(builtin) = Builtin::try_from_ty(ty.clone(), self) { + if let Some(builtin) = Builtin::try_from_ty(ty.clone(), self, arena) { if let Some(idx) = self.builtins().get(&builtin) { ctx.push_expr(ExprRet::Single(*idx), self) .into_expr_err(*loc)?; @@ -398,7 +430,7 @@ pub trait ExpressionParser: Ok(()) } } - Parenthesis(_loc, expr) => self.parse_ctx_expr(expr, ctx), + Parenthesis(_loc, expr) => self.parse_ctx_expr(arena, expr, ctx), } } } diff --git a/crates/solc-expressions/src/context_builder/fn_calls.rs b/crates/solc-expressions/src/context_builder/fn_calls.rs index 3be6c374..34dcc8e7 100644 --- a/crates/solc-expressions/src/context_builder/fn_calls.rs +++ b/crates/solc-expressions/src/context_builder/fn_calls.rs @@ -251,8 +251,8 @@ pub trait FnCallBuilder: _ => {} }, - FunctionCallBlock(_, func_expr, _input_exprs) => match *func_expr { - Variable(ref ident) => { + FunctionCallBlock(_, func_expr, _input_exprs) => { + if let Variable(ref ident) = *func_expr { let loc = func_expr.loc(); let ctx = Context::new( caller, @@ -270,10 +270,9 @@ pub trait FnCallBuilder: self.add_fn_call(caller, func); } } - _ => {} - }, - NamedFunctionCall(_, func_expr, _input_args) => match *func_expr { - Variable(ref ident) => { + } + NamedFunctionCall(_, func_expr, input_args) => { + if let Variable(ref ident) = *func_expr { let loc = func_expr.loc(); let ctx = Context::new( caller, @@ -282,38 +281,36 @@ pub trait FnCallBuilder: ); let ctx = ContextNode::from(self.add_node(Node::Context(ctx))); let visible_funcs = ctx.visible_funcs(self).unwrap(); - let possible_funcs: Vec<_> = visible_funcs + let mut possible_funcs: Vec<_> = visible_funcs .into_iter() .filter(|f| f.name(self).unwrap().starts_with(&ident.name)) .collect(); + possible_funcs.retain(|func| func.params(self).len() == input_args.len()); if possible_funcs.len() == 1 { let func = possible_funcs[0]; self.add_fn_call(caller, func); } } - _ => {} - }, + } FunctionCall(_, func_expr, input_exprs) => { - match *func_expr { - Variable(ref ident) => { - let loc = func_expr.loc(); - let ctx = Context::new( - caller, - format!("<{}_parser_fn>", caller.name(self).unwrap()), - loc, - ); - let ctx = ContextNode::from(self.add_node(Node::Context(ctx))); - let visible_funcs = ctx.visible_funcs(self).unwrap(); - let possible_funcs: Vec<_> = visible_funcs - .into_iter() - .filter(|f| f.name(self).unwrap().starts_with(&ident.name)) - .collect(); - if possible_funcs.len() == 1 { - let func = possible_funcs[0]; - self.add_fn_call(caller, func); - } + if let Variable(ref ident) = *func_expr { + let loc = func_expr.loc(); + let ctx = Context::new( + caller, + format!("<{}_parser_fn>", caller.name(self).unwrap()), + loc, + ); + let ctx = ContextNode::from(self.add_node(Node::Context(ctx))); + let visible_funcs = ctx.visible_funcs(self).unwrap(); + let mut possible_funcs: Vec<_> = visible_funcs + .into_iter() + .filter(|f| f.name(self).unwrap().starts_with(&ident.name)) + .collect(); + possible_funcs.retain(|func| func.params(self).len() == input_exprs.len()); + if possible_funcs.len() == 1 { + let func = possible_funcs[0]; + self.add_fn_call(caller, func); } - _ => {} } input_exprs.iter().for_each(|expr| { diff --git a/crates/solc-expressions/src/context_builder/mod.rs b/crates/solc-expressions/src/context_builder/mod.rs index a4249979..d79b71d2 100644 --- a/crates/solc-expressions/src/context_builder/mod.rs +++ b/crates/solc-expressions/src/context_builder/mod.rs @@ -2,9 +2,11 @@ use crate::{ExprErr, IntoExprErr}; use graph::{ - nodes::{ContextNode, ContextVarNode, ExprRet, KilledKind}, - AnalyzerBackend, ContextEdge, Edge, GraphError, + elem::Elem, + nodes::{Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, KilledKind}, + AnalyzerBackend, ContextEdge, Edge, GraphError, Node, }; +use shared::RangeArena; use solang_parser::pt::{Expression, Loc}; @@ -51,13 +53,58 @@ pub trait ContextBuilder: } /// Match on the [`ExprRet`]s of a return statement and performs the return - fn return_match(&mut self, ctx: ContextNode, loc: &Loc, paths: &ExprRet) { + fn return_match( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + loc: &Loc, + paths: &ExprRet, + idx: usize, + ) { match paths { ExprRet::CtxKilled(kind) => { let _ = ctx.kill(self, *loc, *kind); } ExprRet::Single(expr) | ExprRet::SingleLiteral(expr) => { + // construct a variable from the return type + let target_var = ctx + .associated_fn(self) + .map(|func| { + let rets = func.returns(arena, self); + let Some(ret) = rets.get(idx) else { + return Ok(None) + }; + + ret.underlying(self) + .cloned() + .map(|underlying| { + ContextVar::new_from_func_ret(ctx, self, underlying).map(|var| { + var.map(|var| { + ContextVarNode::from(self.add_node(Node::ContextVar(var))) + }).ok_or(GraphError::NodeConfusion("Could not construct a context variable from function return".to_string())) + .map(Some) + }).and_then(|i| i) + }) + .and_then(|i| i) + }) + .and_then(|i| i) + .into_expr_err(*loc); + let latest = ContextVarNode::from(*expr).latest_version(self); + + match target_var { + Ok(Some(target_var)) => { + // perform a cast + let next = self + .advance_var_in_ctx_forcible(latest, *loc, ctx, true) + .unwrap(); + let res = next.cast_from(&target_var, self, arena).into_expr_err(*loc); + self.add_if_err(res); + } + Ok(None) => {} + Err(e) => self.add_expr_err(e), + } + // let ret = self.advance_var_in_ctx(latest, *loc, *ctx); let path = ctx.path(self); let res = latest.underlying_mut(self).into_expr_err(*loc); @@ -76,8 +123,8 @@ pub trait ContextBuilder: } } ExprRet::Multi(rets) => { - rets.iter().for_each(|expr_ret| { - self.return_match(ctx, loc, expr_ret); + rets.iter().enumerate().for_each(|(i, expr_ret)| { + self.return_match(arena, ctx, loc, expr_ret, i); }); } ExprRet::Null => {} @@ -91,7 +138,13 @@ pub trait ContextBuilder: &mut self, ctx: ContextNode, loc: Loc, - closure: &impl Fn(&mut Self, ContextNode, Loc) -> Result<(), ExprErr>, + arena: &mut RangeArena>, + closure: &impl Fn( + &mut Self, + &mut RangeArena>, + ContextNode, + Loc, + ) -> Result<(), ExprErr>, ) -> Result<(), ExprErr> { let live_edges = ctx.live_edges(self).into_expr_err(loc)?; tracing::trace!( @@ -106,14 +159,14 @@ pub trait ContextBuilder: } else { live_edges .iter() - .try_for_each(|ctx| closure(self, *ctx, loc)) + .try_for_each(|ctx| closure(self, arena, *ctx, loc)) } } else if live_edges.is_empty() { - closure(self, ctx, loc) + closure(self, arena, ctx, loc) } else { live_edges .iter() - .try_for_each(|ctx| closure(self, *ctx, loc)) + .try_for_each(|ctx| closure(self, arena, *ctx, loc)) } } else { Ok(()) @@ -126,7 +179,13 @@ pub trait ContextBuilder: &mut self, ctx: ContextNode, loc: Loc, - closure: &impl Fn(&mut Self, ContextNode, Loc) -> Result, + arena: &mut RangeArena>, + closure: &impl Fn( + &mut Self, + &mut RangeArena>, + ContextNode, + Loc, + ) -> Result, ) -> Result, ExprErr> { let live_edges = ctx.live_edges(self).into_expr_err(loc)?; tracing::trace!( @@ -136,11 +195,11 @@ pub trait ContextBuilder: ); if live_edges.is_empty() { - Ok(vec![closure(self, ctx, loc)?]) + Ok(vec![closure(self, arena, ctx, loc)?]) } else { live_edges .iter() - .map(|ctx| closure(self, *ctx, loc)) + .map(|ctx| closure(self, arena, *ctx, loc)) .collect::, ExprErr>>() } } diff --git a/crates/solc-expressions/src/context_builder/stmt.rs b/crates/solc-expressions/src/context_builder/stmt.rs index 85a6b170..07137a0f 100644 --- a/crates/solc-expressions/src/context_builder/stmt.rs +++ b/crates/solc-expressions/src/context_builder/stmt.rs @@ -7,13 +7,14 @@ use crate::{ }; use graph::{ + elem::Elem, nodes::{ - Context, ContextNode, ContextVar, ContextVarNode, ExprRet, FunctionNode, FunctionParamNode, - FunctionReturnNode, KilledKind, + Concrete, Context, ContextNode, ContextVar, ContextVarNode, ExprRet, FunctionNode, + FunctionParamNode, FunctionReturnNode, KilledKind, }, AnalyzerBackend, ContextEdge, Edge, Node, }; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; use petgraph::{visit::EdgeRef, Direction}; use solang_parser::{ @@ -33,6 +34,7 @@ pub trait StatementParser: /// Performs setup for parsing a solidity statement fn parse_ctx_statement( &mut self, + arena: &mut RangeArena>, stmt: &Statement, unchecked: bool, parent_ctx: Option + Copy>, @@ -48,19 +50,24 @@ pub trait StatementParser: self.add_if_err(ctx.live_edges(self).into_expr_err(stmt.loc())) { if live_edges.is_empty() { - self.parse_ctx_stmt_inner(stmt, unchecked, parent_ctx) + self.parse_ctx_stmt_inner(arena, stmt, unchecked, parent_ctx) } else { live_edges.iter().for_each(|fork_ctx| { - self.parse_ctx_stmt_inner(stmt, unchecked, Some(*fork_ctx)); + self.parse_ctx_stmt_inner( + arena, + stmt, + unchecked, + Some(*fork_ctx), + ); }); } } } } - _ => self.parse_ctx_stmt_inner(stmt, unchecked, parent_ctx), + _ => self.parse_ctx_stmt_inner(arena, stmt, unchecked, parent_ctx), } } else { - self.parse_ctx_stmt_inner(stmt, unchecked, parent_ctx) + self.parse_ctx_stmt_inner(arena, stmt, unchecked, parent_ctx) } } @@ -68,6 +75,7 @@ pub trait StatementParser: /// Performs parsing of a solidity statement fn parse_ctx_stmt_inner( &mut self, + arena: &mut RangeArena>, stmt: &Statement, unchecked: bool, parent_ctx: Option + Copy>, @@ -206,11 +214,12 @@ pub trait StatementParser: if !mods_set { let parent = FunctionNode::from(parent.into()); let _ = self - .set_modifiers(parent, ctx_node.into()) + .set_modifiers(arena, parent, ctx_node.into()) .map_err(|e| self.add_expr_err(e)); } let res = self.func_call_inner( + arena, true, ctx_node.into(), parent.into().into(), @@ -223,20 +232,25 @@ pub trait StatementParser: if self.widen_if_limit_hit(ctx_node.into(), res) { return; } - let res = self.apply_to_edges(ctx_node.into(), *loc, &|analyzer, ctx, loc| { - if ctx.killed_or_ret(analyzer).into_expr_err(loc)? { - tracing::trace!("killing due to bad funciton call"); - let res = ContextNode::from(ctx_node) - .kill( - analyzer, - fn_loc, - ctx.underlying(analyzer).unwrap().killed.unwrap().1, - ) - .into_expr_err(fn_loc); - let _ = analyzer.add_if_err(res); - } - Ok(()) - }); + let res = self.apply_to_edges( + ctx_node.into(), + *loc, + arena, + &|analyzer, arena, ctx, loc| { + if ctx.killed_or_ret(analyzer).into_expr_err(loc)? { + tracing::trace!("killing due to bad funciton call"); + let res = ContextNode::from(ctx_node) + .kill( + analyzer, + fn_loc, + ctx.underlying(analyzer).unwrap().killed.unwrap().1, + ) + .into_expr_err(fn_loc); + let _ = analyzer.add_if_err(res); + } + Ok(()) + }, + ); if self.widen_if_limit_hit(ctx_node.into(), res) { return; @@ -245,12 +259,17 @@ pub trait StatementParser: return; } - let res = self.apply_to_edges(ctx_node.into(), *loc, &|analyzer, ctx, _loc| { - statements - .iter() - .for_each(|stmt| analyzer.parse_ctx_statement(stmt, *unchecked, Some(ctx))); - Ok(()) - }); + let res = self.apply_to_edges( + ctx_node.into(), + *loc, + arena, + &|analyzer, arena, ctx, _loc| { + statements.iter().for_each(|stmt| { + analyzer.parse_ctx_statement(arena, stmt, *unchecked, Some(ctx)) + }); + Ok(()) + }, + ); if self.widen_if_limit_hit(ctx_node.into(), res) {} } VariableDefinition(loc, var_decl, maybe_expr) => { @@ -265,58 +284,71 @@ pub trait StatementParser: ); if let Some(rhs) = maybe_expr { - match self.parse_ctx_expr(rhs, ctx) { + match self.parse_ctx_expr(arena, rhs, ctx) { Ok(()) => { - let res = self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - if !ctx.killed_or_ret(analyzer).into_expr_err(loc)? { - let Some(rhs_paths) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - format!( - "Variable definition had no right hand side, {}", - ctx.path(analyzer) - ), - )); - }; - - if matches!(rhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(rhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - analyzer.parse_ctx_expr(&var_decl.ty, ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { - let Some(lhs_paths) = ctx + let res = self.apply_to_edges( + ctx, + *loc, + arena, + &|analyzer, arena, ctx, loc| { + if !ctx.killed_or_ret(analyzer).into_expr_err(loc)? { + let Some(rhs_paths) = ctx .pop_expr_latest(loc, analyzer) .into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( + return Err(ExprErr::NoRhs( loc, - "Variable definition had no left hand side" - .to_string(), + format!( + "Variable definition had no right hand side, {}", + ctx.path(analyzer) + ), )); }; - if matches!(lhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_paths, analyzer) + if matches!(rhs_paths, ExprRet::CtxKilled(_)) { + ctx.push_expr(rhs_paths, analyzer) .into_expr_err(loc)?; return Ok(()); } - analyzer.match_var_def( + + analyzer.parse_ctx_expr(arena, &var_decl.ty, ctx)?; + analyzer.apply_to_edges( ctx, - var_decl, loc, - &lhs_paths, - Some(&rhs_paths), - )?; + arena, + &|analyzer, arena, ctx, loc| { + let Some(lhs_paths) = ctx + .pop_expr_latest(loc, analyzer) + .into_expr_err(loc)? + else { + return Err(ExprErr::NoLhs( + loc, + "Variable definition had no left hand side" + .to_string(), + )); + }; + + if matches!(lhs_paths, ExprRet::CtxKilled(_)) { + ctx.push_expr(lhs_paths, analyzer) + .into_expr_err(loc)?; + return Ok(()); + } + analyzer.match_var_def( + arena, + ctx, + var_decl, + loc, + &lhs_paths, + Some(&rhs_paths), + )?; + Ok(()) + }, + ) + } else { Ok(()) - }) - } else { - Ok(()) - } - }); + } + }, + ); let _ = self.widen_if_limit_hit(ctx, res); } ret => { @@ -324,26 +356,27 @@ pub trait StatementParser: } } } else { - let res = self.parse_ctx_expr(&var_decl.ty, ctx); + let res = self.parse_ctx_expr(arena, &var_decl.ty, ctx); if self.widen_if_limit_hit(ctx, res) { return; } - let res = self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let Some(lhs_paths) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "Variable definition had no left hand side".to_string(), - )); - }; - if matches!(lhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.match_var_def(ctx, var_decl, loc, &lhs_paths, None)?; - Ok(()) - }); + let res = + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { + let Some(lhs_paths) = + ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + else { + return Err(ExprErr::NoLhs( + loc, + "Variable definition had no left hand side".to_string(), + )); + }; + if matches!(lhs_paths, ExprRet::CtxKilled(_)) { + ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; + return Ok(()); + } + analyzer.match_var_def(arena, ctx, var_decl, loc, &lhs_paths, None)?; + Ok(()) + }); let _ = self.widen_if_limit_hit(ctx, res); } } @@ -353,8 +386,8 @@ pub trait StatementParser: If(loc, if_expr, true_expr, maybe_false_expr) => { tracing::trace!("parsing if, {if_expr:?}"); let ctx = ContextNode::from(parent_ctx.expect("Dangling if statement").into()); - let res = self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - analyzer.cond_op_stmt(loc, if_expr, true_expr, maybe_false_expr, ctx) + let res = self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { + analyzer.cond_op_stmt(arena, loc, if_expr, true_expr, maybe_false_expr, ctx) }); let _ = self.widen_if_limit_hit(ctx, res); } @@ -364,7 +397,10 @@ pub trait StatementParser: let res = self.apply_to_edges( ContextNode::from(parent.into()), *loc, - &|analyzer, ctx, loc| analyzer.while_loop(loc, ctx, cond, body), + arena, + &|analyzer, arena, ctx, loc| { + analyzer.while_loop(arena, loc, ctx, cond, body) + }, ); let _ = self.widen_if_limit_hit(parent.into().into(), res); } @@ -373,21 +409,26 @@ pub trait StatementParser: tracing::trace!("parsing expr, {expr:?}"); if let Some(parent) = parent_ctx { let ctx = parent.into().into(); - match self.parse_ctx_expr(expr, ctx) { + match self.parse_ctx_expr(arena, expr, ctx) { Ok(()) => { - let res = self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - if ctx.killed_or_ret(analyzer).into_expr_err(loc)? { - tracing::trace!("killing due to bad expr"); - ContextNode::from(parent.into()) - .kill( - analyzer, - loc, - ctx.underlying(analyzer).unwrap().killed.unwrap().1, - ) - .into_expr_err(loc)?; - } - Ok(()) - }); + let res = self.apply_to_edges( + ctx, + *loc, + arena, + &|analyzer, arena, ctx, loc| { + if ctx.killed_or_ret(analyzer).into_expr_err(loc)? { + tracing::trace!("killing due to bad expr"); + ContextNode::from(parent.into()) + .kill( + analyzer, + loc, + ctx.underlying(analyzer).unwrap().killed.unwrap().1, + ) + .into_expr_err(loc)?; + } + Ok(()) + }, + ); let _ = self.widen_if_limit_hit(ctx, res); } e => { @@ -399,9 +440,13 @@ pub trait StatementParser: For(loc, maybe_for_start, maybe_for_middle, maybe_for_end, maybe_for_body) => { tracing::trace!("parsing for loop"); if let Some(parent) = parent_ctx { - let res = - self.apply_to_edges(parent.into().into(), *loc, &|analyzer, ctx, loc| { + let res = self.apply_to_edges( + parent.into().into(), + *loc, + arena, + &|analyzer, arena, ctx, loc| { analyzer.for_loop( + arena, loc, ctx, maybe_for_start, @@ -409,7 +454,8 @@ pub trait StatementParser: maybe_for_end, maybe_for_body, ) - }); + }, + ); let _ = self.widen_if_limit_hit(parent.into().into(), res); } } @@ -419,7 +465,10 @@ pub trait StatementParser: let res = self.apply_to_edges( ContextNode::from(parent.into()), *loc, - &|analyzer, ctx, loc| analyzer.while_loop(loc, ctx, while_expr, while_stmt), + arena, + &|analyzer, arena, ctx, loc| { + analyzer.while_loop(arena, loc, ctx, while_expr, while_stmt) + }, ); let _ = self.widen_if_limit_hit(parent.into().into(), res); } @@ -444,8 +493,12 @@ pub trait StatementParser: .expect("No context for variable definition?") .into(), ); - let res = self.apply_to_edges(ctx, *loc, &|analyzer, ctx, _loc| { - analyzer.parse_ctx_yul_statement(&YulStatement::Block(yul_block.clone()), ctx); + let res = self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, _loc| { + analyzer.parse_ctx_yul_statement( + arena, + &YulStatement::Block(yul_block.clone()), + ctx, + ); Ok(()) }); let _ = self.widen_if_limit_hit(ctx, res); @@ -454,14 +507,15 @@ pub trait StatementParser: tracing::trace!("parsing return"); if let Some(ret_expr) = maybe_ret_expr { if let Some(parent) = parent_ctx { - let res = self.parse_ctx_expr(ret_expr, parent.into().into()); + let res = self.parse_ctx_expr(arena, ret_expr, parent.into().into()); if self.widen_if_limit_hit(parent.into().into(), res) { return; } let res = self.apply_to_edges( parent.into().into(), *loc, - &|analyzer, ctx, loc| { + arena, + &|analyzer, arena, ctx, loc| { let Ok(Some(ret)) = ctx.pop_expr_latest(loc, analyzer) else { return Err(ExprErr::NoLhs( loc, @@ -483,7 +537,7 @@ pub trait StatementParser: let _ = analyzer.add_if_err(res); return Ok(()); } - analyzer.return_match(ctx, &loc, &paths); + analyzer.return_match(arena, ctx, &loc, &paths, 0); Ok(()) }, ); @@ -495,13 +549,14 @@ pub trait StatementParser: tracing::trace!("parsing revert"); if let Some(parent) = parent_ctx { let parent = ContextNode::from(parent.into()); - let res = self.apply_to_edges(parent, *loc, &|analyzer, ctx, loc| { - let res = ctx - .kill(analyzer, loc, KilledKind::Revert) - .into_expr_err(loc); - let _ = analyzer.add_if_err(res); - Ok(()) - }); + let res = + self.apply_to_edges(parent, *loc, arena, &|analyzer, arena, ctx, loc| { + let res = ctx + .kill(analyzer, loc, KilledKind::Revert) + .into_expr_err(loc); + let _ = analyzer.add_if_err(res); + Ok(()) + }); let _ = self.add_if_err(res); } } diff --git a/crates/solc-expressions/src/env.rs b/crates/solc-expressions/src/env.rs index 5795e670..414e6f12 100644 --- a/crates/solc-expressions/src/env.rs +++ b/crates/solc-expressions/src/env.rs @@ -3,10 +3,11 @@ use crate::{ }; use graph::{ + elem::Elem, nodes::{Builtin, Concrete, ContextNode, ContextVar, ExprRet}, AnalyzerBackend, ContextEdge, Edge, Node, }; -use shared::StorageLocation; +use shared::{RangeArena, StorageLocation}; use solang_parser::pt::{Expression, Identifier, Loc}; @@ -15,6 +16,7 @@ impl Env for T where T: AnalyzerBackend pub trait Env: AnalyzerBackend + Sized { fn env_variable( &mut self, + arena: &mut RangeArena>, ident: &Identifier, ctx: ContextNode, ) -> Result, ExprErr> { @@ -44,7 +46,7 @@ pub trait Env: AnalyzerBackend + Sized { { ctx.add_gas_cost(self, shared::gas::FUNC_CALL_GAS) .into_expr_err(ident.loc)?; - self.resume_from_modifier(ctx, mod_state.clone())?; + self.resume_from_modifier(arena, ctx, mod_state.clone())?; self.modifier_inherit_return(ctx, mod_state.parent_ctx); Ok(Some(())) } else { diff --git a/crates/solc-expressions/src/func_call/func_caller.rs b/crates/solc-expressions/src/func_call/func_caller.rs index 11090938..143b316b 100644 --- a/crates/solc-expressions/src/func_call/func_caller.rs +++ b/crates/solc-expressions/src/func_call/func_caller.rs @@ -10,13 +10,14 @@ use std::cell::RefCell; use std::rc::Rc; use graph::{ + elem::Elem, nodes::{ - Context, ContextNode, ContextVar, ContextVarNode, ExprRet, FunctionNode, FunctionParamNode, - ModifierState, + Concrete, Context, ContextNode, ContextVar, ContextVarNode, ExprRet, FunctionNode, + FunctionParamNode, ModifierState, }, AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node, }; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; use solang_parser::pt::{Expression, Loc, NamedArgument}; @@ -66,20 +67,21 @@ impl<'a> NamedOrUnnamedArgs<'a> { pub fn parse( &self, + arena: &mut RangeArena>, analyzer: &mut (impl AnalyzerBackend + Sized), ctx: ContextNode, loc: Loc, ) -> Result<(), ExprErr> { match self { - NamedOrUnnamedArgs::Unnamed(inner) => analyzer.parse_inputs(ctx, loc, inner), + NamedOrUnnamedArgs::Unnamed(inner) => analyzer.parse_inputs(arena, ctx, loc, inner), NamedOrUnnamedArgs::Named(inner) => { let append = Rc::new(RefCell::new(false)); inner.iter().try_for_each(|arg| { - analyzer.parse_input(ctx, loc, &arg.expr, &append)?; + analyzer.parse_input(arena, ctx, loc, &arg.expr, &append)?; Ok(()) })?; if !inner.is_empty() { - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(ret) = ctx.pop_tmp_expr(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( loc, @@ -97,6 +99,7 @@ impl<'a> NamedOrUnnamedArgs<'a> { pub fn parse_n( &self, + arena: &mut RangeArena>, n: usize, analyzer: &mut (impl AnalyzerBackend + Sized), ctx: ContextNode, @@ -106,11 +109,11 @@ impl<'a> NamedOrUnnamedArgs<'a> { match self { NamedOrUnnamedArgs::Unnamed(inner) => { inner.iter().take(n).try_for_each(|arg| { - analyzer.parse_input(ctx, loc, arg, &append)?; + analyzer.parse_input(arena, ctx, loc, arg, &append)?; Ok(()) })?; if !inner.is_empty() { - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(ret) = ctx.pop_tmp_expr(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( loc, @@ -125,11 +128,11 @@ impl<'a> NamedOrUnnamedArgs<'a> { } NamedOrUnnamedArgs::Named(inner) => { inner.iter().take(n).try_for_each(|arg| { - analyzer.parse_input(ctx, loc, &arg.expr, &append)?; + analyzer.parse_input(arena, ctx, loc, &arg.expr, &append)?; Ok(()) })?; if !inner.is_empty() { - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(ret) = ctx.pop_tmp_expr(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( loc, @@ -185,6 +188,7 @@ pub trait FuncCaller: /// Perform a function call with named inputs fn named_fn_call_expr( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: &Loc, func_expr: &Expression, @@ -193,13 +197,14 @@ pub trait FuncCaller: use solang_parser::pt::Expression::*; match func_expr { MemberAccess(loc, member_expr, ident) => self.call_name_spaced_func( + arena, ctx, loc, member_expr, ident, NamedOrUnnamedArgs::Named(input_exprs), ), - Variable(ident) => self.call_internal_named_func(ctx, loc, ident, input_exprs), + Variable(ident) => self.call_internal_named_func(arena, ctx, loc, ident, input_exprs), e => Err(ExprErr::IntrinsicNamedArgs( *loc, format!("Cannot call intrinsic functions with named arguments. Call: {e:?}"), @@ -210,6 +215,7 @@ pub trait FuncCaller: /// Perform a function call fn fn_call_expr( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: &Loc, func_expr: &Expression, @@ -218,6 +224,7 @@ pub trait FuncCaller: use solang_parser::pt::Expression::*; match func_expr { MemberAccess(loc, member_expr, ident) => self.call_name_spaced_func( + arena, ctx, loc, member_expr, @@ -225,6 +232,7 @@ pub trait FuncCaller: NamedOrUnnamedArgs::Unnamed(input_exprs), ), Variable(ident) => self.call_internal_func( + arena, ctx, loc, ident, @@ -232,8 +240,8 @@ pub trait FuncCaller: NamedOrUnnamedArgs::Unnamed(input_exprs), ), _ => { - self.parse_ctx_expr(func_expr, ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, func_expr, ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( loc, @@ -245,6 +253,7 @@ pub trait FuncCaller: return Ok(()); } analyzer.match_intrinsic_fallback( + arena, ctx, &loc, &NamedOrUnnamedArgs::Unnamed(input_exprs), @@ -258,6 +267,7 @@ pub trait FuncCaller: /// Perform an intrinsic function call fn match_intrinsic_fallback( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: &Loc, input_exprs: &NamedOrUnnamedArgs, @@ -265,11 +275,11 @@ pub trait FuncCaller: ) -> Result<(), ExprErr> { match ret { ExprRet::Single(func_idx) | ExprRet::SingleLiteral(func_idx) => { - self.intrinsic_func_call(loc, input_exprs, func_idx, ctx) + self.intrinsic_func_call(arena, loc, input_exprs, func_idx, ctx) } - ExprRet::Multi(inner) => inner - .into_iter() - .try_for_each(|ret| self.match_intrinsic_fallback(ctx, loc, input_exprs, ret)), + ExprRet::Multi(inner) => inner.into_iter().try_for_each(|ret| { + self.match_intrinsic_fallback(arena, ctx, loc, input_exprs, ret) + }), ExprRet::CtxKilled(kind) => ctx.kill(self, *loc, kind).into_expr_err(*loc), ExprRet::Null => Ok(()), } @@ -278,6 +288,7 @@ pub trait FuncCaller: /// Setups up storage variables for a function call and calls it fn setup_fn_call( &mut self, + arena: &mut RangeArena>, loc: &Loc, inputs: &ExprRet, func_idx: NodeIdx, @@ -301,7 +312,7 @@ pub trait FuncCaller: .into_expr_err(*loc)? .func_node(self) { - self.func_call(ctx, *loc, inputs, func_node, func_call_str, None) + self.func_call(arena, ctx, *loc, inputs, func_node, func_call_str, None) } else { unreachable!() } @@ -310,6 +321,7 @@ pub trait FuncCaller: /// Matches the input kinds and performs the call fn func_call( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, input_paths: &ExprRet, @@ -329,8 +341,9 @@ pub trait FuncCaller: // if we get a single var, we expect the func to only take a single // variable let inputs = vec![ContextVarNode::from(input_var).latest_version(self)]; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { analyzer.func_call_inner( + arena, false, ctx, func, @@ -362,8 +375,9 @@ pub trait FuncCaller: Ok(ContextVarNode::from(var).latest_version(self)) }) .collect::, ExprErr>>()?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { analyzer.func_call_inner( + arena, false, ctx, func, @@ -393,6 +407,7 @@ pub trait FuncCaller: #[tracing::instrument(level = "trace", skip_all)] fn func_call_inner( &mut self, + arena: &mut RangeArena>, entry_call: bool, ctx: ContextNode, func_node: FunctionNode, @@ -403,7 +418,7 @@ pub trait FuncCaller: modifier_state: &Option, ) -> Result<(), ExprErr> { if !entry_call { - if let Ok(true) = self.join(ctx, loc, func_node, params, inputs) { + if let Ok(true) = self.join(arena, ctx, loc, func_node, params, inputs, &mut vec![]) { return Ok(()); } } @@ -422,26 +437,56 @@ pub trait FuncCaller: // handle remapping of variable names and bringing variables into the new context let renamed_inputs = - self.map_inputs_to_params(loc, entry_call, params, inputs, callee_ctx)?; + self.map_inputs_to_params(arena, loc, entry_call, params, inputs, callee_ctx)?; // begin modifier handling by making sure modifiers were set if !func_node.modifiers_set(self).into_expr_err(loc)? { - self.set_modifiers(func_node, ctx)?; + self.set_modifiers(arena, func_node, ctx)?; } // get modifiers let mods = func_node.modifiers(self); - self.apply_to_edges(callee_ctx, loc, &|analyzer, callee_ctx, loc| { - if let Some(mod_state) = &ctx.underlying(analyzer).into_expr_err(loc)?.modifier_state { - // we are iterating through modifiers - if mod_state.num + 1 < mods.len() { - // use the next modifier - let mut mstate = mod_state.clone(); - mstate.num += 1; - analyzer.call_modifier_for_fn(loc, callee_ctx, func_node, mstate) + self.apply_to_edges( + callee_ctx, + loc, + arena, + &|analyzer, arena, callee_ctx, loc| { + if let Some(mod_state) = + &ctx.underlying(analyzer).into_expr_err(loc)?.modifier_state + { + // we are iterating through modifiers + if mod_state.num + 1 < mods.len() { + // use the next modifier + let mut mstate = mod_state.clone(); + mstate.num += 1; + analyzer.call_modifier_for_fn(arena, loc, callee_ctx, func_node, mstate) + } else { + // out of modifiers, execute the actual function call + analyzer.execute_call_inner( + arena, + loc, + ctx, + callee_ctx, + func_node, + &renamed_inputs, + func_call_str, + ) + } + } else if !mods.is_empty() { + // we have modifiers and havent executed them, start the process of executing them + let state = ModifierState::new( + 0, + loc, + func_node, + callee_ctx, + ctx, + renamed_inputs.clone(), + ); + analyzer.call_modifier_for_fn(arena, loc, callee_ctx, func_node, state) } else { - // out of modifiers, execute the actual function call + // no modifiers, just execute the function analyzer.execute_call_inner( + arena, loc, ctx, callee_ctx, @@ -450,29 +495,15 @@ pub trait FuncCaller: func_call_str, ) } - } else if !mods.is_empty() { - // we have modifiers and havent executed them, start the process of executing them - let state = - ModifierState::new(0, loc, func_node, callee_ctx, ctx, renamed_inputs.clone()); - analyzer.call_modifier_for_fn(loc, callee_ctx, func_node, state) - } else { - // no modifiers, just execute the function - analyzer.execute_call_inner( - loc, - ctx, - callee_ctx, - func_node, - &renamed_inputs, - func_call_str, - ) - } - }) + }, + ) } /// Actually executes the function // #[tracing::instrument(level = "trace", skip_all)] fn execute_call_inner( &mut self, + arena: &mut RangeArena>, loc: Loc, caller_ctx: ContextNode, callee_ctx: ContextNode, @@ -483,23 +514,19 @@ pub trait FuncCaller: tracing::trace!("executing: {}", func_node.name(self).into_expr_err(loc)?); if let Some(body) = func_node.underlying(self).into_expr_err(loc)?.body.clone() { // add return nodes into the subctx - func_node - .returns(self) - .to_vec() - .into_iter() - .for_each(|ret| { - if let Some(var) = ContextVar::maybe_new_from_func_ret( - self, - ret.underlying(self).unwrap().clone(), - ) { - let cvar = self.add_node(Node::ContextVar(var)); - callee_ctx.add_var(cvar.into(), self).unwrap(); - self.add_edge(cvar, callee_ctx, Edge::Context(ContextEdge::Variable)); - } - }); + #[allow(clippy::unnecessary_to_owned)] + func_node.returns(arena, self).into_iter().for_each(|ret| { + if let Some(var) = + ContextVar::maybe_new_from_func_ret(self, ret.underlying(self).unwrap().clone()) + { + let cvar = self.add_node(Node::ContextVar(var)); + callee_ctx.add_var(cvar.into(), self).unwrap(); + self.add_edge(cvar, callee_ctx, Edge::Context(ContextEdge::Variable)); + } + }); // parse the function body - self.parse_ctx_statement(&body, false, Some(callee_ctx)); + self.parse_ctx_statement(arena, &body, false, Some(callee_ctx)); if let Some(mod_state) = &callee_ctx .underlying(self) .into_expr_err(loc)? @@ -507,12 +534,12 @@ pub trait FuncCaller: .clone() { if mod_state.num == 0 { - return self.ctx_rets(loc, mod_state.parent_caller_ctx, callee_ctx); + return self.ctx_rets(arena, loc, mod_state.parent_caller_ctx, callee_ctx); } } if callee_ctx != caller_ctx { - self.ctx_rets(loc, caller_ctx, callee_ctx) + self.ctx_rets(arena, loc, caller_ctx, callee_ctx) } else { Ok(()) } @@ -541,10 +568,10 @@ pub trait FuncCaller: .set_child_call(ret_subctx, self) .into_expr_err(loc); let _ = self.add_if_err(res); - self.apply_to_edges(callee_ctx, loc, &|analyzer, ctx, loc| { + self.apply_to_edges(callee_ctx, loc, arena, &|analyzer, arena, ctx, loc| { + #[allow(clippy::unnecessary_to_owned)] func_node - .returns(analyzer) - .to_vec() + .returns(arena, analyzer) .into_iter() .try_for_each(|ret| { let underlying = ret.underlying(analyzer).unwrap(); diff --git a/crates/solc-expressions/src/func_call/helper.rs b/crates/solc-expressions/src/func_call/helper.rs index ffae7ce9..e5d87812 100644 --- a/crates/solc-expressions/src/func_call/helper.rs +++ b/crates/solc-expressions/src/func_call/helper.rs @@ -5,13 +5,14 @@ use crate::{ }; use graph::{ + elem::Elem, nodes::{ - CallFork, Context, ContextNode, ContextVar, ContextVarNode, ExprRet, FunctionNode, - FunctionParamNode, ModifierState, + CallFork, Concrete, Context, ContextNode, ContextVar, ContextVarNode, ExprRet, + FunctionNode, FunctionParamNode, ModifierState, }, AnalyzerBackend, ContextEdge, Edge, Node, Range, VarType, }; -use shared::{NodeIdx, StorageLocation}; +use shared::{NodeIdx, RangeArena, StorageLocation}; use solang_parser::pt::{CodeLocation, Expression, Loc}; @@ -24,6 +25,7 @@ pub trait CallerHelper: AnalyzerBackend + /// we map `y -> x` for future lookups fn map_inputs_to_params( &mut self, + arena: &mut RangeArena>, loc: Loc, entry_call: bool, params: &[FunctionParamNode], @@ -67,7 +69,7 @@ pub trait CallerHelper: AnalyzerBackend + if let Some(param_ty) = VarType::try_from_idx(self, param.ty(self).unwrap()) { if !node.ty_eq_ty(¶m_ty, self).unwrap() { - node.cast_from_ty(param_ty, self).unwrap(); + node.cast_from_ty(param_ty, self, arena).unwrap(); } } @@ -79,7 +81,8 @@ pub trait CallerHelper: AnalyzerBackend + if let Some(_len_var) = input.array_to_len_var(self) { // bring the length variable along as well - self.get_length(callee_ctx, loc, node, false).unwrap(); + self.get_length(arena, callee_ctx, loc, node, false) + .unwrap(); } let node = node.latest_version(self); @@ -92,12 +95,12 @@ pub trait CallerHelper: AnalyzerBackend + r.range_max().into_owned().cast(r2.range_max().into_owned()); let res = node .latest_version(self) - .try_set_range_min(self, new_min) + .try_set_range_min(self, arena, new_min) .into_expr_err(loc); self.add_if_err(res); let res = node .latest_version(self) - .try_set_range_max(self, new_max) + .try_set_range_max(self, arena, new_max) .into_expr_err(loc); self.add_if_err(res); let res = node @@ -123,6 +126,7 @@ pub trait CallerHelper: AnalyzerBackend + /// Parses input expressions into [`ExprRet`]s and adds them to the expr ret stack fn parse_inputs( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, inputs: &[Expression], @@ -135,10 +139,10 @@ pub trait CallerHelper: AnalyzerBackend + inputs .iter() - .try_for_each(|input| self.parse_input(ctx, loc, input, &append))?; + .try_for_each(|input| self.parse_input(arena, ctx, loc, input, &append))?; if !inputs.is_empty() { - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(ret) = ctx.pop_tmp_expr(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( loc, @@ -154,13 +158,14 @@ pub trait CallerHelper: AnalyzerBackend + fn parse_input( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, _loc: Loc, input: &Expression, append: &Rc>, ) -> Result<(), ExprErr> { - self.parse_ctx_expr(input, ctx)?; - self.apply_to_edges(ctx, input.loc(), &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, input, ctx)?; + self.apply_to_edges(ctx, input.loc(), arena, &|analyzer, arena, ctx, loc| { let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( loc, @@ -227,6 +232,7 @@ pub trait CallerHelper: AnalyzerBackend + /// Disambiguates a function call by their inputs (length & type) fn disambiguate_fn_call( &mut self, + arena: &mut RangeArena>, fn_name: &str, literals: Vec, input_paths: &ExprRet, @@ -236,7 +242,11 @@ pub trait CallerHelper: AnalyzerBackend + // try to find the function based on naive signature // This doesnt do type inference on NumberLiterals (i.e. 100 could be uintX or intX, and there could // be a function that takes an int256 but we evaled as uint256) - let fn_sig = format!("{}{}", fn_name, input_paths.try_as_func_input_str(self)); + let fn_sig = format!( + "{}{}", + fn_name, + input_paths.try_as_func_input_str(self, arena) + ); if let Some(func) = funcs.iter().find(|func| func.name(self).unwrap() == fn_sig) { return Some(*func); } @@ -296,6 +306,7 @@ pub trait CallerHelper: AnalyzerBackend + /// Handle returns for a function call fn ctx_rets( &mut self, + arena: &mut RangeArena>, loc: Loc, caller_ctx: ContextNode, callee_ctx: ContextNode, @@ -330,8 +341,8 @@ pub trait CallerHelper: AnalyzerBackend + match callee_ctx.underlying(self).into_expr_err(loc)?.child { Some(CallFork::Fork(w1, w2)) => { - self.ctx_rets(loc, caller_ctx, w1)?; - self.ctx_rets(loc, caller_ctx, w2)?; + self.ctx_rets(arena, loc, caller_ctx, w1)?; + self.ctx_rets(arena, loc, caller_ctx, w2)?; Ok(()) } Some(CallFork::Call(c)) @@ -339,7 +350,7 @@ pub trait CallerHelper: AnalyzerBackend + >= caller_ctx.underlying(self).into_expr_err(loc)?.depth => { // follow rabbit hole - self.ctx_rets(loc, caller_ctx, c)?; + self.ctx_rets(arena, loc, caller_ctx, c)?; Ok(()) } _ => { @@ -391,8 +402,7 @@ pub trait CallerHelper: AnalyzerBackend + let func_rets = callee_ctx .associated_fn(self) .into_expr_err(loc)? - .returns(self) - .to_vec(); + .returns(arena, self); func_rets .iter() .filter_map(|ret| { @@ -450,14 +460,12 @@ pub trait CallerHelper: AnalyzerBackend + .parent_ctx .associated_fn(self) .into_expr_err(loc)? - .returns(self) - .to_vec() + .returns(arena, self) } else { callee_ctx .associated_fn(self) .into_expr_err(loc)? - .returns(self) - .to_vec() + .returns(arena, self) }; let ret = rets @@ -471,7 +479,7 @@ pub trait CallerHelper: AnalyzerBackend + let tmp_ret = node .as_tmp(callee_ctx.underlying(self).unwrap().loc, ret_subctx, self) .unwrap(); - tmp_ret.cast_from_ty(target_ty, self).unwrap(); + tmp_ret.cast_from_ty(target_ty, self, arena).unwrap(); tmp_ret.underlying_mut(self).into_expr_err(loc)?.is_return = true; tmp_ret .underlying_mut(self) @@ -495,13 +503,14 @@ pub trait CallerHelper: AnalyzerBackend + /// Inherit the input changes from a function call fn inherit_input_changes( &mut self, + arena: &mut RangeArena>, loc: Loc, to_ctx: ContextNode, from_ctx: ContextNode, renamed_inputs: &BTreeMap, ) -> Result<(), ExprErr> { if to_ctx != from_ctx { - self.apply_to_edges(to_ctx, loc, &|analyzer, to_ctx, loc| { + self.apply_to_edges(to_ctx, loc, arena, &|analyzer, arena, to_ctx, loc| { renamed_inputs .iter() .try_for_each(|(input_var, updated_var)| { @@ -515,11 +524,19 @@ pub trait CallerHelper: AnalyzerBackend + latest_updated.range(analyzer).into_expr_err(loc)? { let res = new_input - .set_range_min(analyzer, updated_var_range.range_min().into_owned()) + .set_range_min( + analyzer, + arena, + updated_var_range.range_min().into_owned(), + ) .into_expr_err(loc); let _ = analyzer.add_if_err(res); let res = new_input - .set_range_max(analyzer, updated_var_range.range_max().into_owned()) + .set_range_max( + analyzer, + arena, + updated_var_range.range_max().into_owned(), + ) .into_expr_err(loc); let _ = analyzer.add_if_err(res); let res = new_input @@ -546,55 +563,67 @@ pub trait CallerHelper: AnalyzerBackend + /// Inherit the storage changes from a function call fn inherit_storage_changes( &mut self, + arena: &mut RangeArena>, loc: Loc, inheritor_ctx: ContextNode, grantor_ctx: ContextNode, ) -> Result<(), ExprErr> { if inheritor_ctx != grantor_ctx { - return self.apply_to_edges(inheritor_ctx, loc, &|analyzer, inheritor_ctx, loc| { - let vars = grantor_ctx.local_vars(analyzer).clone(); - vars.iter().try_for_each(|(name, old_var)| { - let var = old_var.latest_version(analyzer); - let underlying = var.underlying(analyzer).into_expr_err(loc)?; - if var.is_storage(analyzer).into_expr_err(loc)? { - if let Some(inheritor_var) = inheritor_ctx.var_by_name(analyzer, name) { - let inheritor_var = inheritor_var.latest_version(analyzer); - if let Some(r) = underlying.ty.range(analyzer).into_expr_err(loc)? { - let new_inheritor_var = analyzer - .advance_var_in_ctx( - inheritor_var, - underlying.loc.expect("No loc for val change"), - inheritor_ctx, - ) - .unwrap(); - let _ = new_inheritor_var - .set_range_min(analyzer, r.range_min().into_owned()); - let _ = new_inheritor_var - .set_range_max(analyzer, r.range_max().into_owned()); - let _ = new_inheritor_var - .set_range_exclusions(analyzer, r.exclusions.clone()); + return self.apply_to_edges( + inheritor_ctx, + loc, + arena, + &|analyzer, arena, inheritor_ctx, loc| { + let vars = grantor_ctx.local_vars(analyzer).clone(); + vars.iter().try_for_each(|(name, old_var)| { + let var = old_var.latest_version(analyzer); + let underlying = var.underlying(analyzer).into_expr_err(loc)?; + if var.is_storage(analyzer).into_expr_err(loc)? { + if let Some(inheritor_var) = inheritor_ctx.var_by_name(analyzer, name) { + let inheritor_var = inheritor_var.latest_version(analyzer); + if let Some(r) = underlying.ty.range(analyzer).into_expr_err(loc)? { + let new_inheritor_var = analyzer + .advance_var_in_ctx( + inheritor_var, + underlying.loc.expect("No loc for val change"), + inheritor_ctx, + ) + .unwrap(); + let _ = new_inheritor_var.set_range_min( + analyzer, + arena, + r.range_min().into_owned(), + ); + let _ = new_inheritor_var.set_range_max( + analyzer, + arena, + r.range_max().into_owned(), + ); + let _ = new_inheritor_var + .set_range_exclusions(analyzer, r.exclusions.clone()); + } + } else { + let new_in_inheritor = + analyzer.add_node(Node::ContextVar(underlying.clone())); + inheritor_ctx + .add_var(new_in_inheritor.into(), analyzer) + .into_expr_err(loc)?; + analyzer.add_edge( + new_in_inheritor, + inheritor_ctx, + Edge::Context(ContextEdge::Variable), + ); + analyzer.add_edge( + new_in_inheritor, + var, + Edge::Context(ContextEdge::InheritedVariable), + ); } - } else { - let new_in_inheritor = - analyzer.add_node(Node::ContextVar(underlying.clone())); - inheritor_ctx - .add_var(new_in_inheritor.into(), analyzer) - .into_expr_err(loc)?; - analyzer.add_edge( - new_in_inheritor, - inheritor_ctx, - Edge::Context(ContextEdge::Variable), - ); - analyzer.add_edge( - new_in_inheritor, - var, - Edge::Context(ContextEdge::InheritedVariable), - ); } - } - Ok(()) - }) - }); + Ok(()) + }) + }, + ); } Ok(()) } diff --git a/crates/solc-expressions/src/func_call/internal_call.rs b/crates/solc-expressions/src/func_call/internal_call.rs index 48b58f39..d771fbab 100644 --- a/crates/solc-expressions/src/func_call/internal_call.rs +++ b/crates/solc-expressions/src/func_call/internal_call.rs @@ -7,9 +7,11 @@ use crate::{ }; use graph::{ + elem::Elem, nodes::{Builtin, Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet}, AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node, VarType, }; +use shared::RangeArena; use solang_parser::pt::{Expression, Identifier, Loc, NamedArgument}; @@ -25,6 +27,7 @@ pub trait InternalFuncCaller: /// Perform a named function call fn call_internal_named_func( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: &Loc, ident: &Identifier, @@ -127,8 +130,8 @@ pub trait InternalFuncCaller: .iter() .find(|arg| arg.name.name == field.name(self).unwrap()) .expect("No field in struct in struct construction"); - self.parse_ctx_expr(&input.expr, ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, &input.expr, ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(assignment) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { @@ -140,12 +143,12 @@ pub trait InternalFuncCaller: return Ok(()); } - analyzer.match_assign_sides(ctx, loc, &field_as_ret, &assignment)?; + analyzer.match_assign_sides(arena, ctx, loc, &field_as_ret, &assignment)?; let _ = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?; Ok(()) }) })?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, _loc| { + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, _loc| { ctx.push_expr(ExprRet::Single(cvar), analyzer) .into_expr_err(*loc)?; Ok(()) @@ -172,13 +175,13 @@ pub trait InternalFuncCaller: input.expr.clone() }) .collect(); - self.parse_inputs(ctx, *loc, &inputs[..])?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_inputs(arena, ctx, *loc, &inputs[..])?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let inputs = ctx .pop_expr_latest(loc, analyzer) .into_expr_err(loc)? .unwrap_or_else(|| ExprRet::Multi(vec![])); - analyzer.setup_fn_call(&ident.loc, &inputs, func.into(), ctx, None) + analyzer.setup_fn_call(arena, &ident.loc, &inputs, func.into(), ctx, None) }) } else { todo!("Disambiguate named function call"); @@ -188,6 +191,7 @@ pub trait InternalFuncCaller: #[tracing::instrument(level = "trace", skip_all)] fn call_internal_func( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: &Loc, ident: &Identifier, @@ -232,8 +236,8 @@ pub trait InternalFuncCaller: match possible_funcs.len() { 0 => { // this is a builtin, cast, or unknown function - self.parse_ctx_expr(func_expr, ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, func_expr, ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let ret = ctx .pop_expr_latest(loc, analyzer) .into_expr_err(loc)? @@ -243,13 +247,13 @@ pub trait InternalFuncCaller: ctx.push_expr(ret, analyzer).into_expr_err(loc)?; return Ok(()); } - analyzer.match_intrinsic_fallback(ctx, &loc, &input_exprs, ret) + analyzer.match_intrinsic_fallback(arena, ctx, &loc, &input_exprs, ret) }) } 1 => { // there is only a single possible function - input_exprs.parse(self, ctx, *loc)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + input_exprs.parse(arena, self, ctx, *loc)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let mut inputs = ctx .pop_expr_latest(loc, analyzer) .into_expr_err(loc)? @@ -267,6 +271,7 @@ pub trait InternalFuncCaller: return Ok(()); } analyzer.setup_fn_call( + arena, &ident.loc, &inputs, (possible_funcs[0]).into(), @@ -277,8 +282,8 @@ pub trait InternalFuncCaller: } _ => { // this is the annoying case due to function overloading & type inference on number literals - input_exprs.parse(self, ctx, *loc)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + input_exprs.parse(arena, self, ctx, *loc)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let inputs = ctx .pop_expr_latest(loc, analyzer) .into_expr_err(loc)? @@ -303,18 +308,19 @@ pub trait InternalFuncCaller: }) .collect(); if let Some(func) = analyzer.disambiguate_fn_call( + arena, &ident.name, resizeables, &inputs, &possible_funcs, ) { - analyzer.setup_fn_call(&loc, &inputs, func.into(), ctx, None) + analyzer.setup_fn_call(arena, &loc, &inputs, func.into(), ctx, None) } else { Err(ExprErr::FunctionNotFound( loc, format!( "Could not disambiguate function, default input types: {}, possible functions: {:#?}", - inputs.try_as_func_input_str(analyzer), + inputs.try_as_func_input_str(analyzer, arena), possible_funcs .iter() .map(|i| i.name(analyzer).unwrap()) diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/abi.rs b/crates/solc-expressions/src/func_call/intrinsic_call/abi.rs index 80eaee23..589b729d 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/abi.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/abi.rs @@ -2,9 +2,11 @@ use crate::func_caller::NamedOrUnnamedArgs; use crate::{ContextBuilder, ExprErr, ExpressionParser, IntoExprErr}; use graph::{ - nodes::{Builtin, ContextNode, ContextVar, ExprRet}, + elem::Elem, + nodes::{Builtin, Concrete, ContextNode, ContextVar, ExprRet}, AnalyzerBackend, ContextEdge, Edge, Node, }; +use shared::RangeArena; use solang_parser::pt::{Expression, Loc}; @@ -15,6 +17,7 @@ pub trait AbiCaller: AnalyzerBackend + Siz /// Perform an `abi.<..>` function call fn abi_call( &mut self, + arena: &mut RangeArena>, func_name: String, input_exprs: &NamedOrUnnamedArgs, loc: Loc, @@ -90,8 +93,8 @@ pub trait AbiCaller: AnalyzerBackend + Siz } } let input_exprs = input_exprs.unnamed_args().unwrap(); - self.parse_ctx_expr(&input_exprs[1], ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, &input_exprs[1], ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( loc, diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/array.rs b/crates/solc-expressions/src/func_call/intrinsic_call/array.rs index 0c883067..5a3286f4 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/array.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/array.rs @@ -8,6 +8,7 @@ use graph::{ nodes::{Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet}, AnalyzerBackend, Node, }; +use shared::RangeArena; use ethers_core::types::U256; use solang_parser::pt::{Expression, Loc}; @@ -19,6 +20,7 @@ pub trait ArrayCaller: AnalyzerBackend + S /// Perform an `array.<..>` function call fn array_call( &mut self, + arena: &mut RangeArena>, func_name: String, input_exprs: &NamedOrUnnamedArgs, loc: Loc, @@ -29,8 +31,8 @@ pub trait ArrayCaller: AnalyzerBackend + S if input_exprs.len() == 1 { // array.push() is valid syntax. It pushes a new // empty element onto the expr ret stack - self.parse_ctx_expr(&input_exprs.unnamed_args().unwrap()[0], ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, &input_exprs.unnamed_args().unwrap()[0], ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(array) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -50,12 +52,13 @@ pub trait ArrayCaller: AnalyzerBackend + S // get length let len = analyzer - .get_length(ctx, loc, arr, true)? + .get_length(arena, ctx, loc, arr, true)? .unwrap() .latest_version(analyzer); // get the index access and add it to the stack - let _ = analyzer.index_into_array_raw(ctx, loc, len, arr, false, false)?; + let _ = analyzer + .index_into_array_raw(arena, ctx, loc, len, arr, false, false)?; // create a temporary 1 variable let cnode = @@ -73,12 +76,13 @@ pub trait ArrayCaller: AnalyzerBackend + S // add 1 to the length let tmp_len = - analyzer.op(loc, len, one, ctx, RangeOp::Add(false), false)?; + analyzer.op(arena, loc, len, one, ctx, RangeOp::Add(false), false)?; let tmp_len = ContextVarNode::from(tmp_len.expect_single().unwrap()); tmp_len.underlying_mut(analyzer).unwrap().is_tmp = false; analyzer.set_var_as_length( + arena, ctx, loc, tmp_len, @@ -89,8 +93,8 @@ pub trait ArrayCaller: AnalyzerBackend + S }) } else if input_exprs.len() == 2 { // array.push(value) - self.parse_ctx_expr(&input_exprs.unnamed_args().unwrap()[0], ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, &input_exprs.unnamed_args().unwrap()[0], ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(array) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( @@ -102,8 +106,12 @@ pub trait ArrayCaller: AnalyzerBackend + S ctx.push_expr(array, analyzer).into_expr_err(loc)?; return Ok(()); } - analyzer.parse_ctx_expr(&input_exprs.unnamed_args().unwrap()[1], ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.parse_ctx_expr( + arena, + &input_exprs.unnamed_args().unwrap()[1], + ctx, + )?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(new_elem) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { @@ -126,13 +134,13 @@ pub trait ArrayCaller: AnalyzerBackend + S // get length let len = analyzer - .get_length(ctx, loc, arr, true)? + .get_length(arena, ctx, loc, arr, true)? .unwrap() .latest_version(analyzer); // get the index access for the *previous* length let index_access = analyzer - .index_into_array_raw(ctx, loc, len, arr, false, true)? + .index_into_array_raw(arena, ctx, loc, len, arr, false, true)? .unwrap(); // create a temporary 1 variable let cnode = @@ -149,14 +157,22 @@ pub trait ArrayCaller: AnalyzerBackend + S let one = ContextVarNode::from(analyzer.add_node(tmp_one)); // add 1 to the length - let tmp_len = - analyzer.op(loc, len, one, ctx, RangeOp::Add(false), false)?; + let tmp_len = analyzer.op( + arena, + loc, + len, + one, + ctx, + RangeOp::Add(false), + false, + )?; let tmp_len = ContextVarNode::from(tmp_len.expect_single().unwrap()); tmp_len.underlying_mut(analyzer).unwrap().is_tmp = false; // set the new length analyzer.set_var_as_length( + arena, ctx, loc, tmp_len, @@ -166,14 +182,15 @@ pub trait ArrayCaller: AnalyzerBackend + S // update the index access's range let elem = Elem::from(pushed_value); index_access - .set_range_min(analyzer, elem.clone()) + .set_range_min(analyzer, arena, elem.clone()) .into_expr_err(loc)?; index_access - .set_range_max(analyzer, elem.clone()) + .set_range_max(analyzer, arena, elem.clone()) .into_expr_err(loc)?; // update the array using the index access analyzer.update_array_from_index_access( + arena, ctx, loc, len, @@ -202,8 +219,8 @@ pub trait ArrayCaller: AnalyzerBackend + S ), )); } - self.parse_ctx_expr(&input_exprs.unnamed_args().unwrap()[0], ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, &input_exprs.unnamed_args().unwrap()[0], ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(array) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( loc, @@ -222,7 +239,7 @@ pub trait ArrayCaller: AnalyzerBackend + S // get length let len = analyzer - .get_length(ctx, loc, arr, true)? + .get_length(arena, ctx, loc, arr, true)? .unwrap() .latest_version(analyzer); @@ -235,25 +252,33 @@ pub trait ArrayCaller: AnalyzerBackend + S let one = ContextVarNode::from(analyzer.add_node(tmp_one)); // subtract 1 from the length - let tmp_len = analyzer.op(loc, len, one, ctx, RangeOp::Sub(false), false)?; + let tmp_len = + analyzer.op(arena, loc, len, one, ctx, RangeOp::Sub(false), false)?; let tmp_len = ContextVarNode::from(tmp_len.expect_single().unwrap()); tmp_len.underlying_mut(analyzer).unwrap().is_tmp = false; // get the index access let index_access = analyzer - .index_into_array_raw(ctx, loc, tmp_len, arr, false, true)? + .index_into_array_raw(arena, ctx, loc, tmp_len, arr, false, true)? .unwrap(); - analyzer.set_var_as_length(ctx, loc, tmp_len, arr.latest_version(analyzer))?; + analyzer.set_var_as_length( + arena, + ctx, + loc, + tmp_len, + arr.latest_version(analyzer), + )?; index_access - .set_range_min(analyzer, Elem::Null) + .set_range_min(analyzer, arena, Elem::Null) .into_expr_err(loc)?; index_access - .set_range_max(analyzer, Elem::Null) + .set_range_max(analyzer, arena, Elem::Null) .into_expr_err(loc)?; analyzer.update_array_from_index_access( + arena, ctx, loc, tmp_len, diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/block.rs b/crates/solc-expressions/src/func_call/intrinsic_call/block.rs index 8b2d2951..36755007 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/block.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/block.rs @@ -2,9 +2,11 @@ use crate::func_caller::NamedOrUnnamedArgs; use crate::{ContextBuilder, ExprErr, IntoExprErr}; use graph::{ - nodes::{Builtin, ContextNode, ContextVar, ExprRet}, + elem::Elem, + nodes::{Builtin, Concrete, ContextNode, ContextVar, ExprRet}, AnalyzerBackend, Node, }; +use shared::RangeArena; use solang_parser::pt::{Expression, Loc}; @@ -15,6 +17,7 @@ pub trait BlockCaller: AnalyzerBackend + S /// Perform a `block` function call fn block_call( &mut self, + arena: &mut RangeArena>, func_name: String, input_exprs: &NamedOrUnnamedArgs, loc: Loc, @@ -22,8 +25,8 @@ pub trait BlockCaller: AnalyzerBackend + S ) -> Result<(), ExprErr> { match &*func_name { "blockhash" => { - input_exprs.parse_n(1, self, ctx, loc)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + input_exprs.parse_n(arena, 1, self, ctx, loc)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(input) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( loc, diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/constructors.rs b/crates/solc-expressions/src/func_call/intrinsic_call/constructors.rs index bcb5b329..88ce7772 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/constructors.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/constructors.rs @@ -6,10 +6,10 @@ use crate::{ use graph::{ elem::*, - nodes::{ContextNode, ContextVar, ContextVarNode, ExprRet, StructNode}, + nodes::{Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, StructNode}, AnalyzerBackend, ContextEdge, Edge, Node, Range, VarType, }; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; use solang_parser::pt::{Expression, Loc}; @@ -25,14 +25,15 @@ pub trait ConstructorCaller: /// Construct an array fn construct_array( &mut self, + arena: &mut RangeArena>, func_idx: NodeIdx, input_exprs: &NamedOrUnnamedArgs, loc: Loc, ctx: ContextNode, ) -> Result<(), ExprErr> { // create a new list - self.parse_ctx_expr(&input_exprs.unnamed_args().unwrap()[0], ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, &input_exprs.unnamed_args().unwrap()[0], ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(len_var) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs(loc, "Array creation failed".to_string())); }; @@ -90,18 +91,18 @@ pub trait ConstructorCaller: // update the length if let Some(r) = arr.ref_range(analyzer).into_expr_err(loc)? { - let min = r.evaled_range_min(analyzer).into_expr_err(loc)?; - let max = r.evaled_range_max(analyzer).into_expr_err(loc)?; + let min = r.evaled_range_min(analyzer, arena).into_expr_err(loc)?; + let max = r.evaled_range_max(analyzer, arena).into_expr_err(loc)?; if let Some(mut rd) = min.maybe_range_dyn() { rd.len = Box::new(Elem::from(len_cvar)); - arr.set_range_min(analyzer, Elem::ConcreteDyn(rd)) + arr.set_range_min(analyzer, arena, Elem::ConcreteDyn(rd)) .into_expr_err(loc)?; } if let Some(mut rd) = max.maybe_range_dyn() { rd.len = Box::new(Elem::from(len_cvar)); - arr.set_range_min(analyzer, Elem::ConcreteDyn(rd)) + arr.set_range_min(analyzer, arena, Elem::ConcreteDyn(rd)) .into_expr_err(loc)?; } } @@ -115,6 +116,7 @@ pub trait ConstructorCaller: /// Construct a contract fn construct_contract( &mut self, + arena: &mut RangeArena>, func_idx: NodeIdx, input_exprs: &NamedOrUnnamedArgs, loc: Loc, @@ -122,9 +124,9 @@ pub trait ConstructorCaller: ) -> Result<(), ExprErr> { // construct a new contract if !input_exprs.is_empty() { - self.parse_ctx_expr(&input_exprs.unnamed_args().unwrap()[0], ctx)?; + self.parse_ctx_expr(arena, &input_exprs.unnamed_args().unwrap()[0], ctx)?; } - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { if !input_exprs.is_empty() { let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs(loc, "Contract creation failed".to_string())); @@ -163,6 +165,7 @@ pub trait ConstructorCaller: /// Construct a struct fn construct_struct( &mut self, + arena: &mut RangeArena>, func_idx: NodeIdx, input_exprs: &NamedOrUnnamedArgs, loc: Loc, @@ -175,8 +178,8 @@ pub trait ConstructorCaller: ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); - input_exprs.parse(self, ctx, loc)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + input_exprs.parse(arena, self, ctx, loc)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(inputs) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( loc, @@ -210,7 +213,7 @@ pub trait ConstructorCaller: analyzer.add_edge(fc_node, ctx, Edge::Context(ContextEdge::Variable)); ctx.add_var(fc_node.into(), analyzer).into_expr_err(loc)?; let field_as_ret = ExprRet::Single(fc_node); - analyzer.match_assign_sides(ctx, loc, &field_as_ret, &input)?; + analyzer.match_assign_sides(arena, ctx, loc, &field_as_ret, &input)?; let _ = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?; Ok(()) })?; diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/dyn_builtin.rs b/crates/solc-expressions/src/func_call/intrinsic_call/dyn_builtin.rs index ee7c4a09..d7f88fac 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/dyn_builtin.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/dyn_builtin.rs @@ -4,10 +4,11 @@ use crate::{ }; use graph::{ - elem::RangeElem, + elem::{Elem, RangeElem}, nodes::{Builtin, Concrete, ContextNode, ContextVarNode, ExprRet}, AnalyzerBackend, ContextEdge, Edge, Node, SolcRange, VarType, }; +use shared::RangeArena; use solang_parser::pt::{Expression, Loc}; @@ -19,13 +20,14 @@ pub trait DynBuiltinCaller: AnalyzerBackend>, func_name: String, input_exprs: &NamedOrUnnamedArgs, loc: Loc, ctx: ContextNode, ) -> Result<(), ExprErr> { match &*func_name { - "concat" => self.concat(&loc, input_exprs, ctx), + "concat" => self.concat(arena, &loc, input_exprs, ctx), _ => Err(ExprErr::FunctionNotFound( loc, format!( @@ -40,6 +42,7 @@ pub trait DynBuiltinCaller: AnalyzerBackend>, loc: &Loc, input_exprs: &NamedOrUnnamedArgs, ctx: ContextNode, @@ -47,8 +50,8 @@ pub trait DynBuiltinCaller: AnalyzerBackend 1 { - analyzer.match_concat(ctx, loc, start.clone(), &inputs[1..], false) + analyzer.match_concat(arena, ctx, loc, start.clone(), &inputs[1..], false) } else { - analyzer.match_concat(ctx, loc, start.clone(), &[], false) + analyzer.match_concat(arena, ctx, loc, start.clone(), &[], false) } } }) @@ -84,6 +87,7 @@ pub trait DynBuiltinCaller: AnalyzerBackend>, ctx: ContextNode, loc: Loc, curr: ExprRet, @@ -111,7 +115,7 @@ pub trait DynBuiltinCaller: AnalyzerBackend Ok(()), ExprRet::Multi(inner) => inner .into_iter() - .try_for_each(|i| self.match_concat(ctx, loc, i, inputs, true)), + .try_for_each(|i| self.match_concat(arena, ctx, loc, i, inputs, true)), ExprRet::CtxKilled(kind) => ctx.kill(self, loc, kind).into_expr_err(loc), } } else { @@ -139,11 +143,11 @@ pub trait DynBuiltinCaller: AnalyzerBackend, ExprErr>>()?; // create the length variable - let _ = self.tmp_length(acc.latest_version(self), ctx, loc); + let _ = self.tmp_length(arena, acc.latest_version(self), ctx, loc); Ok(()) } @@ -153,7 +157,7 @@ pub trait DynBuiltinCaller: AnalyzerBackend inner .into_iter() - .try_for_each(|i| self.match_concat(ctx, loc, i, inputs, false)), + .try_for_each(|i| self.match_concat(arena, ctx, loc, i, inputs, false)), ExprRet::CtxKilled(kind) => ctx.kill(self, loc, kind).into_expr_err(loc), } } @@ -162,6 +166,7 @@ pub trait DynBuiltinCaller: AnalyzerBackend>, loc: Loc, accum: ContextVarNode, right: ContextVarNode, @@ -229,8 +234,8 @@ pub trait DynBuiltinCaller: AnalyzerBackend Ok(()), diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/intrinsic_caller.rs b/crates/solc-expressions/src/func_call/intrinsic_call/intrinsic_caller.rs index 381a6ac4..c3c21974 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/intrinsic_caller.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/intrinsic_caller.rs @@ -14,10 +14,11 @@ use graph::nodes::ContextVarNode; use graph::nodes::ContractNode; use graph::{ - nodes::{Builtin, ContextNode, ExprRet}, + elem::Elem, + nodes::{Builtin, Concrete, ContextNode, ExprRet}, AnalyzerBackend, Node, }; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; use solang_parser::pt::{Expression, Loc}; @@ -63,13 +64,14 @@ pub trait IntrinsicFuncCaller: { fn new_call( &mut self, + arena: &mut RangeArena>, loc: &Loc, ty_expr: &Expression, inputs: &[Expression], ctx: ContextNode, ) -> Result<(), ExprErr> { - self.parse_ctx_expr(ty_expr, ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, ty_expr, ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(ty) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { @@ -82,7 +84,7 @@ pub trait IntrinsicFuncCaller: match analyzer.node(ty_idx) { Node::Builtin(Builtin::Array(_)) | Node::Builtin(Builtin::DynamicBytes) => { // construct a new list - analyzer.construct_array(ty_idx, &NamedOrUnnamedArgs::Unnamed(inputs), loc, ctx) + analyzer.construct_array(arena,ty_idx, &NamedOrUnnamedArgs::Unnamed(inputs), loc, ctx) } Node::Contract(_c) => { let cnode = ContractNode::from(ty_idx); @@ -92,6 +94,7 @@ pub trait IntrinsicFuncCaller: // call the constructor let inputs = ExprRet::Multi(vec![]); analyzer.func_call( + arena, ctx, loc, &inputs, @@ -99,7 +102,7 @@ pub trait IntrinsicFuncCaller: None, None, )?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let var = match ContextVar::maybe_from_user_ty(analyzer, loc, ty_idx) { Some(v) => v, None => { @@ -118,8 +121,8 @@ pub trait IntrinsicFuncCaller: .into_expr_err(loc) }) } else { - analyzer.parse_inputs(ctx, loc, inputs)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.parse_inputs(arena,ctx, loc, inputs)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(input_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { @@ -130,6 +133,7 @@ pub trait IntrinsicFuncCaller: }; // call the constructor analyzer.func_call( + arena, ctx, loc, &input_paths, @@ -137,7 +141,7 @@ pub trait IntrinsicFuncCaller: None, None, )?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let var = match ContextVar::maybe_from_user_ty(analyzer, loc, ty_idx) { Some(v) => v, None => { @@ -185,6 +189,7 @@ pub trait IntrinsicFuncCaller: #[tracing::instrument(level = "trace", skip_all)] fn intrinsic_func_call( &mut self, + arena: &mut RangeArena>, loc: &Loc, input_exprs: &NamedOrUnnamedArgs, func_idx: NodeIdx, @@ -196,7 +201,7 @@ pub trait IntrinsicFuncCaller: match &*func_name.name { // abi _ if func_name.name.starts_with("abi.") => { - self.abi_call(func_name.name.clone(), input_exprs, *loc, ctx) + self.abi_call(arena, func_name.name.clone(), input_exprs, *loc, ctx) } // address "delegatecall" | "staticcall" | "call" | "code" | "balance" => { @@ -204,20 +209,25 @@ pub trait IntrinsicFuncCaller: } // array "push" | "pop" => { - self.array_call(func_name.name.clone(), input_exprs, *loc, ctx) + self.array_call(arena, func_name.name.clone(), input_exprs, *loc, ctx) } // block "blockhash" => { - self.block_call(func_name.name.clone(), input_exprs, *loc, ctx) + self.block_call(arena, func_name.name.clone(), input_exprs, *loc, ctx) } // dynamic sized builtins - "concat" => { - self.dyn_builtin_call(func_name.name.clone(), input_exprs, *loc, ctx) - } + "concat" => self.dyn_builtin_call( + arena, + func_name.name.clone(), + input_exprs, + *loc, + ctx, + ), // msg "gasleft" => self.msg_call(func_name.name.clone(), input_exprs, *loc, ctx), // precompiles "sha256" | "ripemd160" | "ecrecover" => self.precompile_call( + arena, func_name.name.clone(), func_idx, input_exprs, @@ -225,11 +235,11 @@ pub trait IntrinsicFuncCaller: ctx, ), // solidity - "keccak256" | "addmod" | "mulmod" | "require" | "assert" => { - self.solidity_call(func_name.name.clone(), input_exprs, *loc, ctx) - } + "keccak256" | "addmod" | "mulmod" | "require" | "assert" => self + .solidity_call(arena, func_name.name.clone(), input_exprs, *loc, ctx), // typing "type" | "wrap" | "unwrap" => self.types_call( + arena, func_name.name.clone(), func_idx, input_exprs, @@ -247,19 +257,19 @@ pub trait IntrinsicFuncCaller: } Node::Builtin(Builtin::Array(_)) => { // construct a new array - self.construct_array(func_idx, input_exprs, *loc, ctx) + self.construct_array(arena, func_idx, input_exprs, *loc, ctx) } Node::Contract(_) => { // construct a new contract - self.construct_contract(func_idx, input_exprs, *loc, ctx) + self.construct_contract(arena, func_idx, input_exprs, *loc, ctx) } Node::Struct(_) => { // construct a struct - self.construct_struct(func_idx, input_exprs, *loc, ctx) + self.construct_struct(arena, func_idx, input_exprs, *loc, ctx) } Node::Builtin(ty) => { // cast to type - self.cast(ty.clone(), func_idx, input_exprs, *loc, ctx) + self.cast(arena, ty.clone(), func_idx, input_exprs, *loc, ctx) } Node::ContextVar(_c) => { // its a user type, just push it onto the stack @@ -269,9 +279,9 @@ pub trait IntrinsicFuncCaller: } Node::Unresolved(_) => { // Try to give a nice error - input_exprs.parse(self, ctx, *loc)?; + input_exprs.parse(arena, self, ctx, *loc)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(inputs) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs(loc, "Function call failed".to_string())) }; @@ -291,7 +301,7 @@ pub trait IntrinsicFuncCaller: format!( "Could not find function: \"{}{}\", context: {}, visible functions: {:#?}", ident.name, - inputs.try_as_func_input_str(analyzer), + inputs.try_as_func_input_str(analyzer, arena), ctx.path(analyzer), visible_funcs ) diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/precompile.rs b/crates/solc-expressions/src/func_call/intrinsic_call/precompile.rs index 20dbd601..7dcd2249 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/precompile.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/precompile.rs @@ -5,10 +5,11 @@ use crate::{ use graph::nodes::FunctionNode; use graph::{ - nodes::{Builtin, Context, ContextNode, ContextVar, ContextVarNode, ExprRet}, + elem::Elem, + nodes::{Builtin, Concrete, Context, ContextNode, ContextVar, ContextVarNode, ExprRet}, AnalyzerBackend, ContextEdge, Edge, Node, }; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; use solang_parser::pt::{Expression, Loc}; @@ -24,6 +25,7 @@ pub trait PrecompileCaller: /// Perform a precompile's function call, like `ecrecover` fn precompile_call( &mut self, + arena: &mut RangeArena>, func_name: String, func_idx: NodeIdx, input_exprs: &NamedOrUnnamedArgs, @@ -32,8 +34,8 @@ pub trait PrecompileCaller: ) -> Result<(), ExprErr> { match &*func_name { "sha256" => { - self.parse_ctx_expr(&input_exprs.unnamed_args().unwrap()[0], ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, &input_exprs.unnamed_args().unwrap()[0], ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(input) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( loc, @@ -57,8 +59,8 @@ pub trait PrecompileCaller: }) } "ripemd160" => { - self.parse_ctx_expr(&input_exprs.unnamed_args().unwrap()[0], ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, &input_exprs.unnamed_args().unwrap()[0], ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(input) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( loc, @@ -82,8 +84,8 @@ pub trait PrecompileCaller: }) } "ecrecover" => { - input_exprs.parse(self, ctx, loc)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + input_exprs.parse(arena, self, ctx, loc)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let cctx = Context::new_subctx( ctx, None, diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/solidity.rs b/crates/solc-expressions/src/func_call/intrinsic_call/solidity.rs index e024f557..056e3ca0 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/solidity.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/solidity.rs @@ -5,9 +5,11 @@ use crate::{ }; use graph::{ + elem::Elem, nodes::{Builtin, Concrete, ConcreteNode, ContextNode, ContextVar, ContextVarNode, ExprRet}, AnalyzerBackend, Node, }; +use shared::RangeArena; use ethers_core::types::H256; use solang_parser::pt::{Expression, Loc}; @@ -24,6 +26,7 @@ pub trait SolidityCaller: /// Perform a solidity intrinsic function call, like `keccak256` fn solidity_call( &mut self, + arena: &mut RangeArena>, func_name: String, input_exprs: &NamedOrUnnamedArgs, loc: Loc, @@ -31,8 +34,8 @@ pub trait SolidityCaller: ) -> Result<(), ExprErr> { match &*func_name { "keccak256" => { - self.parse_ctx_expr(&input_exprs.unnamed_args().unwrap()[0], ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, &input_exprs.unnamed_args().unwrap()[0], ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(input) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs(loc, "No input into keccak256".to_string())); }; @@ -43,12 +46,12 @@ pub trait SolidityCaller: return Err(ExprErr::NoRhs(loc, "No input into keccak256".to_string())); }; - if cvar.is_const(analyzer).into_expr_err(loc)? { + if cvar.is_const(analyzer, arena).into_expr_err(loc)? { let bytes = cvar - .evaled_range_min(analyzer) + .evaled_range_min(analyzer, arena) .unwrap() .unwrap() - .as_bytes(analyzer, true) + .as_bytes(analyzer, true, arena) .unwrap(); let mut out = [0; 32]; keccak_hash::keccak_256(&bytes, &mut out); @@ -77,9 +80,9 @@ pub trait SolidityCaller: } "addmod" => { // TODO: actually calcuate this if possible - input_exprs.parse(self, ctx, loc)?; + input_exprs.parse(arena, self, ctx, loc)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?; let var = ContextVar::new_from_builtin( loc, @@ -95,8 +98,8 @@ pub trait SolidityCaller: } "mulmod" => { // TODO: actually calcuate this if possible - input_exprs.parse(self, ctx, loc)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + input_exprs.parse(arena, self, ctx, loc)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?; let var = ContextVar::new_from_builtin( loc, @@ -110,9 +113,11 @@ pub trait SolidityCaller: Ok(()) }) } - "require" | "assert" => self.apply_to_edges(ctx, loc, &|analyzer, ctx, _loc| { - analyzer.handle_require(input_exprs.unnamed_args().unwrap(), ctx) - }), + "require" | "assert" => { + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, _loc| { + analyzer.handle_require(arena, input_exprs.unnamed_args().unwrap(), ctx) + }) + } _ => Err(ExprErr::FunctionNotFound( loc, format!( diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/types.rs b/crates/solc-expressions/src/func_call/intrinsic_call/types.rs index 73950491..fbc84e29 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/types.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/types.rs @@ -5,10 +5,12 @@ use graph::nodes::FunctionNode; use graph::{ elem::*, - nodes::{BuiltInNode, Builtin, ContextNode, ContextVar, ContextVarNode, ExprRet, TyNode}, - AnalyzerBackend, Node, Range, VarType, + nodes::{ + BuiltInNode, Builtin, Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, TyNode, + }, + AnalyzerBackend, Node, VarType, }; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; use solang_parser::pt::{Expression, Loc}; @@ -19,6 +21,7 @@ pub trait TypesCaller: AnalyzerBackend + S /// Perform a type-based intrinsic function call, like `wrap` fn types_call( &mut self, + arena: &mut RangeArena>, func_name: String, func_idx: NodeIdx, input_exprs: &NamedOrUnnamedArgs, @@ -26,14 +29,14 @@ pub trait TypesCaller: AnalyzerBackend + S ctx: ContextNode, ) -> Result<(), ExprErr> { match &*func_name { - "type" => self.parse_ctx_expr(&input_exprs.unnamed_args().unwrap()[0], ctx), + "type" => self.parse_ctx_expr(arena, &input_exprs.unnamed_args().unwrap()[0], ctx), "wrap" => { if input_exprs.len() != 2 { return Err(ExprErr::InvalidFunctionInput(loc, format!("Expected a member type and an input to the wrap function, but got: {:?}", input_exprs))); } - input_exprs.parse(self, ctx, loc)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + input_exprs.parse(arena, self, ctx, loc)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(input) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( loc, @@ -63,16 +66,17 @@ pub trait TypesCaller: AnalyzerBackend + S RangeOp::Cast, Elem::from(cvar), )); - next.set_range_min(analyzer, expr.clone()) + next.set_range_min(analyzer, arena, expr.clone()) + .into_expr_err(loc)?; + next.set_range_max(analyzer, arena, expr) .into_expr_err(loc)?; - next.set_range_max(analyzer, expr).into_expr_err(loc)?; ctx.push_expr(ExprRet::Single(cvar.into()), analyzer) .into_expr_err(loc) }) } "unwrap" => { - input_exprs.parse(self, ctx, loc)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + input_exprs.parse(arena, self, ctx, loc)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(input) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( loc, @@ -114,9 +118,9 @@ pub trait TypesCaller: AnalyzerBackend + S ); let cvar = ContextVarNode::from(analyzer.add_node(Node::ContextVar(var))); - cvar.set_range_min(analyzer, Elem::from(to_be_unwrapped)) + cvar.set_range_min(analyzer, arena, Elem::from(to_be_unwrapped)) .into_expr_err(loc)?; - cvar.set_range_max(analyzer, Elem::from(to_be_unwrapped)) + cvar.set_range_max(analyzer, arena, Elem::from(to_be_unwrapped)) .into_expr_err(loc)?; let next = analyzer.advance_var_in_ctx(cvar, loc, ctx)?; let expr = Elem::Expr(RangeExpr::new( @@ -124,9 +128,10 @@ pub trait TypesCaller: AnalyzerBackend + S RangeOp::Cast, Elem::from(cvar), )); - next.set_range_min(analyzer, expr.clone()) + next.set_range_min(analyzer, arena, expr.clone()) + .into_expr_err(loc)?; + next.set_range_max(analyzer, arena, expr) .into_expr_err(loc)?; - next.set_range_max(analyzer, expr).into_expr_err(loc)?; ctx.push_expr(ExprRet::Single(cvar.into()), analyzer) .into_expr_err(loc) }) @@ -144,6 +149,7 @@ pub trait TypesCaller: AnalyzerBackend + S /// Perform a cast of a type fn cast( &mut self, + arena: &mut RangeArena>, ty: Builtin, func_idx: NodeIdx, input_exprs: &NamedOrUnnamedArgs, @@ -155,6 +161,7 @@ pub trait TypesCaller: AnalyzerBackend + S ctx: ContextNode, loc: Loc, analyzer: &mut impl ListAccess, + arena: &mut RangeArena>, ty: &Builtin, ret: ExprRet, func_idx: NodeIdx, @@ -169,24 +176,26 @@ pub trait TypesCaller: AnalyzerBackend + S .into_expr_err(loc)?; let v_ty = VarType::try_from_idx(analyzer, func_idx).expect(""); - let maybe_new_range = cvar.cast_exprs(&v_ty, analyzer).into_expr_err(loc)?; + let maybe_new_range = + cvar.cast_exprs(&v_ty, analyzer, arena).into_expr_err(loc)?; new_var.underlying_mut(analyzer).into_expr_err(loc)?.ty = v_ty; if let Some((new_min, new_max)) = maybe_new_range { new_var - .set_range_min(analyzer, new_min) + .set_range_min(analyzer, arena, new_min) .into_expr_err(loc)?; new_var - .set_range_max(analyzer, new_max) + .set_range_max(analyzer, arena, new_max) .into_expr_err(loc)?; } if cvar.is_indexable(analyzer).into_expr_err(loc)? { // input is indexable. get the length attribute, create a new length for the casted type let _ = analyzer.create_length( + arena, ctx, loc, - cvar, + new_var, new_var.latest_version(analyzer), false, )?; @@ -198,12 +207,12 @@ pub trait TypesCaller: AnalyzerBackend + S } ExprRet::Multi(inner) => inner .into_iter() - .try_for_each(|i| cast_match(ctx, loc, analyzer, ty, i, func_idx)), + .try_for_each(|i| cast_match(ctx, loc, analyzer, arena, ty, i, func_idx)), } } - self.parse_ctx_expr(&input_exprs.unnamed_args().unwrap()[0], ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, &input_exprs.unnamed_args().unwrap()[0], ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs(loc, "Cast had no target type".to_string())); }; @@ -213,7 +222,7 @@ pub trait TypesCaller: AnalyzerBackend + S return Ok(()); } - cast_match(ctx, loc, analyzer, &ty, ret, func_idx) + cast_match(ctx, loc, analyzer, arena, &ty, ret, func_idx) }) } } diff --git a/crates/solc-expressions/src/func_call/join.rs b/crates/solc-expressions/src/func_call/join.rs index 3724f8b2..0aba321d 100644 --- a/crates/solc-expressions/src/func_call/join.rs +++ b/crates/solc-expressions/src/func_call/join.rs @@ -1,20 +1,17 @@ +use crate::context_builder::StatementParser; use crate::member_access::ListAccess; +use crate::variable::Variable; use crate::{helper::CallerHelper, ExprErr, IntoExprErr}; -use graph::elem::Elem; -use graph::elem::RangeElem; -use graph::nodes::Concrete; -use graph::nodes::ContextVar; -use graph::Range; -use graph::SolcRange; -use graph::VarType; -use shared::AnalyzerLike; -use shared::NodeIdx; -use shared::StorageLocation; use graph::{ - nodes::{ContextNode, ContextVarNode, ExprRet, FunctionNode, FunctionParamNode}, - AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node, + elem::{Elem, RangeElem, RangeExpr, RangeOp}, + nodes::{ + Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, FunctionNode, + FunctionParamNode, KilledKind, + }, + AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node, Range, SolcRange, VarType, }; +use shared::{AnalyzerLike, NodeIdx, RangeArena, StorageLocation}; use solang_parser::pt::{Expression, Loc}; @@ -35,11 +32,13 @@ pub trait FuncJoiner: #[tracing::instrument(level = "trace", skip_all)] fn join( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, func: FunctionNode, params: &[FunctionParamNode], func_inputs: &[ContextVarNode], + seen: &mut Vec, ) -> Result { tracing::trace!( "Trying to join function: {}", @@ -62,327 +61,92 @@ pub trait FuncJoiner: .child .is_some() { + tracing::trace!("Joining function: {}", func.name(self).into_expr_err(loc)?); let edges = body_ctx.successful_edges(self).into_expr_err(loc)?; - if edges.len() == 1 { - tracing::trace!( - "Joining function: {}", - func.name(self).into_expr_err(loc)? - ); - let replacement_map = - self.basic_inputs_replacement_map(body_ctx, loc, params, func_inputs)?; - let mut rets: Vec<_> = edges[0] - .return_nodes(self) - .into_expr_err(loc)? - .iter() - .enumerate() - .map(|(i, (_, ret_node))| { - let mut new_var = ret_node.underlying(self).unwrap().clone(); - let new_name = format!("{}.{i}", func.name(self).unwrap()); - new_var.name.clone_from(&new_name); - new_var.display_name = new_name; - if let Some(mut range) = new_var.ty.take_range() { - let mut range: SolcRange = - range.take_flattened_range(self).unwrap().into(); - replacement_map.iter().for_each(|(replace, replacement)| { - range.replace_dep(*replace, replacement.0.clone(), self); - }); - - range.cache_eval(self).unwrap(); - - new_var.ty.set_range(range).unwrap(); - } - - if let Some(ref mut dep_on) = &mut new_var.dep_on { - dep_on.iter_mut().for_each(|d| { - if let Some((_, r)) = replacement_map.get(&(*d).into()) { - *d = *r - } - }); - } - - let new_cvar = - ContextVarNode::from(self.add_node(Node::ContextVar(new_var))); - - // handle the case where the return node is a struct - if let Ok(fields) = ret_node.struct_to_fields(self) { - if !fields.is_empty() { - fields.iter().for_each(|field| { - let mut new_var = - field.underlying(self).unwrap().clone(); - let new_name = format!( - "{}.{i}.{}", - func.name(self).unwrap(), - field.name(self).unwrap() - ); - new_var.name.clone_from(&new_name); - new_var.display_name = new_name; - if let Some(mut range) = new_var.ty.take_range() { - let mut range: SolcRange = range - .take_flattened_range(self) - .unwrap() - .into(); - replacement_map.iter().for_each( - |(replace, replacement)| { - range.replace_dep( - *replace, - replacement.0.clone(), - self, - ); - }, - ); - - range.cache_eval(self).unwrap(); - - new_var.ty.set_range(range).unwrap(); - } - - if let Some(ref mut dep_on) = &mut new_var.dep_on { - dep_on.iter_mut().for_each(|d| { - if let Some((_, r)) = - replacement_map.get(&(*d).into()) - { - *d = *r - } - }); - } - let new_field = ContextVarNode::from( - self.add_node(Node::ContextVar(new_var)), - ); - self.add_edge( - new_field, - new_cvar, - Edge::Context(ContextEdge::AttrAccess("field")), - ); - }); + match edges.len() { + 0 => {} + 1 => { + self.join_pure( + arena, + loc, + func, + params, + func_inputs, + body_ctx, + edges[0], + ctx, + false, + )?; + return Ok(true); + } + 2.. => { + tracing::trace!( + "Branching pure join function: {}", + func.name(self).into_expr_err(loc)? + ); + // self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { + let new_forks = ctx.set_join_forks(loc, edges.clone(), self).unwrap(); + edges.into_iter().zip(new_forks.iter()).try_for_each( + |(edge, new_fork)| { + let res = self.join_pure( + arena, + loc, + func, + params, + func_inputs, + body_ctx, + edge, + *new_fork, + true, + )?; + if !res { + new_fork + .kill(self, loc, KilledKind::Unreachable) + .into_expr_err(loc)?; + Ok(()) + } else { + Ok(()) } - } - - self.add_edge(new_cvar, ctx, Edge::Context(ContextEdge::Variable)); - ctx.add_var(new_cvar, self).unwrap(); - ExprRet::Single(new_cvar.into()) - }) - .collect(); - body_ctx - .ctx_deps(self) - .into_expr_err(loc)? - .iter() - .try_for_each(|dep| { - let mut new_var = dep.underlying(self)?.clone(); - if let Some(mut range) = new_var.ty.take_range() { - let mut range: SolcRange = - range.take_flattened_range(self).unwrap().into(); - replacement_map.iter().for_each(|(replace, replacement)| { - range.replace_dep(*replace, replacement.0.clone(), self); - }); - - range.cache_eval(self)?; - new_var.ty.set_range(range)?; - } - - if let Some(ref mut dep_on) = &mut new_var.dep_on { - dep_on.iter_mut().for_each(|d| { - if let Some((_, r)) = replacement_map.get(&(*d).into()) { - *d = *r - } - }); - } - let new_cvar = - ContextVarNode::from(self.add_node(Node::ContextVar(new_var))); - self.add_edge(new_cvar, ctx, Edge::Context(ContextEdge::Variable)); - ctx.add_var(new_cvar, self)?; - ctx.add_ctx_dep(new_cvar, self) - }) - .into_expr_err(loc)?; - - func.returns(self).to_vec().into_iter().for_each(|ret| { - if let Some(var) = ContextVar::maybe_new_from_func_ret( - self, - ret.underlying(self).unwrap().clone(), - ) { - let cvar = self.add_node(Node::ContextVar(var)); - ctx.add_var(cvar.into(), self).unwrap(); - self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); - rets.push(ExprRet::Single(cvar)); - } - }); - - ctx.underlying_mut(self).into_expr_err(loc)?.path = format!( - "{}.{}.resume{{ {} }}", - ctx.path(self), - edges[0].path(self), - ctx.associated_fn_name(self).unwrap() - ); - ctx.push_expr(ExprRet::Multi(rets), self) - .into_expr_err(loc)?; - self.add_completed_pure(true, false, false, edges[0]); - } else { - tracing::trace!( - "Branching pure join function: {}", - func.name(self).into_expr_err(loc)? - ); - self.add_completed_pure(false, false, true, body_ctx); + }, + )?; + return Ok(true); + } } } else { tracing::trace!( "Childless pure join: {}", func.name(self).into_expr_err(loc)? ); - let replacement_map = - self.basic_inputs_replacement_map(body_ctx, loc, params, func_inputs)?; - // 1. Create a new variable with name `.` - // 2. Set the range to be the copy of the return's simplified range from the function - // 3. Replace the fundamentals with the input data - let mut rets: Vec<_> = body_ctx - .return_nodes(self) - .into_expr_err(loc)? - .iter() - .enumerate() - .map(|(i, (_, ret_node))| { - let mut new_var = ret_node.underlying(self).unwrap().clone(); - let new_name = format!("{}.{i}", func.name(self).unwrap()); - new_var.name.clone_from(&new_name); - new_var.display_name = new_name; - if let Some(mut range) = new_var.ty.take_range() { - let mut range: SolcRange = - range.take_flattened_range(self).unwrap().into(); - replacement_map.iter().for_each(|(replace, replacement)| { - range.replace_dep(*replace, replacement.0.clone(), self); - }); - - range.cache_eval(self).unwrap(); - - new_var.ty.set_range(range).unwrap(); - } - - if let Some(ref mut dep_on) = &mut new_var.dep_on { - dep_on.iter_mut().for_each(|d| { - if let Some((_, r)) = replacement_map.get(&(*d).into()) { - *d = *r - } - }); - } - - let new_cvar = - ContextVarNode::from(self.add_node(Node::ContextVar(new_var))); - self.add_edge(new_cvar, ctx, Edge::Context(ContextEdge::Variable)); - ctx.add_var(new_cvar, self).unwrap(); - - // handle the case where the return node is a struct - if let Ok(fields) = ret_node.struct_to_fields(self) { - if !fields.is_empty() { - fields.iter().for_each(|field| { - let mut new_var = field.underlying(self).unwrap().clone(); - let new_name = format!( - "{}.{i}.{}", - func.name(self).unwrap(), - field.name(self).unwrap() - ); - new_var.name.clone_from(&new_name); - new_var.display_name = new_name; - if let Some(mut range) = new_var.ty.take_range() { - let mut range: SolcRange = - range.take_flattened_range(self).unwrap().into(); - replacement_map.iter().for_each( - |(replace, replacement)| { - range.replace_dep( - *replace, - replacement.0.clone(), - self, - ); - }, - ); - - range.cache_eval(self).unwrap(); - - new_var.ty.set_range(range).unwrap(); - } - - if let Some(ref mut dep_on) = &mut new_var.dep_on { - dep_on.iter_mut().for_each(|d| { - if let Some((_, r)) = - replacement_map.get(&(*d).into()) - { - *d = *r - } - }); - } - let new_field = ContextVarNode::from( - self.add_node(Node::ContextVar(new_var)), - ); - self.add_edge( - new_field, - new_cvar, - Edge::Context(ContextEdge::AttrAccess("field")), - ); - }); - } - } - - ExprRet::Single(new_cvar.into()) - }) - .collect(); - - // println!("requires:"); - body_ctx - .ctx_deps(self) - .into_expr_err(loc)? - .iter() - .try_for_each(|dep| { - let mut new_var = dep.underlying(self)?.clone(); - if let Some(mut range) = new_var.ty.take_range() { - let mut range: SolcRange = - range.take_flattened_range(self).unwrap().into(); - replacement_map.iter().for_each(|(replace, replacement)| { - range.replace_dep(*replace, replacement.0.clone(), self); - }); - - range.cache_eval(self)?; - new_var.ty.set_range(range)?; - } - - // TODO: the naming isn't correct here and we move variables around - // in a dumb way - - if let Some(ref mut dep_on) = &mut new_var.dep_on { - dep_on.iter_mut().for_each(|d| { - if let Some((_, r)) = replacement_map.get(&(*d).into()) { - *d = *r - } - }); - } - - let new_cvar = - ContextVarNode::from(self.add_node(Node::ContextVar(new_var))); - - self.add_edge(new_cvar, ctx, Edge::Context(ContextEdge::Variable)); - ctx.add_var(new_cvar, self)?; - ctx.add_ctx_dep(new_cvar, self) - }) - .into_expr_err(loc)?; + self.join_pure( + arena, + loc, + func, + params, + func_inputs, + body_ctx, + body_ctx, + ctx, + false, + )?; + return Ok(true); + } + } else { + tracing::trace!("Pure function not processed"); + if ctx.associated_fn(self) == Ok(func) { + return Ok(false); + } - func.returns(self).to_vec().into_iter().for_each(|ret| { - if let Some(var) = ContextVar::maybe_new_from_func_ret( - self, - ret.underlying(self).unwrap().clone(), - ) { - let cvar = self.add_node(Node::ContextVar(var)); - ctx.add_var(cvar.into(), self).unwrap(); - self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); - rets.push(ExprRet::Single(cvar)); - } - }); + if seen.contains(&func) { + return Ok(false); + } - ctx.underlying_mut(self).into_expr_err(loc)?.path = format!( - "{}.{}.resume{{ {} }}", - ctx.path(self), - func.name(self).unwrap(), - ctx.associated_fn_name(self).unwrap() - ); - ctx.push_expr(ExprRet::Multi(rets), self) - .into_expr_err(loc)?; - self.add_completed_pure(true, true, false, body_ctx); - return Ok(true); + self.handled_funcs_mut().push(func); + if let Some(body) = &func.underlying(self).unwrap().body.clone() { + self.parse_ctx_statement(arena, body, false, Some(func)); } + + seen.push(func); + return self.join(arena, ctx, loc, func, params, func_inputs, seen); } } else if func.is_view(self).into_expr_err(loc)? { if let Some(body_ctx) = func.maybe_body_ctx(self) { @@ -413,6 +177,8 @@ pub trait FuncJoiner: ); self.add_completed_view(false, true, false, body_ctx); } + } else { + tracing::trace!("View function not processed"); } } else if let Some(body_ctx) = func.maybe_body_ctx(self) { if body_ctx @@ -439,13 +205,196 @@ pub trait FuncJoiner: ); self.add_completed_mut(false, true, false, body_ctx); } + } else { + tracing::trace!("Mut function not processed"); } Ok(false) } + fn join_pure( + &mut self, + arena: &mut RangeArena>, + loc: Loc, + func: FunctionNode, + params: &[FunctionParamNode], + func_inputs: &[ContextVarNode], + body_ctx: ContextNode, + resulting_edge: ContextNode, + target_ctx: ContextNode, + forks: bool, + ) -> Result { + let replacement_map = + self.basic_inputs_replacement_map(arena, body_ctx, loc, params, func_inputs)?; + let mut rets: Vec<_> = resulting_edge + .return_nodes(self) + .into_expr_err(loc)? + .iter() + .enumerate() + .map(|(i, (_, ret_node))| { + let mut new_var = ret_node.underlying(self).unwrap().clone(); + let new_name = format!("{}.{i}", func.name(self).unwrap()); + new_var.name.clone_from(&new_name); + new_var.display_name = new_name; + if let Some(mut range) = new_var.ty.take_range() { + let mut range: SolcRange = + range.take_flattened_range(self, arena).unwrap().into(); + replacement_map.iter().for_each(|(replace, replacement)| { + range.replace_dep(*replace, replacement.0.clone(), self, arena); + }); + + range.cache_eval(self, arena).unwrap(); + // TODO: change ty here to match ret type + new_var.ty.set_range(range).unwrap(); + } + + if let Some(ref mut dep_on) = &mut new_var.dep_on { + dep_on.iter_mut().for_each(|d| { + if let Some((_, r)) = replacement_map.get(&(*d).into()) { + *d = *r + } + }); + } + + let mut new_cvar = ContextVarNode::from(self.add_node(Node::ContextVar(new_var))); + self.add_edge(new_cvar, target_ctx, Edge::Context(ContextEdge::Variable)); + target_ctx.add_var(new_cvar, self).unwrap(); + + // handle the case where the return node is a struct + if let Ok(fields) = ret_node.struct_to_fields(self) { + if !fields.is_empty() { + fields.iter().for_each(|field| { + let mut new_var = field.underlying(self).unwrap().clone(); + let new_name = format!( + "{}.{i}.{}", + func.name(self).unwrap(), + field.name(self).unwrap() + ); + new_var.name.clone_from(&new_name); + new_var.display_name = new_name; + if let Some(mut range) = new_var.ty.take_range() { + let mut range: SolcRange = + range.take_flattened_range(self, arena).unwrap().into(); + replacement_map.iter().for_each(|(replace, replacement)| { + range.replace_dep(*replace, replacement.0.clone(), self, arena); + }); + + range.cache_eval(self, arena).unwrap(); + + new_var.ty.set_range(range).unwrap(); + } + + if let Some(ref mut dep_on) = &mut new_var.dep_on { + dep_on.iter_mut().for_each(|d| { + if let Some((_, r)) = replacement_map.get(&(*d).into()) { + *d = *r + } + }); + } + let new_field = + ContextVarNode::from(self.add_node(Node::ContextVar(new_var))); + self.add_edge( + new_field, + new_cvar, + Edge::Context(ContextEdge::AttrAccess("field")), + ); + }); + } + } else { + let next_cvar = self + .advance_var_in_ctx_forcible(new_cvar, loc, target_ctx, true) + .unwrap(); + let casted = Elem::Expr(RangeExpr::new( + Elem::from(new_cvar), + RangeOp::Cast, + Elem::from(*ret_node), + )); + next_cvar + .set_range_min(self, arena, casted.clone()) + .unwrap(); + next_cvar.set_range_max(self, arena, casted).unwrap(); + + new_cvar = next_cvar; + } + + ExprRet::Single(new_cvar.latest_version(self).into()) + }) + .collect(); + + let mut unsat = false; + + resulting_edge + .ctx_deps(self) + .into_expr_err(loc)? + .iter() + .try_for_each(|dep| { + let mut new_var = dep.underlying(self)?.clone(); + if let Some(mut range) = new_var.ty.take_range() { + // let mut range: SolcRange = + // range.take_flattened_range(self).unwrap().into(); + let mut range: SolcRange = + range.flattened_range(self, arena)?.into_owned().into(); + replacement_map.iter().for_each(|(replace, replacement)| { + range.replace_dep(*replace, replacement.0.clone(), self, arena); + }); + + range.cache_eval(self, arena)?; + new_var.ty.set_range(range)?; + } + + if let Some(ref mut dep_on) = &mut new_var.dep_on { + dep_on.iter_mut().for_each(|d| { + if let Some((_, r)) = replacement_map.get(&(*d).into()) { + *d = *r + } + }); + } + let new_cvar = ContextVarNode::from(self.add_node(Node::ContextVar(new_var))); + + if new_cvar.is_const(self, arena)? + && new_cvar.evaled_range_min(self, arena)? + == Some(Elem::from(Concrete::from(false))) + { + unsat = true; + } + self.add_edge(new_cvar, target_ctx, Edge::Context(ContextEdge::Variable)); + target_ctx.add_var(new_cvar, self)?; + target_ctx.add_ctx_dep(new_cvar, self, arena) + }) + .into_expr_err(loc)?; + + if unsat { + return Ok(false); + } + + #[allow(clippy::unnecessary_to_owned)] + func.returns(arena, self).into_iter().for_each(|ret| { + if let Some(var) = + ContextVar::maybe_new_from_func_ret(self, ret.underlying(self).unwrap().clone()) + { + let cvar = self.add_node(Node::ContextVar(var)); + target_ctx.add_var(cvar.into(), self).unwrap(); + self.add_edge(cvar, target_ctx, Edge::Context(ContextEdge::Variable)); + rets.push(ExprRet::Single(cvar)); + } + }); + + target_ctx.underlying_mut(self).into_expr_err(loc)?.path = format!( + "{}.{}.resume{{ {} }}", + target_ctx.path(self), + resulting_edge.path(self), + target_ctx.associated_fn_name(self).unwrap() + ); + target_ctx + .push_expr(ExprRet::Multi(rets), self) + .into_expr_err(loc)?; + self.add_completed_pure(true, false, forks, resulting_edge); + Ok(true) + } + fn basic_inputs_replacement_map( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, params: &[FunctionParamNode], @@ -488,14 +437,15 @@ pub trait FuncJoiner: if let Some(param_ty) = VarType::try_from_idx(self, param.ty(self).unwrap()) { if !replacement.ty_eq_ty(¶m_ty, self).into_expr_err(loc)? { replacement - .cast_from_ty(param_ty, self) + .cast_from_ty(param_ty, self, arena) .into_expr_err(loc)?; } } if let Some(_len_var) = replacement.array_to_len_var(self) { // bring the length variable along as well - self.get_length(ctx, loc, *func_input, false).unwrap(); + self.get_length(arena, ctx, loc, *func_input, false) + .unwrap(); } if let (Some(r), Some(r2)) = @@ -505,11 +455,11 @@ pub trait FuncJoiner: let new_max = r.range_max().into_owned().cast(r2.range_max().into_owned()); replacement .latest_version(self) - .try_set_range_min(self, new_min) + .try_set_range_min(self, arena, new_min) .into_expr_err(loc)?; replacement .latest_version(self) - .try_set_range_max(self, new_max) + .try_set_range_max(self, arena, new_max) .into_expr_err(loc)?; replacement .latest_version(self) @@ -547,7 +497,7 @@ pub trait FuncJoiner: { let mut replacement_field_as_elem = Elem::from(*replacement_field); - replacement_field_as_elem.arenaize(self).unwrap(); + replacement_field_as_elem.arenaize(self, arena).unwrap(); if let Some(next) = field.next_version(self) { replacement_map.insert( next.0.into(), @@ -564,7 +514,9 @@ pub trait FuncJoiner: } let mut replacement_as_elem = Elem::from(replacement); - replacement_as_elem.arenaize(self).into_expr_err(loc)?; + replacement_as_elem + .arenaize(self, arena) + .into_expr_err(loc)?; if let Some(next) = correct_input.next_version(self) { replacement_map diff --git a/crates/solc-expressions/src/func_call/modifier.rs b/crates/solc-expressions/src/func_call/modifier.rs index f0bd2b86..3b355863 100644 --- a/crates/solc-expressions/src/func_call/modifier.rs +++ b/crates/solc-expressions/src/func_call/modifier.rs @@ -6,9 +6,11 @@ use crate::{ }; use graph::{ - nodes::{Context, ContextNode, ExprRet, FunctionNode, ModifierState}, + elem::Elem, + nodes::{Concrete, Context, ContextNode, ExprRet, FunctionNode, ModifierState}, AnalyzerBackend, Edge, GraphBackend, Node, }; +use shared::RangeArena; use solang_parser::pt::{CodeLocation, Expression, Loc}; @@ -32,6 +34,7 @@ pub trait ModifierCaller: #[tracing::instrument(level = "trace", skip_all)] fn call_modifier_for_fn( &mut self, + arena: &mut RangeArena>, loc: Loc, func_ctx: ContextNode, func_node: FunctionNode, @@ -50,8 +53,8 @@ pub trait ModifierCaller: input_exprs .iter() - .try_for_each(|expr| self.parse_ctx_expr(expr, func_ctx))?; - self.apply_to_edges(func_ctx, loc, &|analyzer, ctx, loc| { + .try_for_each(|expr| self.parse_ctx_expr(arena, expr, func_ctx))?; + self.apply_to_edges(func_ctx, loc, arena, &|analyzer, arena, ctx, loc| { let input_paths = if input_exprs.is_empty() { ExprRet::Multi(vec![]) } else { @@ -71,6 +74,7 @@ pub trait ModifierCaller: }; analyzer.func_call( + arena, ctx, loc, &input_paths, @@ -85,6 +89,7 @@ pub trait ModifierCaller: #[tracing::instrument(level = "trace", skip_all)] fn resume_from_modifier( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, modifier_state: ModifierState, ) -> Result<(), ExprErr> { @@ -95,88 +100,98 @@ pub trait ModifierCaller: ); let mods = modifier_state.parent_fn.modifiers(self); - self.apply_to_edges(ctx, modifier_state.loc, &|analyzer, ctx, loc| { - if modifier_state.num + 1 < mods.len() { - // use the next modifier - let mut mstate = modifier_state.clone(); - mstate.num += 1; + self.apply_to_edges( + ctx, + modifier_state.loc, + arena, + &|analyzer, arena, ctx, loc| { + if modifier_state.num + 1 < mods.len() { + // use the next modifier + let mut mstate = modifier_state.clone(); + mstate.num += 1; - let loc = mods[mstate.num] - .underlying(analyzer) - .into_expr_err(mstate.loc)? - .loc; - - let pctx = Context::new_subctx( - ctx, - Some(modifier_state.parent_ctx), - loc, - None, - None, - false, - analyzer, - Some(modifier_state.clone()), - ) - .unwrap(); - let new_parent_subctx = ContextNode::from(analyzer.add_node(Node::Context(pctx))); + let loc = mods[mstate.num] + .underlying(analyzer) + .into_expr_err(mstate.loc)? + .loc; - new_parent_subctx - .set_continuation_ctx( + let pctx = Context::new_subctx( + ctx, + Some(modifier_state.parent_ctx), + loc, + None, + None, + false, analyzer, - modifier_state.parent_ctx, - "resume_from_modifier_nonfinal", + Some(modifier_state.clone()), ) - .into_expr_err(loc)?; - ctx.set_child_call(new_parent_subctx, analyzer) - .into_expr_err(modifier_state.loc)?; + .unwrap(); + let new_parent_subctx = + ContextNode::from(analyzer.add_node(Node::Context(pctx))); - analyzer.call_modifier_for_fn( - mods[mstate.num] - .underlying(analyzer) - .into_expr_err(mstate.loc)? - .loc, - new_parent_subctx, - mstate.parent_fn, - mstate, - )?; - Ok(()) - } else { - let pctx = Context::new_subctx( - ctx, - Some(modifier_state.parent_ctx), - modifier_state.loc, - None, - None, - false, - analyzer, - None, - ) - .unwrap(); - let new_parent_subctx = ContextNode::from(analyzer.add_node(Node::Context(pctx))); - new_parent_subctx - .set_continuation_ctx( + new_parent_subctx + .set_continuation_ctx( + analyzer, + modifier_state.parent_ctx, + "resume_from_modifier_nonfinal", + ) + .into_expr_err(loc)?; + ctx.set_child_call(new_parent_subctx, analyzer) + .into_expr_err(modifier_state.loc)?; + + analyzer.call_modifier_for_fn( + arena, + mods[mstate.num] + .underlying(analyzer) + .into_expr_err(mstate.loc)? + .loc, + new_parent_subctx, + mstate.parent_fn, + mstate, + )?; + Ok(()) + } else { + let pctx = Context::new_subctx( + ctx, + Some(modifier_state.parent_ctx), + modifier_state.loc, + None, + None, + false, analyzer, - modifier_state.parent_ctx, - "resume_from_modifier_final", + None, ) - .into_expr_err(loc)?; - ctx.set_child_call(new_parent_subctx, analyzer) - .into_expr_err(modifier_state.loc)?; + .unwrap(); + let new_parent_subctx = + ContextNode::from(analyzer.add_node(Node::Context(pctx))); + new_parent_subctx + .set_continuation_ctx( + analyzer, + modifier_state.parent_ctx, + "resume_from_modifier_final", + ) + .into_expr_err(loc)?; + ctx.set_child_call(new_parent_subctx, analyzer) + .into_expr_err(modifier_state.loc)?; - // actually execute the parent function - analyzer.execute_call_inner( - modifier_state.loc, - ctx, - new_parent_subctx, - modifier_state.parent_fn, - &modifier_state.renamed_inputs, - None, - ) - } - }) + // actually execute the parent function + analyzer.execute_call_inner( + arena, + modifier_state.loc, + ctx, + new_parent_subctx, + modifier_state.parent_fn, + &modifier_state.renamed_inputs, + None, + ) + } + }, + ) } fn modifiers( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, func: FunctionNode, ) -> Result, ExprErr> { @@ -210,15 +225,19 @@ pub trait ModifierCaller: let callee_ctx = ContextNode::from(self.add_node(Node::Context(mctx))); let _res = ctx.set_child_call(callee_ctx, self); - self.parse_ctx_expr(expr, callee_ctx)?; - let f: Vec = - self.take_from_edge(ctx, expr.loc(), &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, expr, callee_ctx)?; + let f: Vec = self.take_from_edge( + ctx, + expr.loc(), + arena, + &|analyzer, arena, ctx, loc| { let ret = ctx .pop_expr_latest(loc, analyzer) .into_expr_err(loc)? .unwrap(); - Ok(ret.try_as_func_input_str(analyzer)) - })?; + Ok(ret.try_as_func_input_str(analyzer, arena)) + }, + )?; ctx.delete_child(self).into_expr_err(expr.loc())?; Ok(f.first().unwrap().clone()) @@ -247,8 +266,13 @@ pub trait ModifierCaller: } /// Sets the modifiers for a function - fn set_modifiers(&mut self, func: FunctionNode, ctx: ContextNode) -> Result<(), ExprErr> { - let modifiers = self.modifiers(ctx, func)?; + fn set_modifiers( + &mut self, + arena: &mut RangeArena>, + func: FunctionNode, + ctx: ContextNode, + ) -> Result<(), ExprErr> { + let modifiers = self.modifiers(arena, ctx, func)?; modifiers .iter() .enumerate() diff --git a/crates/solc-expressions/src/func_call/namespaced_call.rs b/crates/solc-expressions/src/func_call/namespaced_call.rs index fa4c778e..a4c989e7 100644 --- a/crates/solc-expressions/src/func_call/namespaced_call.rs +++ b/crates/solc-expressions/src/func_call/namespaced_call.rs @@ -8,17 +8,18 @@ use crate::{ member_access::MemberAccess, ContextBuilder, ExprErr, ExpressionParser, IntoExprErr, }; -use graph::nodes::ContextVar; +use graph::nodes::{Concrete, ContextVar}; use graph::ContextEdge; use graph::Edge; use graph::VarType; use graph::{ + elem::Elem, nodes::{ContextNode, ContextVarNode, ExprRet, FunctionNode}, AnalyzerBackend, GraphBackend, Node, }; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; use solang_parser::pt::{Expression, Identifier, Loc}; @@ -34,6 +35,7 @@ pub trait NameSpaceFuncCaller: /// Call a namedspaced function, i.e. `MyContract.myFunc(...)` fn call_name_spaced_func( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: &Loc, member_expr: &Expression, @@ -48,7 +50,7 @@ pub trait NameSpaceFuncCaller: let fn_node = self .builtin_fn_or_maybe_add(&func_name) .unwrap_or_else(|| panic!("No builtin function with name {func_name}")); - return self.intrinsic_func_call(loc, &input_exprs, fn_node, ctx); + return self.intrinsic_func_call(arena, loc, &input_exprs, fn_node, ctx); } else if name == "super" { if let Some(contract) = ctx.maybe_associated_contract(self).into_expr_err(*loc)? { let supers = contract.super_contracts(self); @@ -70,8 +72,8 @@ pub trait NameSpaceFuncCaller: "Could not find function in super".to_string(), )); } - input_exprs.parse(self, ctx, *loc)?; - return self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + input_exprs.parse(arena, self, ctx, *loc)?; + return self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let inputs = if let Some(inputs) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? { @@ -97,7 +99,14 @@ pub trait NameSpaceFuncCaller: .kill(analyzer, loc, inputs.killed_kind().unwrap()) .into_expr_err(loc); } - analyzer.setup_fn_call(&ident.loc, &inputs, func.into(), ctx, None) + analyzer.setup_fn_call( + arena, + &ident.loc, + &inputs, + func.into(), + ctx, + None, + ) } else { // this is the annoying case due to function overloading & type inference on number literals let mut lits = vec![false]; @@ -124,12 +133,13 @@ pub trait NameSpaceFuncCaller: .into_expr_err(loc); } if let Some(func) = analyzer.disambiguate_fn_call( + arena, &ident.name, lits, &inputs, &possible_funcs, ) { - analyzer.setup_fn_call(&loc, &inputs, func.into(), ctx, None) + analyzer.setup_fn_call(arena, &loc, &inputs, func.into(), ctx, None) } else { Err(ExprErr::FunctionNotFound( loc, @@ -142,8 +152,8 @@ pub trait NameSpaceFuncCaller: } } - self.parse_ctx_expr(member_expr, ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, member_expr, ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( loc, @@ -156,13 +166,14 @@ pub trait NameSpaceFuncCaller: return Ok(()); } - analyzer.match_namespaced_member(ctx, loc, member_expr, ident, &input_exprs, ret) + analyzer.match_namespaced_member(arena, ctx, loc, member_expr, ident, &input_exprs, ret) }) } /// Match the expression return for getting the member node fn match_namespaced_member( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, member_expr: &Expression, @@ -173,6 +184,7 @@ pub trait NameSpaceFuncCaller: match ret { ExprRet::Single(inner) | ExprRet::SingleLiteral(inner) => self .call_name_spaced_func_inner( + arena, ctx, loc, member_expr, @@ -182,7 +194,7 @@ pub trait NameSpaceFuncCaller: true, ), ExprRet::Multi(inner) => inner.into_iter().try_for_each(|ret| { - self.match_namespaced_member(ctx, loc, member_expr, ident, input_exprs, ret) + self.match_namespaced_member(arena, ctx, loc, member_expr, ident, input_exprs, ret) }), ExprRet::CtxKilled(kind) => ctx.kill(self, loc, kind).into_expr_err(loc), ExprRet::Null => Err(ExprErr::NoLhs( @@ -196,6 +208,7 @@ pub trait NameSpaceFuncCaller: /// Actually perform the namespaced function call fn call_name_spaced_func_inner( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, member_expr: &Expression, @@ -226,8 +239,8 @@ pub trait NameSpaceFuncCaller: ctx.push_expr(ExprRet::Single(member), self) .into_expr_err(loc)?; - input_exprs.parse(self, ctx, loc)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + input_exprs.parse(arena, self, ctx, loc)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(mut inputs) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( loc, @@ -293,11 +306,11 @@ pub trait NameSpaceFuncCaller: return Ok(()); } - analyzer.match_assign_sides(ctx, loc, &field_as_ret, &assignment)?; + analyzer.match_assign_sides(arena, ctx, loc, &field_as_ret, &assignment)?; let _ = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?; Ok(()) })?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, _loc| { + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, _loc| { ctx.push_expr(ExprRet::Single(cvar), analyzer) .into_expr_err(loc)?; Ok(()) @@ -323,7 +336,7 @@ pub trait NameSpaceFuncCaller: } let inputs = ExprRet::Multi(inputs); - let as_input_str = inputs.try_as_func_input_str(analyzer); + let as_input_str = inputs.try_as_func_input_str(analyzer, arena); let lits = inputs.literals_list().into_expr_err(loc)?; if lits.iter().any(|i| *i) { @@ -375,6 +388,7 @@ pub trait NameSpaceFuncCaller: Some(possible_builtins[0]) } else { analyzer.disambiguate_fn_call( + arena, &ident.name, lits, &inputs, @@ -395,8 +409,8 @@ pub trait NameSpaceFuncCaller: .to_string(), }, ); - analyzer.parse_ctx_expr(expr, ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.parse_ctx_expr(arena, expr, ctx)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { @@ -412,6 +426,7 @@ pub trait NameSpaceFuncCaller: let mut modifier_input_exprs = vec![member_expr.clone()]; modifier_input_exprs.extend(input_exprs.exprs()); analyzer.match_intrinsic_fallback( + arena, ctx, &loc, &NamedOrUnnamedArgs::Unnamed(&modifier_input_exprs), @@ -440,8 +455,8 @@ pub trait NameSpaceFuncCaller: name: format!("{}{}", ident.name, as_input_str), }, ); - analyzer.parse_ctx_expr(expr, ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.parse_ctx_expr(arena, expr, ctx)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( @@ -456,6 +471,7 @@ pub trait NameSpaceFuncCaller: let mut modifier_input_exprs = vec![member_expr.clone()]; modifier_input_exprs.extend(input_exprs.exprs()); analyzer.match_intrinsic_fallback( + arena, ctx, &loc, &NamedOrUnnamedArgs::Unnamed(&modifier_input_exprs), @@ -485,7 +501,7 @@ pub trait NameSpaceFuncCaller: .into_expr_err(loc); } - analyzer.setup_fn_call(&ident.loc, &inputs, func.into(), ctx, None) + analyzer.setup_fn_call(arena, &ident.loc, &inputs, func.into(), ctx, None) } else { // Add the member back in if its a context variable let mut inputs = inputs.as_vec(); @@ -518,9 +534,9 @@ pub trait NameSpaceFuncCaller: .into_expr_err(loc); } if let Some(func) = - analyzer.disambiguate_fn_call(&ident.name, lits, &inputs, &possible_funcs) + analyzer.disambiguate_fn_call(arena, &ident.name, lits, &inputs, &possible_funcs) { - analyzer.setup_fn_call(&loc, &inputs, func.into(), ctx, None) + analyzer.setup_fn_call(arena, &loc, &inputs, func.into(), ctx, None) } else { Err(ExprErr::FunctionNotFound( loc, diff --git a/crates/solc-expressions/src/list.rs b/crates/solc-expressions/src/list.rs index 0544bd07..ef19a590 100644 --- a/crates/solc-expressions/src/list.rs +++ b/crates/solc-expressions/src/list.rs @@ -1,9 +1,11 @@ use crate::{ContextBuilder, ExprErr, ExpressionParser, IntoExprErr}; use graph::{ - nodes::{ContextNode, ContextVar, ExprRet}, + elem::Elem, + nodes::{Concrete, ContextNode, ContextVar, ExprRet}, AnalyzerBackend, ContextEdge, Edge, Node, VarType, }; +use shared::RangeArena; use solang_parser::pt::{Expression, Loc, Parameter, ParameterList}; @@ -11,11 +13,17 @@ impl List for T where T: AnalyzerBackend + Sized { #[tracing::instrument(level = "trace", skip_all)] - fn list(&mut self, ctx: ContextNode, loc: Loc, params: &ParameterList) -> Result<(), ExprErr> { + fn list( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + loc: Loc, + params: &ParameterList, + ) -> Result<(), ExprErr> { params.iter().try_for_each(|(loc, input)| { if let Some(input) = input { - self.parse_ctx_expr(&input.ty, ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, &input.ty, ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( loc, @@ -31,13 +39,13 @@ pub trait List: AnalyzerBackend + Sized { }) } else { // create a dummy var - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { ctx.append_tmp_expr(ExprRet::Null, analyzer) .into_expr_err(loc) }) } })?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(ret) = ctx.pop_tmp_expr(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( loc, diff --git a/crates/solc-expressions/src/literal.rs b/crates/solc-expressions/src/literal.rs index a37e14c3..fb118816 100644 --- a/crates/solc-expressions/src/literal.rs +++ b/crates/solc-expressions/src/literal.rs @@ -5,6 +5,7 @@ use graph::{ nodes::{Builtin, Concrete, ConcreteNode, ContextNode, ContextVar, ContextVarNode, ExprRet}, AnalyzerBackend, ContextEdge, Edge, Node, }; +use shared::RangeArena; use ethers_core::types::{Address, H256, I256, U256}; use solang_parser::pt::{HexLiteral, Identifier, Loc}; @@ -38,7 +39,7 @@ pub trait Literal: AnalyzerBackend + Sized { val }; - let size: u16 = ((32 - (val.leading_zeros() / 8)) * 8) as u16; + let size: u16 = ((32 - (val.leading_zeros() / 8)) * 8).max(8) as u16; let concrete_node = if negative { let val = if val == U256::from(2).pow(255.into()) { // no need to set upper bit @@ -76,6 +77,7 @@ pub trait Literal: AnalyzerBackend + Sized { fn rational_number_literal( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, integer: &str, @@ -110,9 +112,9 @@ pub trait Literal: AnalyzerBackend + Sized { ContextVar::new_from_builtin(loc, self.builtin_or_add(Builtin::Uint(256)).into(), self) .into_expr_err(loc)?; let node = ContextVarNode::from(self.add_node(Node::ContextVar(cvar))); - node.set_range_max(self, rational_range.clone()) + node.set_range_max(self, arena, rational_range.clone()) .into_expr_err(loc)?; - node.set_range_min(self, rational_range) + node.set_range_min(self, arena, rational_range) .into_expr_err(loc)?; ctx.add_var(node, self).into_expr_err(loc)?; diff --git a/crates/solc-expressions/src/loops.rs b/crates/solc-expressions/src/loops.rs index e54f50dc..aa6f4488 100644 --- a/crates/solc-expressions/src/loops.rs +++ b/crates/solc-expressions/src/loops.rs @@ -3,9 +3,11 @@ use graph::ContextEdge; use graph::Edge; use graph::{ - nodes::{Context, ContextNode}, + elem::Elem, + nodes::{Concrete, Context, ContextNode}, AnalyzerBackend, GraphBackend, Node, }; +use shared::RangeArena; use solang_parser::pt::{Expression, Loc, Statement}; @@ -22,6 +24,7 @@ pub trait Looper: /// Handles a for loop. Needs improvement fn for_loop( &mut self, + arena: &mut RangeArena>, loc: Loc, ctx: ContextNode, maybe_init: &Option>, @@ -31,12 +34,12 @@ pub trait Looper: ) -> Result<(), ExprErr> { // TODO: improve this if let Some(initer) = maybe_init { - self.parse_ctx_statement(initer, false, Some(ctx)); + self.parse_ctx_statement(arena, initer, false, Some(ctx)); } if let Some(body) = maybe_body { - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { - analyzer.reset_vars(loc, ctx, body) + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { + analyzer.reset_vars(arena, loc, ctx, body) }) } else { Ok(()) @@ -44,14 +47,20 @@ pub trait Looper: } /// Resets all variables referenced in the loop because we don't elegantly handle loops - fn reset_vars(&mut self, loc: Loc, ctx: ContextNode, body: &Statement) -> Result<(), ExprErr> { + fn reset_vars( + &mut self, + arena: &mut RangeArena>, + loc: Loc, + ctx: ContextNode, + body: &Statement, + ) -> Result<(), ExprErr> { let og_ctx = ctx; let sctx = Context::new_loop_subctx(ctx, loc, self).into_expr_err(loc)?; let subctx = ContextNode::from(self.add_node(Node::Context(sctx))); ctx.set_child_call(subctx, self).into_expr_err(loc)?; self.add_edge(subctx, ctx, Edge::Context(ContextEdge::Loop)); - self.parse_ctx_statement(body, false, Some(subctx)); - self.apply_to_edges(subctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_statement(arena, body, false, Some(subctx)); + self.apply_to_edges(subctx, loc, arena, &|analyzer, arena, ctx, loc| { let vars = subctx.local_vars(analyzer).clone(); vars.iter().for_each(|(name, var)| { // widen to max range @@ -68,11 +77,11 @@ pub trait Looper: .advance_var_in_ctx(inheritor_var, loc, ctx) .unwrap(); let res = new_inheritor_var - .set_range_min(analyzer, r.min) + .set_range_min(analyzer, arena, r.min) .into_expr_err(loc); let _ = analyzer.add_if_err(res); let res = new_inheritor_var - .set_range_max(analyzer, r.max) + .set_range_max(analyzer, arena, r.max) .into_expr_err(loc); let _ = analyzer.add_if_err(res); } @@ -90,14 +99,15 @@ pub trait Looper: /// Handles a while-loop fn while_loop( &mut self, + arena: &mut RangeArena>, loc: Loc, ctx: ContextNode, _limiter: &Expression, body: &Statement, ) -> Result<(), ExprErr> { // TODO: improve this - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { - analyzer.reset_vars(loc, ctx, body) + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { + analyzer.reset_vars(arena, loc, ctx, body) }) } } diff --git a/crates/solc-expressions/src/member_access/list_access.rs b/crates/solc-expressions/src/member_access/list_access.rs index adbbbb8a..ab1be6ad 100644 --- a/crates/solc-expressions/src/member_access/list_access.rs +++ b/crates/solc-expressions/src/member_access/list_access.rs @@ -5,6 +5,7 @@ use graph::{ nodes::{BuiltInNode, Builtin, Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet}, AnalyzerBackend, ContextEdge, Edge, Node, Range, SolcRange, VarType, }; +use shared::RangeArena; use ethers_core::types::U256; use solang_parser::pt::{Expression, Loc}; @@ -16,12 +17,13 @@ pub trait ListAccess: AnalyzerBackend + Si /// Get the length member of an array/list fn length( &mut self, + arena: &mut RangeArena>, loc: Loc, input_expr: &Expression, ctx: ContextNode, ) -> Result<(), ExprErr> { - self.parse_ctx_expr(input_expr, ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, input_expr, ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( loc, @@ -32,7 +34,7 @@ pub trait ListAccess: AnalyzerBackend + Si ctx.push_expr(ret, analyzer).into_expr_err(loc)?; return Ok(()); } - analyzer.match_length(ctx, loc, ret, true) + analyzer.match_length(arena, ctx, loc, ret, true) }) } @@ -40,6 +42,7 @@ pub trait ListAccess: AnalyzerBackend + Si /// Get the length member of an array/list fn match_length( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, elem_path: ExprRet, @@ -52,7 +55,7 @@ pub trait ListAccess: AnalyzerBackend + Si } ExprRet::CtxKilled(kind) => ctx.kill(self, loc, kind).into_expr_err(loc), ExprRet::Single(arr) => { - self.get_length(ctx, loc, arr.into(), false)?; + self.get_length(arena, ctx, loc, arr.into(), false)?; Ok(()) } e => todo!("here: {e:?}"), @@ -61,6 +64,7 @@ pub trait ListAccess: AnalyzerBackend + Si fn get_length( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, array: ContextVarNode, @@ -78,7 +82,7 @@ pub trait ListAccess: AnalyzerBackend + Si Ok(Some(len_node)) } } else { - self.create_length(ctx, loc, array, next_arr, return_var) + self.create_length(arena, ctx, loc, array, next_arr, return_var) // no length variable, create one // let name = format!("{}.length", array.name(self).into_expr_err(loc)?); @@ -141,6 +145,7 @@ pub trait ListAccess: AnalyzerBackend + Si fn create_length( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, array: ContextVarNode, @@ -151,7 +156,6 @@ pub trait ListAccess: AnalyzerBackend + Si let name = format!("{}.length", array.name(self).into_expr_err(loc)?); // Create the range from the current length or default to [0, uint256.max] - let len_min = Elem::from(array) .get_length() .max(Elem::from(Concrete::from(U256::zero()))); @@ -192,10 +196,10 @@ pub trait ListAccess: AnalyzerBackend + Si // Update the array next_target_arr - .set_range_min(self, update_array_len.clone()) + .set_range_min(self, arena, update_array_len.clone()) .into_expr_err(loc)?; next_target_arr - .set_range_max(self, update_array_len.clone()) + .set_range_max(self, arena, update_array_len.clone()) .into_expr_err(loc)?; if !return_var { @@ -211,6 +215,7 @@ pub trait ListAccess: AnalyzerBackend + Si /// Get the length member of an array/list and create it as a temporary variable fn tmp_length( &mut self, + arena: &mut RangeArena>, arr: ContextVarNode, array_ctx: ContextNode, loc: Loc, @@ -255,26 +260,26 @@ pub trait ListAccess: AnalyzerBackend + Si .unwrap() { if let Some(r) = next_arr.ref_range(self).unwrap() { - let min = r.simplified_range_min(self).unwrap(); - let max = r.simplified_range_max(self).unwrap(); + let min = r.simplified_range_min(self, arena).unwrap(); + let max = r.simplified_range_max(self, arena).unwrap(); if let Some(mut rd) = min.maybe_range_dyn() { ContextVarNode::from(len_node) - .set_range_min(self, *rd.len.clone()) + .set_range_min(self, arena, *rd.len.clone()) .unwrap(); rd.len = Box::new(Elem::from(len_node)); let res = next_arr - .set_range_min(self, Elem::ConcreteDyn(rd)) + .set_range_min(self, arena, Elem::ConcreteDyn(rd)) .into_expr_err(loc); let _ = self.add_if_err(res); } if let Some(mut rd) = max.maybe_range_dyn() { ContextVarNode::from(len_node) - .set_range_max(self, *rd.len.clone()) + .set_range_max(self, arena, *rd.len.clone()) .unwrap(); rd.len = Box::new(Elem::from(len_node)); let res = next_arr - .set_range_max(self, Elem::ConcreteDyn(rd)) + .set_range_max(self, arena, Elem::ConcreteDyn(rd)) .into_expr_err(loc); let _ = self.add_if_err(res); } diff --git a/crates/solc-expressions/src/member_access/member_trait.rs b/crates/solc-expressions/src/member_access/member_trait.rs index dd192dde..ab26fe1b 100644 --- a/crates/solc-expressions/src/member_access/member_trait.rs +++ b/crates/solc-expressions/src/member_access/member_trait.rs @@ -2,17 +2,16 @@ use crate::{ BuiltinAccess, ContextBuilder, ContractAccess, EnumAccess, Env, ExprErr, ExpressionParser, IntoExprErr, ListAccess, StructAccess, }; -use graph::nodes::Concrete; -use graph::nodes::ConcreteNode; use graph::{ + elem::Elem, nodes::{ - BuiltInNode, ContextNode, ContextVar, ContextVarNode, ContractNode, EnumNode, ExprRet, - FunctionNode, StructNode, TyNode, + BuiltInNode, Concrete, ConcreteNode, ContextNode, ContextVar, ContextVarNode, ContractNode, + EnumNode, ExprRet, FunctionNode, StructNode, TyNode, }, AnalyzerBackend, Node, TypeNode, VarType, }; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; use solang_parser::pt::{Expression, Identifier, Loc}; @@ -40,6 +39,7 @@ pub trait MemberAccess: #[tracing::instrument(level = "trace", skip_all)] fn member_access( &mut self, + arena: &mut RangeArena>, loc: Loc, member_expr: &Expression, ident: &Identifier, @@ -47,11 +47,11 @@ pub trait MemberAccess: ) -> Result<(), ExprErr> { // TODO: this is wrong as it overwrites a function call of the form elem.length(...) i believe if ident.name == "length" { - return self.length(loc, member_expr, ctx); + return self.length(arena, loc, member_expr, ctx); } - self.parse_ctx_expr(member_expr, ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, member_expr, ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( loc, diff --git a/crates/solc-expressions/src/pre_post_in_decrement.rs b/crates/solc-expressions/src/pre_post_in_decrement.rs index 829b2780..275c979a 100644 --- a/crates/solc-expressions/src/pre_post_in_decrement.rs +++ b/crates/solc-expressions/src/pre_post_in_decrement.rs @@ -7,6 +7,7 @@ use graph::{ nodes::{Concrete, ContextNode, ContextVarNode, ExprRet}, AnalyzerBackend, }; +use shared::RangeArena; use ethers_core::types::U256; use solang_parser::pt::{Expression, Loc}; @@ -22,12 +23,13 @@ pub trait PrePostIncDecrement: /// Handle a preincrement fn pre_increment( &mut self, + arena: &mut RangeArena>, expr: &Expression, loc: Loc, ctx: ContextNode, ) -> Result<(), ExprErr> { - self.parse_ctx_expr(expr, ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, expr, ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { tracing::trace!("PreIncrement variable pop"); let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -40,19 +42,20 @@ pub trait PrePostIncDecrement: ctx.push_expr(ret, analyzer).into_expr_err(loc)?; return Ok(()); } - analyzer.match_in_de_crement(ctx, true, true, loc, &ret) + analyzer.match_in_de_crement(arena, ctx, true, true, loc, &ret) }) } /// Handle a postincrement fn post_increment( &mut self, + arena: &mut RangeArena>, expr: &Expression, loc: Loc, ctx: ContextNode, ) -> Result<(), ExprErr> { - self.parse_ctx_expr(expr, ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, expr, ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { tracing::trace!("PostIncrement variable pop"); let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -64,19 +67,20 @@ pub trait PrePostIncDecrement: ctx.push_expr(ret, analyzer).into_expr_err(loc)?; return Ok(()); } - analyzer.match_in_de_crement(ctx, false, true, loc, &ret) + analyzer.match_in_de_crement(arena, ctx, false, true, loc, &ret) }) } /// Handle a predecrement fn pre_decrement( &mut self, + arena: &mut RangeArena>, expr: &Expression, loc: Loc, ctx: ContextNode, ) -> Result<(), ExprErr> { - self.parse_ctx_expr(expr, ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, expr, ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { tracing::trace!("PreDecrement variable pop"); let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -88,19 +92,20 @@ pub trait PrePostIncDecrement: ctx.push_expr(ret, analyzer).into_expr_err(loc)?; return Ok(()); } - analyzer.match_in_de_crement(ctx, true, false, loc, &ret) + analyzer.match_in_de_crement(arena, ctx, true, false, loc, &ret) }) } /// Handle a postdecrement fn post_decrement( &mut self, + arena: &mut RangeArena>, expr: &Expression, loc: Loc, ctx: ContextNode, ) -> Result<(), ExprErr> { - self.parse_ctx_expr(expr, ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, expr, ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { tracing::trace!("PostDecrement variable pop"); let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -112,13 +117,14 @@ pub trait PrePostIncDecrement: ctx.push_expr(ret, analyzer).into_expr_err(loc)?; return Ok(()); } - analyzer.match_in_de_crement(ctx, false, false, loc, &ret) + analyzer.match_in_de_crement(arena, ctx, false, false, loc, &ret) }) } /// Match on the [`ExprRet`]s of a pre-or-post in/decrement and performs it fn match_in_de_crement( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, pre: bool, increment: bool, @@ -132,9 +138,9 @@ pub trait PrePostIncDecrement: } ExprRet::SingleLiteral(var) => { ContextVarNode::from(*var) - .try_increase_size(self) + .try_increase_size(self, arena) .into_expr_err(loc)?; - self.match_in_de_crement(ctx, pre, increment, loc, &ExprRet::Single(*var)) + self.match_in_de_crement(arena, ctx, pre, increment, loc, &ExprRet::Single(*var)) } ExprRet::Single(var) => { let cvar = ContextVarNode::from(*var).latest_version(self); @@ -145,31 +151,33 @@ pub trait PrePostIncDecrement: if increment { if pre { let dup = cvar.as_tmp(loc, ctx, self).into_expr_err(loc)?; - dup.set_range_min(self, elem.clone() + one.clone()) + dup.set_range_min(self, arena, elem.clone() + one.clone()) .into_expr_err(loc)?; - dup.set_range_max(self, elem.clone() + one.clone()) + dup.set_range_max(self, arena, elem.clone() + one.clone()) .into_expr_err(loc)?; let new_cvar = self.advance_var_in_ctx(cvar, loc, ctx)?; new_cvar - .set_range_min(self, elem.clone() + one.clone()) + .set_range_min(self, arena, elem.clone() + one.clone()) .into_expr_err(loc)?; new_cvar - .set_range_max(self, elem + one) + .set_range_max(self, arena, elem + one) .into_expr_err(loc)?; ctx.push_expr(ExprRet::Single(dup.latest_version(self).into()), self) .into_expr_err(loc)?; Ok(()) } else { let dup = cvar.as_tmp(loc, ctx, self).into_expr_err(loc)?; - dup.set_range_min(self, elem.clone()).into_expr_err(loc)?; - dup.set_range_max(self, elem.clone()).into_expr_err(loc)?; + dup.set_range_min(self, arena, elem.clone()) + .into_expr_err(loc)?; + dup.set_range_max(self, arena, elem.clone()) + .into_expr_err(loc)?; let new_cvar = self.advance_var_in_ctx(cvar, loc, ctx)?; let res = new_cvar - .set_range_min(self, elem.clone() + one.clone()) + .set_range_min(self, arena, elem.clone() + one.clone()) .into_expr_err(loc); let _ = self.add_if_err(res); new_cvar - .set_range_max(self, elem + one) + .set_range_max(self, arena, elem + one) .into_expr_err(loc)?; ctx.push_expr(ExprRet::Single(dup.latest_version(self).into()), self) .into_expr_err(loc)?; @@ -177,39 +185,41 @@ pub trait PrePostIncDecrement: } } else if pre { let dup = cvar.as_tmp(loc, ctx, self).into_expr_err(loc)?; - dup.set_range_min(self, elem.clone() - one.clone()) + dup.set_range_min(self, arena, elem.clone() - one.clone()) .into_expr_err(loc)?; - dup.set_range_max(self, elem.clone() - one.clone()) + dup.set_range_max(self, arena, elem.clone() - one.clone()) .into_expr_err(loc)?; let new_cvar = self.advance_var_in_ctx(cvar, loc, ctx)?; new_cvar - .set_range_min(self, elem.clone() - one.clone()) + .set_range_min(self, arena, elem.clone() - one.clone()) .into_expr_err(loc)?; new_cvar - .set_range_max(self, elem - one) + .set_range_max(self, arena, elem - one) .into_expr_err(loc)?; ctx.push_expr(ExprRet::Single(dup.latest_version(self).into()), self) .into_expr_err(loc)?; Ok(()) } else { let dup = cvar.as_tmp(loc, ctx, self).into_expr_err(loc)?; - dup.set_range_min(self, elem.clone()).into_expr_err(loc)?; - dup.set_range_max(self, elem.clone()).into_expr_err(loc)?; + dup.set_range_min(self, arena, elem.clone()) + .into_expr_err(loc)?; + dup.set_range_max(self, arena, elem.clone()) + .into_expr_err(loc)?; let new_cvar = self.advance_var_in_ctx(cvar, loc, ctx)?; new_cvar - .set_range_min(self, elem.clone() - one.clone()) + .set_range_min(self, arena, elem.clone() - one.clone()) .into_expr_err(loc)?; new_cvar - .set_range_max(self, elem - one) + .set_range_max(self, arena, elem - one) .into_expr_err(loc)?; ctx.push_expr(ExprRet::Single(dup.into()), self) .into_expr_err(loc)?; Ok(()) } } - ExprRet::Multi(inner) => inner - .iter() - .try_for_each(|expr| self.match_in_de_crement(ctx, pre, increment, loc, expr)), + ExprRet::Multi(inner) => inner.iter().try_for_each(|expr| { + self.match_in_de_crement(arena, ctx, pre, increment, loc, expr) + }), ExprRet::Null => Ok(()), } } diff --git a/crates/solc-expressions/src/require.rs b/crates/solc-expressions/src/require.rs index 1c0478c5..28d4f608 100644 --- a/crates/solc-expressions/src/require.rs +++ b/crates/solc-expressions/src/require.rs @@ -9,6 +9,7 @@ use graph::{ range_string::ToRangeString, AnalyzerBackend, ContextEdge, Edge, Node, Range, RangeEval, SolcRange, VarType, }; +use shared::RangeArena; use ethers_core::types::I256; use solang_parser::{ @@ -46,13 +47,18 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { /// Handles a require expression #[tracing::instrument(level = "trace", skip_all)] - fn handle_require(&mut self, inputs: &[Expression], ctx: ContextNode) -> Result<(), ExprErr> { + fn handle_require( + &mut self, + arena: &mut RangeArena>, + inputs: &[Expression], + ctx: ContextNode, + ) -> Result<(), ExprErr> { ctx.add_gas_cost(self, shared::gas::BIN_OP_GAS) .into_expr_err(inputs[0].loc())?; match inputs.first().expect("No lhs input for require statement") { Expression::Equal(loc, lhs, rhs) => { - self.parse_ctx_expr(rhs, ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, rhs, ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -68,8 +74,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { return Ok(()); } - analyzer.parse_ctx_expr(lhs, ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.parse_ctx_expr(arena, lhs, ctx)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { @@ -84,6 +90,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { return Ok(()); } analyzer.handle_require_inner( + arena, ctx, loc, &lhs_paths.flatten(), @@ -96,8 +103,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { }) } Expression::NotEqual(loc, lhs, rhs) => { - self.parse_ctx_expr(rhs, ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, rhs, ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -111,8 +118,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { ctx.push_expr(rhs_paths, analyzer).into_expr_err(loc)?; return Ok(()); } - analyzer.parse_ctx_expr(lhs, ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.parse_ctx_expr(arena, lhs, ctx)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { @@ -126,6 +133,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { return Ok(()); } analyzer.handle_require_inner( + arena, ctx, loc, &lhs_paths.flatten(), @@ -138,8 +146,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { }) } Expression::Less(loc, lhs, rhs) => { - self.parse_ctx_expr(rhs, ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, rhs, ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -154,8 +162,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { return Ok(()); } - analyzer.parse_ctx_expr(lhs, ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.parse_ctx_expr(arena, lhs, ctx)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { @@ -169,6 +177,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { return Ok(()); } analyzer.handle_require_inner( + arena, ctx, loc, &lhs_paths.flatten(), @@ -181,8 +190,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { }) } Expression::More(loc, lhs, rhs) => { - self.parse_ctx_expr(rhs, ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, rhs, ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -197,8 +206,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { return Ok(()); } - analyzer.parse_ctx_expr(lhs, ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.parse_ctx_expr(arena, lhs, ctx)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { @@ -212,6 +221,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { return Ok(()); } analyzer.handle_require_inner( + arena, ctx, loc, &lhs_paths.flatten(), @@ -224,8 +234,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { }) } Expression::MoreEqual(loc, lhs, rhs) => { - self.parse_ctx_expr(rhs, ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, rhs, ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -240,8 +250,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { return Ok(()); } - analyzer.parse_ctx_expr(lhs, ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.parse_ctx_expr(arena, lhs, ctx)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { @@ -255,6 +265,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { return Ok(()); } analyzer.handle_require_inner( + arena, ctx, loc, &lhs_paths.flatten(), @@ -267,8 +278,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { }) } Expression::LessEqual(loc, lhs, rhs) => { - self.parse_ctx_expr(rhs, ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, rhs, ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -283,8 +294,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { return Ok(()); } - analyzer.parse_ctx_expr(lhs, ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.parse_ctx_expr(arena, lhs, ctx)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { @@ -298,6 +309,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { return Ok(()); } analyzer.handle_require_inner( + arena, ctx, loc, &lhs_paths.flatten(), @@ -310,8 +322,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { }) } Expression::Not(loc, lhs) => { - self.parse_ctx_expr(lhs, ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, lhs, ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( @@ -333,6 +345,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { let rhs_paths = ExprRet::Single(ContextVarNode::from(analyzer.add_node(tmp_false)).into()); analyzer.handle_require_inner( + arena, ctx, loc, &lhs_paths, @@ -344,8 +357,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { }) } Expression::And(loc, lhs, rhs) => { - self.parse_ctx_expr(lhs, ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, lhs, ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( @@ -359,8 +372,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { return Ok(()); } - analyzer.parse_ctx_expr(rhs, ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.parse_ctx_expr(arena, rhs, ctx)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { @@ -398,6 +411,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { if let Some(tmp) = underlying.tmp_of { if let Some((op, _inv_op, pair)) = tmp.op.require_parts() { analyzer.handle_require_inner( + arena, ctx, loc, &ExprRet::Single(tmp.lhs.into()), @@ -416,6 +430,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { if let Some(tmp) = underlying.tmp_of { if let Some((op, _inv_op, pair)) = tmp.op.require_parts() { analyzer.handle_require_inner( + arena, ctx, loc, &ExprRet::Single(tmp.lhs.into()), @@ -428,6 +443,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { } analyzer.handle_require_inner( + arena, ctx, loc, &lhs_paths, @@ -438,6 +454,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { )?; analyzer.handle_require_inner( + arena, ctx, loc, &rhs_paths, @@ -452,8 +469,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { }) } Expression::Or(loc, lhs, rhs) => { - self.parse_ctx_expr(lhs, ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, lhs, ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( @@ -466,8 +483,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { return Ok(()); } - analyzer.parse_ctx_expr(rhs, ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.parse_ctx_expr(arena, rhs, ctx)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { @@ -547,6 +564,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { analyzer.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); let rhs_paths = ExprRet::Single(node); analyzer.handle_require_inner( + arena, ctx, loc, &ExprRet::Single(or_var.into()), @@ -559,8 +577,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { }) } other => { - self.parse_ctx_expr(other, ctx)?; - self.apply_to_edges(ctx, other.loc(), &|analyzer, ctx, loc| { + self.parse_ctx_expr(arena, other, ctx)?; + self.apply_to_edges(ctx, other.loc(), arena, &|analyzer, arena, ctx, loc| { let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( @@ -581,6 +599,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { let rhs_paths = ExprRet::Single(ContextVarNode::from(analyzer.add_node(tmp_true)).into()); analyzer.handle_require_inner( + arena, ctx, loc, &lhs_paths, @@ -597,6 +616,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { /// Do matching on [`ExprRet`]s to actually perform the require statement evaluation fn handle_require_inner( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, lhs_paths: &ExprRet, @@ -610,9 +630,10 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { (_, ExprRet::CtxKilled(..)) | (ExprRet::CtxKilled(..), _) => Ok(()), (ExprRet::SingleLiteral(lhs), ExprRet::Single(rhs)) => { ContextVarNode::from(*lhs) - .cast_from(&ContextVarNode::from(*rhs), self) + .cast_from(&ContextVarNode::from(*rhs), self, arena) .into_expr_err(loc)?; self.handle_require_inner( + arena, ctx, loc, &ExprRet::Single(*lhs), @@ -624,9 +645,10 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { } (ExprRet::Single(lhs), ExprRet::SingleLiteral(rhs)) => { ContextVarNode::from(*rhs) - .cast_from(&ContextVarNode::from(*lhs), self) + .cast_from(&ContextVarNode::from(*lhs), self, arena) .into_expr_err(loc)?; self.handle_require_inner( + arena, ctx, loc, lhs_paths, @@ -642,17 +664,35 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { let rhs_cvar = ContextVarNode::from(*rhs).latest_version(self); let new_rhs = self.advance_var_in_ctx(rhs_cvar, loc, ctx)?; - self.require(new_lhs, new_rhs, ctx, loc, op, rhs_op, recursion_ops)?; + self.require(arena, new_lhs, new_rhs, ctx, loc, op, rhs_op, recursion_ops)?; Ok(()) } (l @ ExprRet::Single(_) | l @ ExprRet::SingleLiteral(_), ExprRet::Multi(rhs_sides)) => { rhs_sides.iter().try_for_each(|expr_ret| { - self.handle_require_inner(ctx, loc, l, expr_ret, op, rhs_op, recursion_ops) + self.handle_require_inner( + arena, + ctx, + loc, + l, + expr_ret, + op, + rhs_op, + recursion_ops, + ) }) } (ExprRet::Multi(lhs_sides), r @ ExprRet::Single(_) | r @ ExprRet::SingleLiteral(_)) => { lhs_sides.iter().try_for_each(|expr_ret| { - self.handle_require_inner(ctx, loc, expr_ret, r, op, rhs_op, recursion_ops) + self.handle_require_inner( + arena, + ctx, + loc, + expr_ret, + r, + op, + rhs_op, + recursion_ops, + ) }) } (ExprRet::Multi(lhs_sides), ExprRet::Multi(rhs_sides)) => { @@ -661,6 +701,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { lhs_sides.iter().zip(rhs_sides.iter()).try_for_each( |(lhs_expr_ret, rhs_expr_ret)| { self.handle_require_inner( + arena, ctx, loc, lhs_expr_ret, @@ -674,6 +715,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { } else { rhs_sides.iter().try_for_each(|rhs_expr_ret| { self.handle_require_inner( + arena, ctx, loc, lhs_paths, @@ -698,6 +740,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { #[tracing::instrument(level = "trace", skip_all)] fn require( &mut self, + arena: &mut RangeArena>, mut new_lhs: ContextVarNode, mut new_rhs: ContextVarNode, ctx: ContextNode, @@ -724,11 +767,11 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { let mut new_var_range = lhs_range_fn(lhs_range.clone(), new_rhs); if let Some(rhs_range) = new_rhs.range(self).into_expr_err(loc)? { - let lhs_is_const = new_lhs.is_const(self).into_expr_err(loc)?; - let rhs_is_const = new_rhs.is_const(self).into_expr_err(loc)?; + let lhs_is_const = new_lhs.is_const(self, arena).into_expr_err(loc)?; + let rhs_is_const = new_rhs.is_const(self, arena).into_expr_err(loc)?; match (lhs_is_const, rhs_is_const) { (true, true) => { - if self.const_killable(op, lhs_range, rhs_range) { + if self.const_killable(arena, op, lhs_range, rhs_range) { tracing::trace!("const killable"); ctx.kill(self, loc, KilledKind::Revert).into_expr_err(loc)?; return Ok(None); @@ -739,7 +782,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { let rhs_range_fn = SolcRange::dyn_fn_from_op(rhs_op); new_var_range = rhs_range_fn(rhs_range.clone(), new_lhs); if self.update_nonconst_from_const( - ctx, loc, rhs_op, new_lhs, new_rhs, rhs_range, + arena, ctx, loc, rhs_op, new_lhs, new_rhs, rhs_range, )? { tracing::trace!("half-const killable"); ctx.kill(self, loc, KilledKind::Revert).into_expr_err(loc)?; @@ -747,9 +790,9 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { } } (false, true) => { - if self - .update_nonconst_from_const(ctx, loc, op, new_rhs, new_lhs, lhs_range)? - { + if self.update_nonconst_from_const( + arena, ctx, loc, op, new_rhs, new_lhs, lhs_range, + )? { tracing::trace!("half-const killable"); ctx.kill(self, loc, KilledKind::Revert).into_expr_err(loc)?; return Ok(None); @@ -757,7 +800,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { } (false, false) => { if self.update_nonconst_from_nonconst( - ctx, loc, op, new_lhs, new_rhs, lhs_range, rhs_range, + arena, ctx, loc, op, new_lhs, new_rhs, lhs_range, rhs_range, )? { tracing::trace!("nonconst killable"); ctx.kill(self, loc, KilledKind::Revert).into_expr_err(loc)?; @@ -889,11 +932,12 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { tmp_cvar = Some(cvar); tracing::trace!("checking unsat"); - any_unsat |= new_var_range.unsat(self); + any_unsat |= new_var_range.unsat(self, arena); - ctx.add_ctx_dep(conditional_cvar, self).into_expr_err(loc)?; + ctx.add_ctx_dep(conditional_cvar, self, arena) + .into_expr_err(loc)?; - if any_unsat || ctx.unreachable(self).into_expr_err(loc)? { + if any_unsat || ctx.unreachable(self, arena).into_expr_err(loc)? { ctx.kill(self, loc, KilledKind::Revert).into_expr_err(loc)?; return Ok(None); } @@ -911,7 +955,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { match tmp.op { RangeOp::Not => {} _ => { - self.uninvertable_range_recursion(tmp, new_lhs, new_rhs, loc, ctx); + self.uninvertable_range_recursion(arena, tmp, new_lhs, new_rhs, loc, ctx); } } } @@ -921,50 +965,56 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { } /// Checks and returns whether the require statement is killable (i.e. impossible) - fn const_killable(&mut self, op: RangeOp, lhs_range: SolcRange, rhs_range: SolcRange) -> bool { + fn const_killable( + &mut self, + arena: &mut RangeArena>, + op: RangeOp, + lhs_range: SolcRange, + rhs_range: SolcRange, + ) -> bool { // check that the op is satisfied, return it as a bool match op { RangeOp::Eq => !lhs_range - .evaled_range_min(self) + .evaled_range_min(self, arena) .unwrap() - .range_eq(&rhs_range.evaled_range_min(self).unwrap(), self), + .range_eq(&rhs_range.evaled_range_min(self, arena).unwrap(), arena), RangeOp::Neq => lhs_range - .evaled_range_min(self) + .evaled_range_min(self, arena) .unwrap() - .range_eq(&rhs_range.evaled_range_min(self).unwrap(), self), + .range_eq(&rhs_range.evaled_range_min(self, arena).unwrap(), arena), RangeOp::Gt => { matches!( lhs_range - .evaled_range_min(self) + .evaled_range_min(self, arena) .unwrap() - .range_ord(&rhs_range.evaled_range_min(self).unwrap(), self), + .range_ord(&rhs_range.evaled_range_min(self, arena).unwrap(), arena), Some(Ordering::Equal) | Some(Ordering::Less) ) } RangeOp::Gte => { matches!( lhs_range - .evaled_range_min(self) + .evaled_range_min(self, arena) .unwrap() - .range_ord(&rhs_range.evaled_range_min(self).unwrap(), self), + .range_ord(&rhs_range.evaled_range_min(self, arena).unwrap(), arena), Some(Ordering::Less) ) } RangeOp::Lt => { matches!( lhs_range - .evaled_range_min(self) + .evaled_range_min(self, arena) .unwrap() - .range_ord(&rhs_range.evaled_range_min(self).unwrap(), self), + .range_ord(&rhs_range.evaled_range_min(self, arena).unwrap(), arena), Some(Ordering::Equal) | Some(Ordering::Greater) ) } RangeOp::Lte => { matches!( lhs_range - .evaled_range_min(self) + .evaled_range_min(self, arena) .unwrap() - .range_ord(&rhs_range.evaled_range_min(self).unwrap(), self), + .range_ord(&rhs_range.evaled_range_min(self, arena).unwrap(), arena), Some(Ordering::Greater) ) } @@ -976,6 +1026,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { #[tracing::instrument(level = "trace", skip_all)] fn update_nonconst_from_const( &mut self, + arena: &mut RangeArena>, _ctx: ContextNode, loc: Loc, op: RangeOp, @@ -988,19 +1039,23 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { RangeOp::Eq => { // check that the constant is contained in the nonconst var range let elem = Elem::from(const_var.latest_version(self)); - let evaled_min = nonconst_range.evaled_range_min(self).into_expr_err(loc)?; + let evaled_min = nonconst_range + .evaled_range_min(self, arena) + .into_expr_err(loc)?; if evaled_min.maybe_concrete().is_none() { - return Err(ExprErr::BadRange(loc, format!("Expected to have a concrete range by now. This is likely a bug (update nonconst from const: Eq). Min: {}", evaled_min.to_range_string(false, self).s))); + return Err(ExprErr::BadRange(loc, format!("Expected to have a concrete range by now. This is likely a bug (update nonconst from const: Eq). Min: {}", evaled_min.to_range_string(false, self, arena).s))); } - if !nonconst_range.contains_elem(&elem, self) { + if !nonconst_range.contains_elem(&elem, self, arena) { return Ok(true); } // if its contained, we can set the min & max to it nonconst_var - .set_range_min(self, elem.clone()) + .set_range_min(self, arena, elem.clone()) + .into_expr_err(loc)?; + nonconst_var + .set_range_max(self, arena, elem) .into_expr_err(loc)?; - nonconst_var.set_range_max(self, elem).into_expr_err(loc)?; Ok(false) } RangeOp::Neq => { @@ -1009,35 +1064,43 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { // potentially add the const var as a range exclusion if let Some(Ordering::Equal) = nonconst_range - .evaled_range_min(self) + .evaled_range_min(self, arena) .into_expr_err(loc)? - .range_ord(&elem, self) + .range_ord(&elem, arena) { // mins are equivalent, add 1 instead of adding an exclusion - let min = nonconst_range.evaled_range_min(self).into_expr_err(loc)?; + let min = nonconst_range + .evaled_range_min(self, arena) + .into_expr_err(loc)?; let Some(min) = min.maybe_concrete() else { - return Err(ExprErr::BadRange(loc, format!("Expected to have a concrete range by now. This is likely a bug (update nonconst from const: Neq). Min: {}", min.to_range_string(false, self).s))); + return Err(ExprErr::BadRange(loc, format!("Expected to have a concrete range by now. This is likely a bug (update nonconst from const: Neq). Min: {}", min.to_range_string(false, self, arena).s))); }; let one = Concrete::one(&min.val).expect("Cannot increment range elem by one"); let min = nonconst_range.range_min().into_owned() + Elem::from(one); - nonconst_var.set_range_min(self, min).into_expr_err(loc)?; + nonconst_var + .set_range_min(self, arena, min) + .into_expr_err(loc)?; } else if let Some(std::cmp::Ordering::Equal) = nonconst_range - .evaled_range_max(self) + .evaled_range_max(self, arena) .into_expr_err(loc)? - .range_ord(&elem, self) + .range_ord(&elem, arena) { // maxs are equivalent, subtract 1 instead of adding an exclusion - let max = nonconst_range.evaled_range_max(self).into_expr_err(loc)?; + let max = nonconst_range + .evaled_range_max(self, arena) + .into_expr_err(loc)?; let Some(max) = max.maybe_concrete() else { - return Err(ExprErr::BadRange(loc, format!("Expected to have a concrete range by now. This is likely a bug (update nonconst from const: Neq 2). Max: {}", max.to_range_string(true, self).s))); + return Err(ExprErr::BadRange(loc, format!("Expected to have a concrete range by now. This is likely a bug (update nonconst from const: Neq 2). Max: {}", max.to_range_string(true, self, arena).s))); }; let one = Concrete::one(&max.val).expect("Cannot decrement range elem by one"); let max = nonconst_range.range_max().into_owned() - Elem::from(one); - nonconst_var.set_range_max(self, max).into_expr_err(loc)?; + nonconst_var + .set_range_max(self, arena, max) + .into_expr_err(loc)?; } else { // just add as an exclusion - let idx = self.range_arena_idx_or_upsert(elem); + let idx = arena.idx_or_upsert(elem, self); nonconst_range.add_range_exclusion(idx); nonconst_var .set_range_exclusions(self, nonconst_range.exclusions) @@ -1050,9 +1113,11 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { let elem = Elem::from(const_var.latest_version(self)); // if nonconst max is <= const, we can't make this true - let max = nonconst_range.evaled_range_max(self).into_expr_err(loc)?; + let max = nonconst_range + .evaled_range_max(self, arena) + .into_expr_err(loc)?; if matches!( - max.range_ord(&elem.minimize(self).into_expr_err(loc)?, self), + max.range_ord(&elem.minimize(self, arena).into_expr_err(loc)?, arena), Some(Ordering::Less) | Some(Ordering::Equal) ) { return Ok(true); @@ -1060,7 +1125,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { // we add one to the element because its strict > let Some(max_conc) = const_var - .evaled_range_min(self) + .evaled_range_min(self, arena) .unwrap() .unwrap() .maybe_concrete() @@ -1072,13 +1137,14 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { nonconst_var.display_name(self).unwrap(), op.to_string(), const_var.display_name(self).unwrap(), - const_var.evaled_range_min(self).unwrap().unwrap() + const_var.evaled_range_min(self, arena).unwrap().unwrap() ))); }; let one = Concrete::one(&max_conc.val).expect("Cannot decrement range elem by one"); nonconst_var .set_range_min( self, + arena, (elem + one.into()).max(nonconst_range.range_min().into_owned()), ) .into_expr_err(loc)?; @@ -1090,16 +1156,20 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { // if nonconst max is < const, we can't make this true if matches!( nonconst_range - .evaled_range_max(self) + .evaled_range_max(self, arena) .into_expr_err(loc)? - .range_ord(&elem.minimize(self).into_expr_err(loc)?, self), + .range_ord(&elem.minimize(self, arena).into_expr_err(loc)?, arena), Some(Ordering::Less) ) { return Ok(true); } nonconst_var - .set_range_min(self, elem.max(nonconst_range.range_min().into_owned())) + .set_range_min( + self, + arena, + elem.max(nonconst_range.range_min().into_owned()), + ) .into_expr_err(loc)?; Ok(false) } @@ -1107,9 +1177,11 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { let elem = Elem::from(const_var.latest_version(self)); // if nonconst min is >= const, we can't make this true - let min = nonconst_range.evaled_range_min(self).into_expr_err(loc)?; + let min = nonconst_range + .evaled_range_min(self, arena) + .into_expr_err(loc)?; if matches!( - min.range_ord(&elem.minimize(self).into_expr_err(loc)?, self), + min.range_ord(&elem.minimize(self, arena).into_expr_err(loc)?, arena), Some(Ordering::Greater) | Some(Ordering::Equal) ) { return Ok(true); @@ -1125,6 +1197,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { nonconst_var .set_range_max( self, + arena, (elem - one.into()).min(nonconst_range.range_max().into_owned()), ) .into_expr_err(loc)?; @@ -1134,16 +1207,22 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { let elem = Elem::from(const_var.latest_version(self)); // if nonconst min is > const, we can't make this true - let min = nonconst_range.evaled_range_min(self).into_expr_err(loc)?; + let min = nonconst_range + .evaled_range_min(self, arena) + .into_expr_err(loc)?; if matches!( - min.range_ord(&elem.minimize(self).into_expr_err(loc)?, self), + min.range_ord(&elem.minimize(self, arena).into_expr_err(loc)?, arena), Some(Ordering::Greater) ) { return Ok(true); } nonconst_var - .set_range_max(self, elem.min(nonconst_range.range_max().into_owned())) + .set_range_max( + self, + arena, + elem.min(nonconst_range.range_max().into_owned()), + ) .into_expr_err(loc)?; Ok(false) } @@ -1154,6 +1233,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { /// Given a const var and a nonconst range, update the range based on the op. Returns whether its impossible fn update_nonconst_from_nonconst( &mut self, + arena: &mut RangeArena>, _ctx: ContextNode, loc: Loc, op: RangeOp, @@ -1166,26 +1246,28 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { match op { RangeOp::Eq => { // check that there is overlap in the ranges - if !lhs_range.overlaps(&rhs_range, self) { + if !lhs_range.overlaps(&rhs_range, self, arena) { return Ok(true); } // take the tighest range match lhs_range - .evaled_range_min(self) + .evaled_range_min(self, arena) .into_expr_err(loc)? - .range_ord(&rhs_range.evaled_range_min(self).into_expr_err(loc)?, self) - { + .range_ord( + &rhs_range.evaled_range_min(self, arena).into_expr_err(loc)?, + arena, + ) { Some(Ordering::Greater) => { // take lhs range min as its tigher new_rhs - .set_range_min(self, Elem::from(new_rhs)) + .set_range_min(self, arena, Elem::from(new_rhs)) .into_expr_err(loc)?; } Some(Ordering::Less) => { // take rhs range min as its tigher new_lhs - .set_range_min(self, rhs_range.range_min().into_owned()) + .set_range_min(self, arena, rhs_range.range_min().into_owned()) .into_expr_err(loc)?; } _ => { @@ -1195,20 +1277,22 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { // take the tighest range match lhs_range - .evaled_range_max(self) + .evaled_range_max(self, arena) .into_expr_err(loc)? - .range_ord(&rhs_range.evaled_range_max(self).into_expr_err(loc)?, self) - { + .range_ord( + &rhs_range.evaled_range_max(self, arena).into_expr_err(loc)?, + arena, + ) { Some(Ordering::Less) => { // take lhs range min as its tigher new_rhs - .set_range_max(self, lhs_range.range_max().into_owned()) + .set_range_max(self, arena, lhs_range.range_max().into_owned()) .into_expr_err(loc)?; } Some(Ordering::Greater) => { // take rhs range min as its tigher new_lhs - .set_range_max(self, rhs_range.range_max().into_owned()) + .set_range_max(self, arena, rhs_range.range_max().into_owned()) .into_expr_err(loc)?; } _ => { @@ -1225,7 +1309,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { let rhs_elem = Elem::from(new_rhs.latest_version(self)); // just add as an exclusion - let idx = self.range_arena_idx_or_upsert(rhs_elem); + let idx = arena.idx_or_upsert(rhs_elem, self); lhs_range.add_range_exclusion(idx); new_lhs .set_range_exclusions(self, lhs_range.exclusions) @@ -1233,7 +1317,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { let lhs_elem = Elem::from(new_lhs.latest_version(self)); // just add as an exclusion - let idx = self.range_arena_idx_or_upsert(lhs_elem); + let idx = arena.idx_or_upsert(lhs_elem, self); rhs_range.add_range_exclusion(idx); new_rhs .set_range_exclusions(self, rhs_range.exclusions) @@ -1245,16 +1329,16 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { let lhs_elem = Elem::from(new_lhs.latest_version(self)); // if lhs.max is <= rhs.min, we can't make this true - let max = lhs_range.evaled_range_max(self).into_expr_err(loc)?; + let max = lhs_range.evaled_range_max(self, arena).into_expr_err(loc)?; if matches!( - max.range_ord(&rhs_elem.minimize(self).into_expr_err(loc)?, self), + max.range_ord(&rhs_elem.minimize(self, arena).into_expr_err(loc)?, arena), Some(Ordering::Less) | Some(Ordering::Equal) ) { return Ok(true); } let Some(max_conc) = max.maybe_concrete() else { - return Err(ExprErr::BadRange(loc, format!("Expected to have a concrete range by now. This is likely a bug (update nonconst from nonconst: Gt). Max: {}", max.to_range_string(true, self).s))); + return Err(ExprErr::BadRange(loc, format!("Expected to have a concrete range by now. This is likely a bug (update nonconst from nonconst: Gt). Max: {}", max.to_range_string(true, self, arena).s))); }; let one = Concrete::one(&max_conc.val).expect("Cannot decrement range elem by one"); @@ -1264,6 +1348,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { .latest_version(self) .set_range_min( self, + arena, (rhs_elem + one.clone().into()).max(lhs_range.range_min().into_owned()), ) .into_expr_err(loc)?; @@ -1271,6 +1356,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { .latest_version(self) .set_range_max( self, + arena, (lhs_elem - one.into()).min(rhs_range.range_max().into_owned()), ) .into_expr_err(loc)?; @@ -1285,9 +1371,9 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { let lhs_elem = Elem::from(new_lhs.latest_version(self)); // if lhs.max is < rhs.min, we can't make this true - let max = lhs_range.evaled_range_max(self).into_expr_err(loc)?; - let min = rhs_elem.minimize(self).into_expr_err(loc)?; - if let Some(Ordering::Less) = max.range_ord(&min, self) { + let max = lhs_range.evaled_range_max(self, arena).into_expr_err(loc)?; + let min = rhs_elem.minimize(self, arena).into_expr_err(loc)?; + if let Some(Ordering::Less) = max.range_ord(&min, arena) { return Ok(true); } @@ -1306,10 +1392,13 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { let new_new_rhs = self.advance_var_in_curr_ctx(new_rhs, loc)?; new_new_lhs - .set_range_min(self, new_min) + .set_range_min(self, arena, new_min.clone()) + .into_expr_err(loc)?; + new_new_rhs + .set_range_min(self, arena, new_max.clone()) .into_expr_err(loc)?; new_new_rhs - .set_range_max(self, new_max) + .set_range_max(self, arena, new_max) .into_expr_err(loc)?; Ok(false) } @@ -1318,9 +1407,9 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { let lhs_elem = Elem::from(new_lhs.latest_version(self)); // if lhs min is >= rhs.max, we can't make this true - let min = lhs_range.evaled_range_min(self).into_expr_err(loc)?; + let min = lhs_range.evaled_range_min(self, arena).into_expr_err(loc)?; if matches!( - min.range_ord(&rhs_elem.maximize(self).into_expr_err(loc)?, self), + min.range_ord(&rhs_elem.maximize(self, arena).into_expr_err(loc)?, arena), Some(Ordering::Greater) | Some(Ordering::Equal) ) { return Ok(true); @@ -1328,7 +1417,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { // we add/sub one to the element because its strict > let Some(min_conc) = min.maybe_concrete() else { - return Err(ExprErr::BadRange(loc, format!("Expected to have a concrete range by now. This is likely a bug (update nonconst from const: Lt). Min: {}", min.to_range_string(false, self).s))); + return Err(ExprErr::BadRange(loc, format!("Expected to have a concrete range by now. This is likely a bug (update nonconst from const: Lt). Min: {}", min.to_range_string(false, self, arena).s))); }; let one = Concrete::one(&min_conc.val).expect("Cannot decrement range elem by one"); @@ -1340,6 +1429,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { .latest_version(self) .set_range_max( self, + arena, (rhs_elem - one.clone().into()).min(lhs_range.range_max().into_owned()), ) .into_expr_err(loc)?; @@ -1347,6 +1437,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { .latest_version(self) .set_range_min( self, + arena, (lhs_elem + one.into()).max(rhs_range.range_min().into_owned()), ) .into_expr_err(loc)?; @@ -1354,12 +1445,13 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { } RangeOp::Lte => { let rhs_elem = Elem::from(new_rhs.latest_version(self)); - let lhs_elem = Elem::from(new_lhs.latest_version(self)); + let lhs_elem = Elem::from(new_lhs.latest_version(self)) + .max(rhs_range.range_min().into_owned()); // if nonconst min is > const, we can't make this true - let min = lhs_range.evaled_range_min(self).into_expr_err(loc)?; + let min = lhs_range.evaled_range_min(self, arena).into_expr_err(loc)?; if matches!( - min.range_ord(&rhs_elem.maximize(self).into_expr_err(loc)?, self), + min.range_ord(&rhs_elem.maximize(self, arena).into_expr_err(loc)?, arena), Some(Ordering::Greater) ) { return Ok(true); @@ -1367,11 +1459,19 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { new_lhs .latest_version(self) - .set_range_max(self, rhs_elem.min(lhs_range.range_max().into_owned())) + .set_range_max( + self, + arena, + rhs_elem.min(lhs_range.range_max().into_owned()), + ) + .into_expr_err(loc)?; + new_rhs + .latest_version(self) + .set_range_min(self, arena, lhs_elem.clone()) .into_expr_err(loc)?; new_rhs .latest_version(self) - .set_range_min(self, lhs_elem.max(rhs_range.range_min().into_owned())) + .set_range_max(self, arena, lhs_elem) .into_expr_err(loc)?; Ok(false) } @@ -1381,24 +1481,26 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { fn uninvertable_range_recursion( &mut self, + arena: &mut RangeArena>, tmp_construction: TmpConstruction, _new_lhs_core: ContextVarNode, _rhs_cvar: ContextVarNode, loc: Loc, ctx: ContextNode, ) { - if !tmp_construction.lhs.is_const(self).unwrap() { + if !tmp_construction.lhs.is_const(self, arena).unwrap() { // widen to maximum range :( let new_underlying_lhs = self .advance_var_in_ctx(tmp_construction.lhs.latest_version(self), loc, ctx) .unwrap(); if let Some(lhs_range) = tmp_construction.lhs.ref_range(self).unwrap() { - if let Elem::Concrete(c) = lhs_range.evaled_range_min(self).unwrap() { + if let Elem::Concrete(c) = lhs_range.evaled_range_min(self, arena).unwrap() { new_underlying_lhs .set_range_min( self, + arena, Elem::Concrete(RangeConcrete { - val: Concrete::min(&c.val).unwrap_or_else(|| c.val.clone()), + val: Concrete::min_of_type(&c.val).unwrap_or_else(|| c.val.clone()), loc, }), ) @@ -1406,8 +1508,9 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { new_underlying_lhs .set_range_max( self, + arena, Elem::Concrete(RangeConcrete { - val: Concrete::max(&c.val).unwrap_or(c.val), + val: Concrete::max_of_type(&c.val).unwrap_or(c.val), loc, }), ) @@ -1420,6 +1523,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { /// Recursively updates the range for a fn range_recursion( &mut self, + arena: &mut RangeArena>, tmp_construction: TmpConstruction, (flip_op, no_flip_op): (RangeOp, RangeOp), rhs_cvar: ContextVarNode, @@ -1433,10 +1537,15 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { return Ok(()); }; - if !tmp_construction.lhs.is_const(self).into_expr_err(loc)? { + if !tmp_construction + .lhs + .is_const(self, arena) + .into_expr_err(loc)? + { tracing::trace!("handling lhs range recursion"); let adjusted_gt_rhs = ContextVarNode::from({ let tmp = self.op( + arena, loc, rhs_cvar, tmp_construction.rhs.expect("No rhs in tmp_construction"), @@ -1470,19 +1579,20 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { let new_lhs_range = lhs_range_fn(lhs_range, adjusted_gt_rhs); new_underlying_lhs - .set_range_min(self, new_lhs_range.range_min().into_owned()) + .set_range_min(self, arena, new_lhs_range.range_min().into_owned()) .into_expr_err(loc)?; new_underlying_lhs - .set_range_max(self, new_lhs_range.range_max().into_owned()) + .set_range_max(self, arena, new_lhs_range.range_max().into_owned()) .into_expr_err(loc)?; - if new_lhs_range.unsat(self) { + if new_lhs_range.unsat(self, arena) { *any_unsat = true; ctx.kill(self, loc, KilledKind::Revert).into_expr_err(loc)?; return Ok(()); } if let Some(tmp) = new_underlying_lhs.tmp_of(self).into_expr_err(loc)? { self.range_recursion( + arena, tmp, (flip_op, no_flip_op), adjusted_gt_rhs, @@ -1497,7 +1607,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { // handle rhs if let Some(rhs) = tmp_construction.rhs { - if !rhs.is_const(self).into_expr_err(loc)? { + if !rhs.is_const(self, arena).into_expr_err(loc)? { tracing::trace!("handling rhs range recursion"); let (needs_inverse, adjusted_gt_rhs) = match tmp_construction.op { RangeOp::Sub(..) => { @@ -1511,8 +1621,15 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { ContextVarNode::from(self.add_node(Node::ContextVar(lhs_cvar))); // tmp_rhs = rhs_cvar * -1 - let tmp_rhs = - self.op(loc, rhs_cvar, tmp_lhs, ctx, RangeOp::Mul(false), false)?; + let tmp_rhs = self.op( + arena, + loc, + rhs_cvar, + tmp_lhs, + ctx, + RangeOp::Mul(false), + false, + )?; if matches!(tmp_rhs, ExprRet::CtxKilled(_)) { ctx.push_expr(tmp_rhs, self).into_expr_err(loc)?; return Ok(()); @@ -1521,8 +1638,15 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { ContextVarNode::from(tmp_rhs.expect_single().into_expr_err(loc)?); // new_rhs = (rhs_cvar * -1) + tmp_construction.lhs - let new_rhs = - self.op(loc, tmp_rhs, tmp_construction.lhs, ctx, inverse, false)?; + let new_rhs = self.op( + arena, + loc, + tmp_rhs, + tmp_construction.lhs, + ctx, + inverse, + false, + )?; if matches!(new_rhs, ExprRet::CtxKilled(_)) { ctx.push_expr(new_rhs, self).into_expr_err(loc)?; return Ok(()); @@ -1532,8 +1656,15 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { (true, new_rhs) } RangeOp::Add(..) => { - let new_rhs = - self.op(loc, rhs_cvar, tmp_construction.lhs, ctx, inverse, false)?; + let new_rhs = self.op( + arena, + loc, + rhs_cvar, + tmp_construction.lhs, + ctx, + inverse, + false, + )?; if matches!(new_rhs, ExprRet::CtxKilled(_)) { ctx.push_expr(new_rhs, self).into_expr_err(loc)?; return Ok(()); @@ -1543,8 +1674,15 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { (false, new_rhs) } RangeOp::Mul(..) => { - let new_rhs = - self.op(loc, rhs_cvar, tmp_construction.lhs, ctx, inverse, false)?; + let new_rhs = self.op( + arena, + loc, + rhs_cvar, + tmp_construction.lhs, + ctx, + inverse, + false, + )?; if matches!(new_rhs, ExprRet::CtxKilled(_)) { ctx.push_expr(new_rhs, self).into_expr_err(loc)?; return Ok(()); @@ -1554,8 +1692,15 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { (false, new_rhs) } RangeOp::Div(..) => { - let new_rhs = - self.op(loc, rhs_cvar, tmp_construction.lhs, ctx, inverse, false)?; + let new_rhs = self.op( + arena, + loc, + rhs_cvar, + tmp_construction.lhs, + ctx, + inverse, + false, + )?; if matches!(new_rhs, ExprRet::CtxKilled(_)) { ctx.push_expr(new_rhs, self).into_expr_err(loc)?; return Ok(()); @@ -1565,8 +1710,15 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { (false, new_rhs) } RangeOp::Shl => { - let new_rhs = - self.op(loc, rhs_cvar, tmp_construction.lhs, ctx, inverse, false)?; + let new_rhs = self.op( + arena, + loc, + rhs_cvar, + tmp_construction.lhs, + ctx, + inverse, + false, + )?; if matches!(new_rhs, ExprRet::CtxKilled(_)) { ctx.push_expr(new_rhs, self).into_expr_err(loc)?; return Ok(()); @@ -1576,8 +1728,15 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { (false, new_rhs) } RangeOp::Shr => { - let new_rhs = - self.op(loc, rhs_cvar, tmp_construction.lhs, ctx, inverse, false)?; + let new_rhs = self.op( + arena, + loc, + rhs_cvar, + tmp_construction.lhs, + ctx, + inverse, + false, + )?; if matches!(new_rhs, ExprRet::CtxKilled(_)) { ctx.push_expr(new_rhs, self).into_expr_err(loc)?; return Ok(()); @@ -1587,8 +1746,15 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { (false, new_rhs) } RangeOp::Eq => { - let new_rhs = - self.op(loc, rhs_cvar, tmp_construction.lhs, ctx, inverse, false)?; + let new_rhs = self.op( + arena, + loc, + rhs_cvar, + tmp_construction.lhs, + ctx, + inverse, + false, + )?; if matches!(new_rhs, ExprRet::CtxKilled(_)) { ctx.push_expr(new_rhs, self).into_expr_err(loc)?; return Ok(()); @@ -1598,8 +1764,15 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { (false, new_rhs) } RangeOp::Neq => { - let new_rhs = - self.op(loc, rhs_cvar, tmp_construction.lhs, ctx, inverse, false)?; + let new_rhs = self.op( + arena, + loc, + rhs_cvar, + tmp_construction.lhs, + ctx, + inverse, + false, + )?; if matches!(new_rhs, ExprRet::CtxKilled(_)) { ctx.push_expr(new_rhs, self).into_expr_err(loc)?; return Ok(()); @@ -1636,13 +1809,13 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { }; new_underlying_rhs - .set_range_min(self, new_lhs_range.range_min().into_owned()) + .set_range_min(self, arena, new_lhs_range.range_min().into_owned()) .into_expr_err(loc)?; new_underlying_rhs - .set_range_max(self, new_lhs_range.range_max().into_owned()) + .set_range_max(self, arena, new_lhs_range.range_max().into_owned()) .into_expr_err(loc)?; - if new_lhs_range.unsat(self) { + if new_lhs_range.unsat(self, arena) { *any_unsat = true; ctx.kill(self, loc, KilledKind::Revert).into_expr_err(loc)?; return Ok(()); @@ -1650,6 +1823,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { if let Some(tmp) = new_underlying_rhs.tmp_of(self).into_expr_err(loc)? { self.range_recursion( + arena, tmp, (flip_op, no_flip_op), adjusted_gt_rhs, diff --git a/crates/solc-expressions/src/variable.rs b/crates/solc-expressions/src/variable.rs index 18d0956b..d7d774ee 100644 --- a/crates/solc-expressions/src/variable.rs +++ b/crates/solc-expressions/src/variable.rs @@ -1,9 +1,11 @@ use crate::{assign::Assign, env::Env, ContextBuilder, ExprErr, IntoExprErr}; use graph::{ - nodes::{ContextNode, ContextVar, ContextVarNode, ExprRet, VarNode}, + elem::Elem, + nodes::{Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, VarNode}, AnalyzerBackend, ContextEdge, Edge, GraphError, Node, VarType, }; +use shared::RangeArena; use solang_parser::pt::{Expression, Identifier, Loc, VariableDeclaration}; @@ -14,6 +16,7 @@ pub trait Variable: AnalyzerBackend + Size /// Get a variable based on an identifier fn variable( &mut self, + arena: &mut RangeArena>, ident: &Identifier, ctx: ContextNode, recursion_target: Option, @@ -33,14 +36,19 @@ pub trait Variable: AnalyzerBackend + Size // solang doesnt have `super` as a keyword if let Some(cvar) = ctx.var_by_name(self, &ident.name) { let cvar = cvar.latest_version(self); - self.apply_to_edges(target_ctx, ident.loc, &|analyzer, edge_ctx, _loc| { - let var = analyzer.advance_var_in_ctx(cvar, ident.loc, edge_ctx)?; - edge_ctx - .push_expr(ExprRet::Single(var.into()), analyzer) - .into_expr_err(ident.loc) - }) + self.apply_to_edges( + target_ctx, + ident.loc, + arena, + &|analyzer, arena, edge_ctx, _loc| { + let var = analyzer.advance_var_in_ctx(cvar, ident.loc, edge_ctx)?; + edge_ctx + .push_expr(ExprRet::Single(var.into()), analyzer) + .into_expr_err(ident.loc) + }, + ) } else if ident.name == "_" { - self.env_variable(ident, target_ctx)?; + self.env_variable(arena, ident, target_ctx)?; Ok(()) } else if let Some(cvar) = ctx .var_by_name_or_recurse(self, &ident.name) @@ -48,18 +56,23 @@ pub trait Variable: AnalyzerBackend + Size { // check if we can inherit it let cvar = cvar.latest_version(self); - self.apply_to_edges(target_ctx, ident.loc, &|analyzer, edge_ctx, _loc| { - let var = analyzer.advance_var_in_ctx(cvar, ident.loc, edge_ctx)?; - edge_ctx - .push_expr(ExprRet::Single(var.into()), analyzer) - .into_expr_err(ident.loc) - }) + self.apply_to_edges( + target_ctx, + ident.loc, + arena, + &|analyzer, arena, edge_ctx, _loc| { + let var = analyzer.advance_var_in_ctx(cvar, ident.loc, edge_ctx)?; + edge_ctx + .push_expr(ExprRet::Single(var.into()), analyzer) + .into_expr_err(ident.loc) + }, + ) // if let Some(recursion_target) = recursion_target { // self.variable(ident, parent_ctx, Some(recursion_target)) // } else { // self.variable(ident, parent_ctx, Some(target_ctx)) // } - } else if (self.env_variable(ident, target_ctx)?).is_some() { + } else if (self.env_variable(arena, ident, target_ctx)?).is_some() { Ok(()) } else if let Some(idx) = self.user_types().get(&ident.name).cloned() { let const_var = if let Node::Var(_v) = self.node(idx) { @@ -141,6 +154,7 @@ pub trait Variable: AnalyzerBackend + Size fn get_unchanged_tmp_variable( &mut self, + arena: &mut RangeArena>, name: &str, ctx: ContextNode, ) -> Result, GraphError> { @@ -151,13 +165,13 @@ pub trait Variable: AnalyzerBackend + Size if let Some(tmp) = var.tmp_of(self)? { if tmp.lhs.latest_version(self) != tmp.lhs { let latest = tmp.lhs.latest_version(self); - let newest_min = latest.evaled_range_min(self)?; - let curr_min = tmp.lhs.evaled_range_min(self)?; + let newest_min = latest.evaled_range_min(self, arena)?; + let curr_min = tmp.lhs.evaled_range_min(self, arena)?; if newest_min != curr_min { return Ok(None); } - let newest_max = latest.evaled_range_max(self)?; - let curr_max = tmp.lhs.evaled_range_max(self)?; + let newest_max = latest.evaled_range_max(self, arena)?; + let curr_max = tmp.lhs.evaled_range_max(self, arena)?; if newest_max != curr_max { return Ok(None); } @@ -166,13 +180,13 @@ pub trait Variable: AnalyzerBackend + Size if let Some(rhs) = tmp.rhs { if rhs.latest_version(self) != rhs { let latest = rhs.latest_version(self); - let newest_min = latest.evaled_range_min(self)?; - let curr_min = rhs.evaled_range_min(self)?; + let newest_min = latest.evaled_range_min(self, arena)?; + let curr_min = rhs.evaled_range_min(self, arena)?; if newest_min != curr_min { return Ok(None); } - let newest_max = latest.evaled_range_max(self)?; - let curr_max = rhs.evaled_range_max(self)?; + let newest_max = latest.evaled_range_max(self, arena)?; + let curr_max = rhs.evaled_range_max(self, arena)?; if newest_max != curr_max { return Ok(None); } @@ -188,6 +202,7 @@ pub trait Variable: AnalyzerBackend + Size /// Match on the [`ExprRet`]s of a variable definition and construct the variable fn match_var_def( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, var_decl: &VariableDeclaration, loc: Loc, @@ -205,6 +220,7 @@ pub trait Variable: AnalyzerBackend + Size let res = rhs_cvar.literal_cast_from_ty(ty, self).into_expr_err(loc); let _ = self.add_if_err(res); self.match_var_def( + arena, ctx, var_decl, loc, @@ -232,8 +248,8 @@ pub trait Variable: AnalyzerBackend + Size self.add_edge(lhs, ctx, Edge::Context(ContextEdge::Variable)); let rhs = ContextVarNode::from(*rhs); - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { - let _ = analyzer.assign(loc, lhs, rhs, ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { + let _ = analyzer.assign(arena, loc, lhs, rhs, ctx)?; // match_assign_ret(analyzer, ctx, ret); Ok(()) })?; @@ -262,19 +278,19 @@ pub trait Variable: AnalyzerBackend + Size } (l @ ExprRet::Single(_lhs), Some(ExprRet::Multi(rhs_sides))) => Ok(rhs_sides .iter() - .map(|expr_ret| self.match_var_def(ctx, var_decl, loc, l, Some(expr_ret))) + .map(|expr_ret| self.match_var_def(arena, ctx, var_decl, loc, l, Some(expr_ret))) .collect::, ExprErr>>()? .iter() .all(|e| *e)), (ExprRet::Multi(lhs_sides), r @ Some(ExprRet::Single(_))) => Ok(lhs_sides .iter() - .map(|expr_ret| self.match_var_def(ctx, var_decl, loc, expr_ret, r)) + .map(|expr_ret| self.match_var_def(arena, ctx, var_decl, loc, expr_ret, r)) .collect::, ExprErr>>()? .iter() .all(|e| *e)), (ExprRet::Multi(lhs_sides), None) => Ok(lhs_sides .iter() - .map(|expr_ret| self.match_var_def(ctx, var_decl, loc, expr_ret, None)) + .map(|expr_ret| self.match_var_def(arena, ctx, var_decl, loc, expr_ret, None)) .collect::, ExprErr>>()? .iter() .all(|e| *e)), @@ -285,7 +301,14 @@ pub trait Variable: AnalyzerBackend + Size .iter() .zip(rhs_sides.iter()) .map(|(lhs_expr_ret, rhs_expr_ret)| { - self.match_var_def(ctx, var_decl, loc, lhs_expr_ret, Some(rhs_expr_ret)) + self.match_var_def( + arena, + ctx, + var_decl, + loc, + lhs_expr_ret, + Some(rhs_expr_ret), + ) }) .collect::, ExprErr>>()? .iter() @@ -294,7 +317,14 @@ pub trait Variable: AnalyzerBackend + Size Ok(rhs_sides .iter() .map(|rhs_expr_ret| { - self.match_var_def(ctx, var_decl, loc, lhs_paths, Some(rhs_expr_ret)) + self.match_var_def( + arena, + ctx, + var_decl, + loc, + lhs_paths, + Some(rhs_expr_ret), + ) }) .collect::, ExprErr>>()? .iter() diff --git a/crates/solc-expressions/src/yul/yul_builder.rs b/crates/solc-expressions/src/yul/yul_builder.rs index 2fdf9185..7383337e 100644 --- a/crates/solc-expressions/src/yul/yul_builder.rs +++ b/crates/solc-expressions/src/yul/yul_builder.rs @@ -5,9 +5,13 @@ use crate::{ }; use graph::{ - nodes::{BuiltInNode, Builtin, Context, ContextNode, ContextVar, ContextVarNode, ExprRet}, + elem::Elem, + nodes::{ + BuiltInNode, Builtin, Concrete, Context, ContextNode, ContextVar, ContextVarNode, ExprRet, + }, AnalyzerBackend, ContextEdge, Edge, Node, SolcRange, VarType, }; +use shared::RangeArena; use solang_parser::{ helpers::CodeLocation, @@ -24,8 +28,12 @@ pub trait YulBuilder: { #[tracing::instrument(level = "trace", skip_all, fields(ctx = %ctx.path(self)))] /// Parse a yul statement - fn parse_ctx_yul_statement(&mut self, stmt: &YulStatement, ctx: ContextNode) - where + fn parse_ctx_yul_statement( + &mut self, + arena: &mut RangeArena>, + stmt: &YulStatement, + ctx: ContextNode, + ) where Self: Sized, { if let Some(true) = self.add_if_err(ctx.is_ended(self).into_expr_err(stmt.loc())) { @@ -33,10 +41,10 @@ pub trait YulBuilder: } if let Some(live_edges) = self.add_if_err(ctx.live_edges(self).into_expr_err(stmt.loc())) { if live_edges.is_empty() { - self.parse_ctx_yul_stmt_inner(stmt, ctx) + self.parse_ctx_yul_stmt_inner(arena, stmt, ctx) } else { live_edges.iter().for_each(|fork_ctx| { - self.parse_ctx_yul_stmt_inner(stmt, *fork_ctx); + self.parse_ctx_yul_stmt_inner(arena, stmt, *fork_ctx); }); } } @@ -44,8 +52,12 @@ pub trait YulBuilder: #[tracing::instrument(level = "trace", skip_all)] /// After doing some setup in `parse_ctx_yul_statement`, actually parse a yul statement - fn parse_ctx_yul_stmt_inner(&mut self, stmt: &YulStatement, ctx: ContextNode) - where + fn parse_ctx_yul_stmt_inner( + &mut self, + arena: &mut RangeArena>, + stmt: &YulStatement, + ctx: ContextNode, + ) where Self: Sized, { use YulStatement::*; @@ -59,46 +71,60 @@ pub trait YulBuilder: if ctx.is_killed(self).unwrap() { return; } - let ret = self.apply_to_edges(ctx, stmt.loc(), &|analyzer, ctx, _loc| { + let ret = self.apply_to_edges(ctx, stmt.loc(), arena, &|analyzer, arena, ctx, _loc| { match stmt { Assign(loc, yul_exprs, yul_expr) => { match yul_exprs .iter() - .try_for_each(|expr| analyzer.parse_ctx_yul_expr(expr, ctx)) + .try_for_each(|expr| analyzer.parse_ctx_yul_expr(arena, expr, ctx)) { - Ok(()) => analyzer.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let Some(lhs_side) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "No left hand side assignments in yul block".to_string(), - )); - }; - if matches!(lhs_side, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_side, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - analyzer.parse_ctx_yul_expr(yul_expr, ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { - let Some(rhs_side) = + Ok(()) => analyzer.apply_to_edges( + ctx, + *loc, + arena, + &|analyzer, arena, ctx, loc| { + let Some(lhs_side) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( + return Err(ExprErr::NoLhs( loc, - "No right hand side assignments in yul block".to_string(), + "No left hand side assignments in yul block".to_string(), )); }; - - if matches!(rhs_side, ExprRet::CtxKilled(_)) { - ctx.push_expr(rhs_side, analyzer).into_expr_err(loc)?; + if matches!(lhs_side, ExprRet::CtxKilled(_)) { + ctx.push_expr(lhs_side, analyzer).into_expr_err(loc)?; return Ok(()); } - analyzer.match_assign_sides(ctx, loc, &lhs_side, &rhs_side) - }) - }), + analyzer.parse_ctx_yul_expr(arena, yul_expr, ctx)?; + analyzer.apply_to_edges( + ctx, + loc, + arena, + &|analyzer, arena, ctx, loc| { + let Some(rhs_side) = ctx + .pop_expr_latest(loc, analyzer) + .into_expr_err(loc)? + else { + return Err(ExprErr::NoRhs( + loc, + "No right hand side assignments in yul block" + .to_string(), + )); + }; + + if matches!(rhs_side, ExprRet::CtxKilled(_)) { + ctx.push_expr(rhs_side, analyzer).into_expr_err(loc)?; + return Ok(()); + } + + analyzer.match_assign_sides( + arena, ctx, loc, &lhs_side, &rhs_side, + ) + }, + ) + }, + ), Err(e) => Err(e), } } @@ -128,8 +154,8 @@ pub trait YulBuilder: .collect::>(); if let Some(yul_expr) = maybe_yul_expr { - analyzer.parse_ctx_yul_expr(yul_expr, ctx)?; - analyzer.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + analyzer.parse_ctx_yul_expr(arena, yul_expr, ctx)?; + analyzer.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { @@ -151,8 +177,8 @@ pub trait YulBuilder: } } If(loc, yul_expr, yul_block) => { - analyzer.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let ret = analyzer.yul_cond_op_stmt(loc, yul_expr, yul_block, ctx); + analyzer.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { + let ret = analyzer.yul_cond_op_stmt(arena, loc, yul_expr, yul_block, ctx); let _ = analyzer.add_if_err(ret); Ok(()) }) @@ -169,7 +195,7 @@ pub trait YulBuilder: .into_expr_err(*loc)?; let subctx = ContextNode::from(analyzer.add_node(Node::Context(sctx))); ctx.set_child_call(subctx, analyzer).into_expr_err(*loc)?; - analyzer.apply_to_edges(subctx, *loc, &|analyzer, subctx, loc| { + analyzer.apply_to_edges(subctx, *loc, arena, &|analyzer, arena, subctx, loc| { let vars = subctx.local_vars(analyzer).clone(); vars.iter().for_each(|(name, var)| { // widen to max range @@ -186,11 +212,11 @@ pub trait YulBuilder: .advance_var_in_ctx(inheritor_var, loc, ctx) .unwrap(); let res = new_inheritor_var - .set_range_min(analyzer, r.min) + .set_range_min(analyzer, arena, r.min) .into_expr_err(loc); let _ = analyzer.add_if_err(res); let res = new_inheritor_var - .set_range_max(analyzer, r.max) + .set_range_max(analyzer, arena, r.max) .into_expr_err(loc); let _ = analyzer.add_if_err(res); } @@ -204,8 +230,9 @@ pub trait YulBuilder: condition, cases, default, - }) => analyzer.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + }) => analyzer.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { analyzer.yul_switch_stmt( + arena, loc, condition.clone(), cases.to_vec(), @@ -229,14 +256,14 @@ pub trait YulBuilder: yul_block .statements .iter() - .for_each(|stmt| analyzer.parse_ctx_yul_stmt_inner(stmt, ctx)); + .for_each(|stmt| analyzer.parse_ctx_yul_stmt_inner(arena, stmt, ctx)); Ok(()) } FunctionDefinition(yul_func_def) => Err(ExprErr::Todo( yul_func_def.loc(), "Yul `function` defintions are not currently supported".to_string(), )), - FunctionCall(yul_func_call) => analyzer.yul_func_call(yul_func_call, ctx), + FunctionCall(yul_func_call) => analyzer.yul_func_call(arena, yul_func_call, ctx), Error(loc) => Err(ExprErr::ParseError( *loc, "Could not parse this yul statement".to_string(), @@ -250,6 +277,7 @@ pub trait YulBuilder: /// Parse a yul expression fn parse_ctx_yul_expr( &mut self, + arena: &mut RangeArena>, expr: &YulExpression, ctx: ContextNode, ) -> Result<(), ExprErr> { @@ -257,11 +285,11 @@ pub trait YulBuilder: let edges = ctx.live_edges(self).into_expr_err(expr.loc())?; if edges.is_empty() { - self.parse_ctx_yul_expr_inner(expr, ctx) + self.parse_ctx_yul_expr_inner(arena, expr, ctx) } else { edges .iter() - .try_for_each(|fork_ctx| self.parse_ctx_yul_expr(expr, *fork_ctx))?; + .try_for_each(|fork_ctx| self.parse_ctx_yul_expr(arena, expr, *fork_ctx))?; Ok(()) } } @@ -269,6 +297,7 @@ pub trait YulBuilder: /// After performing some setup in `parse_ctx_yul_expr`, actually parse the yul expression fn parse_ctx_yul_expr_inner( &mut self, + arena: &mut RangeArena>, expr: &YulExpression, ctx: ContextNode, ) -> Result<(), ExprErr> { @@ -282,8 +311,8 @@ pub trait YulBuilder: HexStringLiteral(lit, _) => self.hex_literals(ctx, &[lit.clone()]), StringLiteral(lit, _) => self.string_literal(ctx, lit.loc, &lit.string), Variable(ident) => { - self.variable(ident, ctx, None)?; - self.apply_to_edges(ctx, ident.loc, &|analyzer, edge_ctx, loc| { + self.variable(arena, ident, ctx, None)?; + self.apply_to_edges(ctx, ident.loc, arena, &|analyzer, arena, edge_ctx, loc| { if let Some(ret) = edge_ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? { if ContextVarNode::from(ret.expect_single().into_expr_err(loc)?) .is_memory(analyzer) @@ -312,11 +341,11 @@ pub trait YulBuilder: } }) } - FunctionCall(yul_func_call) => self.yul_func_call(yul_func_call, ctx), + FunctionCall(yul_func_call) => self.yul_func_call(arena, yul_func_call, ctx), SuffixAccess(loc, yul_member_expr, ident) => { - self.parse_inputs(ctx, *loc, &[*yul_member_expr.clone()])?; + self.parse_inputs(arena, ctx, *loc, &[*yul_member_expr.clone()])?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Ok(Some(lhs)) = ctx.pop_expr_latest(loc, analyzer) else { return Err(ExprErr::NoLhs( loc, diff --git a/crates/solc-expressions/src/yul/yul_cond_op.rs b/crates/solc-expressions/src/yul/yul_cond_op.rs index 9be9fe32..6a63b839 100644 --- a/crates/solc-expressions/src/yul/yul_cond_op.rs +++ b/crates/solc-expressions/src/yul/yul_cond_op.rs @@ -5,7 +5,7 @@ use graph::{ nodes::{Concrete, ConcreteNode, Context, ContextNode, ContextVar, ContextVarNode, ExprRet}, AnalyzerBackend, ContextEdge, Edge, Node, }; -use shared::NodeIdx; +use shared::{NodeIdx, RangeArena}; use ethers_core::types::U256; use solang_parser::pt::{ @@ -26,12 +26,13 @@ pub trait YulCondOp: /// Handle a yul conditional operation statement fn yul_cond_op_stmt( &mut self, + arena: &mut RangeArena>, loc: Loc, if_expr: &YulExpression, true_stmt: &YulBlock, ctx: ContextNode, ) -> Result<(), ExprErr> { - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let tctx = Context::new_subctx(ctx, None, loc, Some("true"), None, false, analyzer, None) .into_expr_err(loc)?; @@ -61,8 +62,8 @@ pub trait YulCondOp: Edge::Context(ContextEdge::Subcontext), ); - analyzer.parse_ctx_yul_expr(if_expr, true_subctx)?; - analyzer.apply_to_edges(true_subctx, loc, &|analyzer, ctx, loc| { + analyzer.parse_ctx_yul_expr(arena, if_expr, true_subctx)?; + analyzer.apply_to_edges(true_subctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( loc, @@ -75,10 +76,14 @@ pub trait YulCondOp: return Ok(()); } - analyzer.match_yul_true(ctx, if_expr.loc(), &ret) + analyzer.match_yul_true(arena, ctx, if_expr.loc(), &ret) })?; - analyzer.parse_ctx_yul_statement(&YulStatement::Block(true_stmt.clone()), true_subctx); + analyzer.parse_ctx_yul_statement( + arena, + &YulStatement::Block(true_stmt.clone()), + true_subctx, + ); // let false_expr = YulExpression::FunctionCall(Box::new(YulFunctionCall { // loc, // id: Identifier { @@ -87,8 +92,8 @@ pub trait YulCondOp: // }, // arguments: vec![if_expr.clone()], // })); - analyzer.parse_ctx_yul_expr(if_expr, false_subctx)?; - analyzer.apply_to_edges(false_subctx, loc, &|analyzer, ctx, loc| { + analyzer.parse_ctx_yul_expr(arena, if_expr, false_subctx)?; + analyzer.apply_to_edges(false_subctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( loc, @@ -101,7 +106,7 @@ pub trait YulCondOp: return Ok(()); } - analyzer.match_yul_false(ctx, if_expr.loc(), &ret) + analyzer.match_yul_false(arena, ctx, if_expr.loc(), &ret) }) }) } @@ -110,11 +115,12 @@ pub trait YulCondOp: /// Handle a yul if-else fn yul_if_else( &mut self, + arena: &mut RangeArena>, loc: Loc, if_else_chain: &IfElseChain, ctx: ContextNode, ) -> Result<(), ExprErr> { - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let tctx = Context::new_subctx(ctx, None, loc, Some("true"), None, false, analyzer, None) .into_expr_err(loc)?; @@ -145,42 +151,52 @@ pub trait YulCondOp: ); let if_expr_loc = if_else_chain.if_expr.loc(); - analyzer.apply_to_edges(true_subctx, if_expr_loc, &|analyzer, ctx, loc| { - analyzer.parse_ctx_yul_expr(&if_else_chain.if_expr, true_subctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, _loc| { - let Some(true_vars) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "Yul switch statement was missing a case discriminator".to_string(), - )); - }; + analyzer.apply_to_edges( + true_subctx, + if_expr_loc, + arena, + &|analyzer, arena, ctx, loc| { + analyzer.parse_ctx_yul_expr(arena, &if_else_chain.if_expr, true_subctx)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, _loc| { + let Some(true_vars) = + ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + else { + return Err(ExprErr::NoRhs( + loc, + "Yul switch statement was missing a case discriminator".to_string(), + )); + }; - if matches!(true_vars, ExprRet::CtxKilled(_)) { - ctx.push_expr(true_vars, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.match_yul_true(ctx, loc, &true_vars)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, _loc| { - analyzer.parse_ctx_yul_statement(&if_else_chain.true_stmt, ctx); - Ok(()) + if matches!(true_vars, ExprRet::CtxKilled(_)) { + ctx.push_expr(true_vars, analyzer).into_expr_err(loc)?; + return Ok(()); + } + analyzer.match_yul_true(arena, ctx, loc, &true_vars)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, _loc| { + analyzer.parse_ctx_yul_statement(arena, &if_else_chain.true_stmt, ctx); + Ok(()) + }) }) - }) - })?; + }, + )?; if let Some(next) = &if_else_chain.next { match next { - ElseOrDefault::Default(default) => { - analyzer.apply_to_edges(false_subctx, loc, &|analyzer, ctx, _loc| { - analyzer.parse_ctx_yul_statement(default, ctx); + ElseOrDefault::Default(default) => analyzer.apply_to_edges( + false_subctx, + loc, + arena, + &|analyzer, arena, ctx, _loc| { + analyzer.parse_ctx_yul_statement(arena, default, ctx); Ok(()) - }) - } - ElseOrDefault::Else(iec) => { - analyzer.apply_to_edges(false_subctx, loc, &|analyzer, ctx, loc| { - analyzer.yul_if_else(loc, iec, ctx) - }) - } + }, + ), + ElseOrDefault::Else(iec) => analyzer.apply_to_edges( + false_subctx, + loc, + arena, + &|analyzer, arena, ctx, loc| analyzer.yul_if_else(arena, loc, iec, ctx), + ), } } else { Ok(()) @@ -191,6 +207,7 @@ pub trait YulCondOp: /// Helper for the `true` evaluation of a yul conditional fn match_yul_true( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, true_cvars: &ExprRet, @@ -209,6 +226,7 @@ pub trait YulCondOp: ExprRet::Single(ContextVarNode::from(self.add_node(tmp_true)).into()); self.handle_require_inner( + arena, ctx, loc, true_cvars, @@ -224,7 +242,7 @@ pub trait YulCondOp: true_paths .iter() .take(1) - .try_for_each(|expr_ret| self.match_yul_true(ctx, loc, expr_ret))?; + .try_for_each(|expr_ret| self.match_yul_true(arena, ctx, loc, expr_ret))?; } ExprRet::Null => {} } @@ -234,6 +252,7 @@ pub trait YulCondOp: /// Helper for the `false` evaluation of a yul conditional fn match_yul_false( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, false_cvars: &ExprRet, @@ -252,6 +271,7 @@ pub trait YulCondOp: ExprRet::Single(ContextVarNode::from(self.add_node(tmp_true)).into()); self.handle_require_inner( + arena, ctx, loc, false_cvars, @@ -267,7 +287,7 @@ pub trait YulCondOp: false_paths .iter() .take(1) - .try_for_each(|expr_ret| self.match_yul_false(ctx, loc, expr_ret))?; + .try_for_each(|expr_ret| self.match_yul_false(arena, ctx, loc, expr_ret))?; } ExprRet::Null => {} } @@ -279,6 +299,7 @@ pub trait YulCondOp: /// Handle a yul swithc statement by converting it into an if-else chain fn yul_switch_stmt( &mut self, + arena: &mut RangeArena>, loc: Loc, condition: YulExpression, cases: Vec, @@ -286,8 +307,8 @@ pub trait YulCondOp: ctx: ContextNode, ) -> Result<(), ExprErr> { let iec = IfElseChain::from(loc, (condition, cases, default))?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, _loc| { - analyzer.yul_if_else(loc, &iec, ctx) + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, _loc| { + analyzer.yul_if_else(arena, loc, &iec, ctx) }) } } diff --git a/crates/solc-expressions/src/yul/yul_funcs.rs b/crates/solc-expressions/src/yul/yul_funcs.rs index d618174e..6702fb51 100644 --- a/crates/solc-expressions/src/yul/yul_funcs.rs +++ b/crates/solc-expressions/src/yul/yul_funcs.rs @@ -11,7 +11,7 @@ use graph::{ }, AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node, SolcRange, VarType, }; -use shared::StorageLocation; +use shared::{RangeArena, StorageLocation}; use ethers_core::types::U256; use solang_parser::pt::{Expression, Loc, YulExpression, YulFunctionCall}; @@ -28,6 +28,7 @@ pub trait YulFuncCaller: { fn yul_func_call( &mut self, + arena: &mut RangeArena>, func_call: &YulFunctionCall, ctx: ContextNode, ) -> Result<(), ExprErr> { @@ -68,8 +69,8 @@ pub trait YulFuncCaller: ctx.kill(self, *loc, KilledKind::Revert).into_expr_err(*loc) } "return" => { - self.parse_ctx_yul_expr(&arguments[0], ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_yul_expr(arena, &arguments[0], ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(offset) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs(loc, "Yul Return had no offset".to_string())); @@ -78,8 +79,8 @@ pub trait YulFuncCaller: ctx.push_expr(offset, analyzer).into_expr_err(loc)?; return Ok(()); } - analyzer.parse_ctx_yul_expr(&arguments[1], ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.parse_ctx_yul_expr(arena, &arguments[1], ctx)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(size) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs(loc, "Yul Return had no size".to_string())); @@ -108,8 +109,8 @@ pub trait YulFuncCaller: )); } - self.parse_ctx_yul_expr(&arguments[0], ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_yul_expr(arena, &arguments[0], ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( loc, @@ -121,7 +122,7 @@ pub trait YulFuncCaller: ctx.push_expr(lhs, analyzer).into_expr_err(loc)?; return Ok(()); } - analyzer.bit_not_inner(ctx, loc, lhs.flatten()) + analyzer.bit_not_inner(arena, ctx, loc, lhs.flatten()) }) } "add" | "sub" | "mul" | "div" | "sdiv" | "mod" | "smod" | "exp" | "and" | "or" @@ -159,8 +160,8 @@ pub trait YulFuncCaller: vec![arguments[0].clone(), arguments[1].clone()] }; - self.parse_inputs(ctx, *loc, &inputs)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_inputs(arena, ctx, *loc, &inputs)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(inputs) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -182,16 +183,17 @@ pub trait YulFuncCaller: let lhs_paths = ContextVarNode::from(inputs[0].expect_single().into_expr_err(loc)?); lhs_paths - .cast_from_ty(cast_ty.clone(), analyzer) + .cast_from_ty(cast_ty.clone(), analyzer, arena) .into_expr_err(loc)?; let rhs_paths = ContextVarNode::from(inputs[1].expect_single().into_expr_err(loc)?); rhs_paths - .cast_from_ty(cast_ty, analyzer) + .cast_from_ty(cast_ty, analyzer, arena) .into_expr_err(loc)?; analyzer.op_match( + arena, ctx, loc, &ExprRet::Single(lhs_paths.latest_version(analyzer).into()), @@ -220,8 +222,8 @@ pub trait YulFuncCaller: )); } - self.parse_ctx_yul_expr(&arguments[0], ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_yul_expr(arena, &arguments[0], ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -235,8 +237,8 @@ pub trait YulFuncCaller: return Ok(()); } - analyzer.parse_ctx_yul_expr(&arguments[1], ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + analyzer.parse_ctx_yul_expr(arena, &arguments[1], ctx)?; + analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { @@ -250,7 +252,7 @@ pub trait YulFuncCaller: ctx.push_expr(rhs_paths, analyzer).into_expr_err(loc)?; return Ok(()); } - analyzer.cmp_inner(ctx, loc, &lhs_paths, op, &rhs_paths)?; + analyzer.cmp_inner(arena, ctx, loc, &lhs_paths, op, &rhs_paths)?; let Some(result) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( @@ -267,9 +269,10 @@ pub trait YulFuncCaller: Elem::from(Concrete::Uint(256, U256::zero())), )); - next.set_range_min(analyzer, expr.clone()) + next.set_range_min(analyzer, arena, expr.clone()) + .into_expr_err(loc)?; + next.set_range_max(analyzer, arena, expr) .into_expr_err(loc)?; - next.set_range_max(analyzer, expr).into_expr_err(loc)?; ctx.push_expr(ExprRet::Single(next.into()), analyzer) .into_expr_err(loc) }) @@ -286,8 +289,8 @@ pub trait YulFuncCaller: )); } - self.parse_ctx_yul_expr(&arguments[0], ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_yul_expr(arena, &arguments[0], ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -310,7 +313,7 @@ pub trait YulFuncCaller: let rhs_paths = ExprRet::Single(ContextVarNode::from(analyzer.add_node(tmp_true)).into()); - analyzer.cmp_inner(ctx, loc, &lhs_paths, RangeOp::Eq, &rhs_paths) + analyzer.cmp_inner(arena, ctx, loc, &lhs_paths, RangeOp::Eq, &rhs_paths) }) } "addmod" | "mulmod" => { @@ -357,8 +360,8 @@ pub trait YulFuncCaller: )); } - self.parse_ctx_yul_expr(&arguments[0], ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_yul_expr(arena, &arguments[0], ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -449,9 +452,13 @@ pub trait YulFuncCaller: let res = latest_var.ty(self).into_expr_err(*loc)?; if let Some(r) = res.default_range(self).unwrap() { let new_var = self.advance_var_in_ctx(latest_var, *loc, ctx).unwrap(); - let res = new_var.set_range_min(self, r.min).into_expr_err(*loc); + let res = new_var + .set_range_min(self, arena, r.min) + .into_expr_err(*loc); let _ = self.add_if_err(res); - let res = new_var.set_range_max(self, r.max).into_expr_err(*loc); + let res = new_var + .set_range_max(self, arena, r.max) + .into_expr_err(*loc); let _ = self.add_if_err(res); } } @@ -473,8 +480,8 @@ pub trait YulFuncCaller: )); } - self.parse_inputs(ctx, *loc, arguments)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_inputs(arena, ctx, *loc, arguments)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(mut lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { @@ -496,6 +503,7 @@ pub trait YulFuncCaller: if let Some(slot) = cvar.slot_to_storage(analyzer) { analyzer.match_assign_sides( + arena, ctx, loc, &ExprRet::Single(slot.into()), @@ -515,11 +523,13 @@ pub trait YulFuncCaller: if let Some(r) = res.default_range(analyzer).unwrap() { let new_var = analyzer.advance_var_in_ctx(latest_var, loc, ctx).unwrap(); - let res = - new_var.set_range_min(analyzer, r.min).into_expr_err(loc); + let res = new_var + .set_range_min(analyzer, arena, r.min) + .into_expr_err(loc); let _ = analyzer.add_if_err(res); - let res = - new_var.set_range_max(analyzer, r.max).into_expr_err(loc); + let res = new_var + .set_range_max(analyzer, arena, r.max) + .into_expr_err(loc); let _ = analyzer.add_if_err(res); } } @@ -533,8 +543,8 @@ pub trait YulFuncCaller: Ok(()) } "balance" => { - self.parse_ctx_yul_expr(&arguments[0], ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_yul_expr(arena, &arguments[0], ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -581,8 +591,8 @@ pub trait YulFuncCaller: .into_expr_err(*loc) } "extcodesize" => { - self.parse_ctx_yul_expr(&arguments[0], ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_yul_expr(arena, &arguments[0], ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -630,8 +640,8 @@ pub trait YulFuncCaller: )); } - self.parse_inputs(ctx, *loc, arguments)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_inputs(arena, ctx, *loc, arguments)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(_lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -655,8 +665,8 @@ pub trait YulFuncCaller: )); } - self.parse_inputs(ctx, *loc, arguments)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_inputs(arena, ctx, *loc, arguments)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(_lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -669,8 +679,8 @@ pub trait YulFuncCaller: }) } "extcodehash" => { - self.parse_ctx_yul_expr(&arguments[0], ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + self.parse_ctx_yul_expr(arena, &arguments[0], ctx)?; + self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoRhs( @@ -752,6 +762,7 @@ pub trait YulFuncCaller: #[tracing::instrument(level = "trace", skip_all)] fn parse_inputs( &mut self, + arena: &mut RangeArena>, ctx: ContextNode, loc: Loc, inputs: &[YulExpression], @@ -763,8 +774,8 @@ pub trait YulFuncCaller: }; inputs.iter().try_for_each(|input| { - self.parse_ctx_yul_expr(input, ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.parse_ctx_yul_expr(arena, input, ctx)?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( loc, @@ -784,7 +795,7 @@ pub trait YulFuncCaller: }) })?; if !inputs.is_empty() { - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { let Some(ret) = ctx.pop_tmp_expr(loc, analyzer).into_expr_err(loc)? else { return Err(ExprErr::NoLhs( loc,