Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
sbillig committed Nov 24, 2021
1 parent 4fca2e4 commit f22fb29
Show file tree
Hide file tree
Showing 27 changed files with 896 additions and 217 deletions.
21 changes: 19 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/analyzer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ parking_lot_core = { version = "=0.8.0" } # used by salsa; version pinned for wa
indexmap = "1.6.2"
if_chain = "1.0.1"
smallvec = { version = "1.6.1", features = ["union"] }
petgraph = "0.6.0"

[dev-dependencies]
insta = "1.7.1"
Expand Down
8 changes: 6 additions & 2 deletions crates/analyzer/src/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ pub enum ValueMethod {
AbiEncode,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumString, AsRefStr, Hash, EnumIter)]
#[derive(
Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, EnumString, AsRefStr, EnumIter,
)]
#[strum(serialize_all = "snake_case")]
pub enum GlobalFunction {
Keccak256,
Expand All @@ -33,7 +35,9 @@ impl ContractTypeMethod {
}
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, EnumString, EnumIter, AsRefStr)]
#[derive(
Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, EnumString, EnumIter, AsRefStr,
)]
#[strum(serialize_all = "lowercase")]
pub enum GlobalObject {
Block,
Expand Down
12 changes: 9 additions & 3 deletions crates/analyzer/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,10 @@ pub enum CallType {
class: Class,
method: FunctionId,
},
External {
contract: ContractId,
function: FunctionId,
},
Pure(FunctionId),
TypeConstructor(Type),
}
Expand All @@ -292,9 +296,10 @@ impl CallType {
| BuiltinValueMethod(_)
| TypeConstructor(_)
| BuiltinAssociatedFunction { .. } => None,
AssociatedFunction { function: id, .. } | ValueMethod { method: id, .. } | Pure(id) => {
Some(*id)
}
AssociatedFunction { function: id, .. }
| ValueMethod { method: id, .. }
| External { function: id, .. }
| Pure(id) => Some(*id),
}
}

Expand All @@ -306,6 +311,7 @@ impl CallType {

CallType::AssociatedFunction { function: id, .. }
| CallType::ValueMethod { method: id, .. }
| CallType::External { function: id, .. }
| CallType::Pure(id) => id.name(db),
CallType::TypeConstructor(typ) => typ.to_string(),
}
Expand Down
12 changes: 10 additions & 2 deletions crates/analyzer/src/db.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::context::{Analysis, FunctionBody};
use crate::errors::TypeError;
use crate::namespace::items::{
self, ContractFieldId, ContractId, EventId, FunctionId, GlobalId, IngotId, Item,
ModuleConstantId, ModuleId, StructFieldId, StructId, TypeAliasId,
self, ContractFieldId, ContractId, DepGraphWrapper, EventId, FunctionId, GlobalId, IngotId,
Item, ModuleConstantId, ModuleId, StructFieldId, StructId, TypeAliasId,
};
use crate::namespace::types;
use fe_common::Span;
Expand Down Expand Up @@ -119,12 +119,18 @@ pub trait AnalyzerDb {
&self,
field: ContractFieldId,
) -> Analysis<Result<types::Type, TypeError>>;
#[salsa::invoke(queries::contracts::contract_dependency_graph)]
fn contract_dependency_graph(&self, id: ContractId) -> DepGraphWrapper;
#[salsa::invoke(queries::contracts::contract_runtime_dependency_graph)]
fn contract_runtime_dependency_graph(&self, id: ContractId) -> DepGraphWrapper;

// Function
#[salsa::invoke(queries::functions::function_signature)]
fn function_signature(&self, id: FunctionId) -> Analysis<Rc<types::FunctionSignature>>;
#[salsa::invoke(queries::functions::function_body)]
fn function_body(&self, id: FunctionId) -> Analysis<Rc<FunctionBody>>;
#[salsa::invoke(queries::functions::function_dependency_graph)]
fn function_dependency_graph(&self, id: FunctionId) -> DepGraphWrapper;

// Struct
#[salsa::invoke(queries::structs::struct_type)]
Expand All @@ -142,6 +148,8 @@ pub trait AnalyzerDb {
fn struct_all_functions(&self, id: StructId) -> Rc<Vec<FunctionId>>;
#[salsa::invoke(queries::structs::struct_function_map)]
fn struct_function_map(&self, id: StructId) -> Analysis<Rc<IndexMap<String, FunctionId>>>;
#[salsa::invoke(queries::structs::struct_dependency_graph)]
fn struct_dependency_graph(&self, id: StructId) -> DepGraphWrapper;

// Event
#[salsa::invoke(queries::events::event_type)]
Expand Down
66 changes: 63 additions & 3 deletions crates/analyzer/src/db/queries/contracts.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
use crate::context::{AnalyzerContext, NamedThing};
use crate::db::{Analysis, AnalyzerDb};
use crate::errors;
use crate::namespace::items::{self, ContractFieldId, ContractId, EventId, FunctionId, Item};
use crate::namespace::items::{
self, ContractFieldId, ContractId, DepGraph, DepGraphWrapper, DepLocality, EventId, FunctionId,
Item, TypeDef,
};
use crate::namespace::scopes::ItemScope;
use crate::namespace::types;
use crate::namespace::types::{self, Contract, Struct, Type};
use crate::traversal::types::type_desc;
use fe_common::diagnostics::Label;
use fe_parser::ast;
use indexmap::map::{Entry, IndexMap};

use std::rc::Rc;

/// A `Vec` of every function defined in the contract, including duplicates and the init function.
Expand Down Expand Up @@ -277,3 +279,61 @@ pub fn contract_field_type(
diagnostics: Rc::new(scope.diagnostics),
}
}

pub fn contract_dependency_graph(db: &dyn AnalyzerDb, contract: ContractId) -> DepGraphWrapper {
// A contract depends on the types of its fields, and the things those types depend on.
// Note that this *does not* include the contract's public function graph.
// (See `contract_public_fn_dependency_graph` below)

let fields = contract.fields(db);
let field_types = fields
.values()
.filter_map(|field| match field.typ(db).ok()? {
// We don't want
Type::Contract(Contract { id, .. }) => Some(Item::Type(TypeDef::Contract(id))),
Type::Struct(Struct { id, .. }) => Some(Item::Type(TypeDef::Struct(id))),
// TODO: when tuples can contain non-primitive items,
// we'll have to depend on tuple element types
_ => None,
})
.collect::<Vec<_>>();

let root = Item::Type(TypeDef::Contract(contract));
let mut graph = DepGraph::from_edges(
field_types
.iter()
.map(|item| (root, *item, DepLocality::Local)),
);

for item in field_types {
if let Some(subgraph) = item.dependency_graph(db) {
graph.extend(subgraph.all_edges())
}
}
DepGraphWrapper(Rc::new(graph))
}

pub fn contract_runtime_dependency_graph(
db: &dyn AnalyzerDb,
contract: ContractId,
) -> DepGraphWrapper {
// This is the dependency graph of the (as yet imaginary) `__call__` function, which
// dispatches to the contract's public functions. This should be used when compiling
// the runtime object for a contract.

let root = Item::Type(TypeDef::Contract(contract));
let pub_fns = contract
.public_functions(db)
.values()
.map(|fun| (root, Item::Function(*fun), DepLocality::Local))
.collect::<Vec<_>>();

let mut graph = DepGraph::from_edges(pub_fns.iter());

for (_, item, _) in pub_fns {
if let Some(subgraph) = item.dependency_graph(db) {
graph.extend(subgraph.all_edges())
}
}
DepGraphWrapper(Rc::new(graph))
}
118 changes: 114 additions & 4 deletions crates/analyzer/src/db/queries/functions.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use crate::context::{AnalyzerContext, FunctionBody};
use crate::context::{AnalyzerContext, CallType, FunctionBody};
use crate::db::{Analysis, AnalyzerDb};
use crate::errors::TypeError;
use crate::namespace::items::{Class, FunctionId};
use crate::namespace::items::{
Class, DepGraph, DepGraphWrapper, DepLocality, FunctionId, Item, TypeDef,
};
use crate::namespace::scopes::{BlockScope, BlockScopeType, FunctionScope, ItemScope};
use crate::namespace::types::{self, FixedSize, SelfDecl};
use crate::namespace::types::{self, Contract, FixedSize, SelfDecl, Struct, Type};
use crate::traversal::functions::traverse_statements;
use crate::traversal::types::type_desc;
use fe_common::diagnostics::Label;
Expand All @@ -24,7 +26,7 @@ pub fn function_signature(
let def = &node.kind;

let mut scope = ItemScope::new(db, function.module(db));
let fn_parent = function.parent(db);
let fn_parent = function.class(db);

if_chain! {
if let Some(Class::Contract(_)) = fn_parent;
Expand Down Expand Up @@ -215,3 +217,111 @@ fn all_paths_return_or_revert(block: &[Node<ast::FuncStmt>]) -> bool {

false
}

pub fn function_dependency_graph(db: &dyn AnalyzerDb, function: FunctionId) -> DepGraphWrapper {
// XXX when do we want to include Contract types? Just for create/create2?

let root = Item::Function(function);

// Edges to direct dependencies.
let mut directs = vec![];

let sig = function.signature(db);
directs.extend(
sig.return_type
.clone()
.into_iter()
.chain(sig.params.iter().filter_map(|param| param.typ.clone().ok()))
.filter_map(|typ| match typ {
FixedSize::Contract(Contract { id, .. }) => {
// Contract types that are taken as (non-self) args or returned are "external",
// meaning that they're addresses of other contracts, so we don't have direct
// access to their fields, etc.
Some((
root,
Item::Type(TypeDef::Contract(id)),
DepLocality::External,
))
}
FixedSize::Struct(Struct { id, .. }) => {
Some((root, Item::Type(TypeDef::Struct(id)), DepLocality::Local))
}
_ => None,
}),
);
// A function that takes `self` depends on the type of `self`, so that any
// relevant struct getters/setters are included when compiling.
if let Some(class) = function.class(db) {
directs.push((root, class.as_item(), DepLocality::Local));
}

let body = function.body(db);
for calltype in body.calls.values() {
match calltype {
CallType::Pure(function) | CallType::AssociatedFunction { function, .. } => {
directs.push((root, Item::Function(*function), DepLocality::Local));
}
CallType::ValueMethod { class, method, .. } => {
// Including the "class" type here is probably redundant; the type will
// also be part of the fn sig, or some type decl, or some create/create2 call, or...
directs.push((root, class.as_item(), DepLocality::Local));
directs.push((root, Item::Function(*method), DepLocality::Local));
}
CallType::External { contract, function } => {
directs.push((root, Item::Function(*function), DepLocality::External));
// Probably redundant:
directs.push((
root,
Item::Type(TypeDef::Contract(*contract)),
DepLocality::External,
));
}
CallType::TypeConstructor(Type::Struct(Struct { id, .. })) => {
directs.push((root, Item::Type(TypeDef::Struct(*id)), DepLocality::Local));
}
CallType::TypeConstructor(Type::Contract(Contract { id, .. })) => {
directs.push((
root,
Item::Type(TypeDef::Contract(*id)),
DepLocality::External,
));
}
CallType::TypeConstructor(_) => {}
CallType::BuiltinAssociatedFunction { contract, .. } => {
// create/create2 call. The contract type is "external" for dependency graph purposes.
directs.push((
root,
Item::Type(TypeDef::Contract(*contract)),
DepLocality::External,
));
}
// Builtin functions aren't part of the dependency graph yet.
CallType::BuiltinFunction(_) | CallType::BuiltinValueMethod(_) => {}
}
}

directs.extend(
body.emits
.values()
.map(|event| (root, Item::Event(*event), DepLocality::Local)),
);
directs.extend(body.var_decl_types.values().filter_map(|typ| match typ {
FixedSize::Contract(Contract { id, .. }) => Some((
root,
Item::Type(TypeDef::Contract(*id)),
DepLocality::External,
)),
FixedSize::Struct(Struct { id, .. }) => {
Some((root, Item::Type(TypeDef::Struct(*id)), DepLocality::Local))
}
_ => None,
}));

let mut graph = DepGraph::from_edges(directs.iter());
for (_, item, _) in directs {
if let Some(subgraph) = item.dependency_graph(db) {
graph.extend(subgraph.all_edges())
}
}
DepGraphWrapper(Rc::new(graph))
}
Loading

0 comments on commit f22fb29

Please sign in to comment.