Skip to content

Commit 617f127

Browse files
Overload (and other ambiguities) resolution and many fixes in the new binder (#1426)
This PR adds functionality to resolve function overloads and many other ambiguities that were not possible to resolve with the stack graph binder. It also fixes a lot of issues found via running the new binder against contracts from sourcify (see #1419) as well as comparing the bindings output by `solc`. The resulting binding information should be much better than that obtained by the stack graph binder. Notably: - function overloads are resolved by matching the type (and count) of arguments - `super` calls should resolve considering the current contract as the target - `using` directives consider the full type to which they apply (previously eg. function types were not discernible) - full and correct public getters types are created for complex data types - all built-ins should type correctly, even in tricky cases such as `.push()` for arrays - `abi.decode` can now be typed correctly We're still not doing full validation, so the binder may return incorrect results eg. for cases with no ambiguity but a type mismatch. But the valid Solidity code, the expectation is that the new binder is correct. A lot of new snapshot cases are added in this PR, mostly to test specific functionality or cases that the new binder handles. In many of these, the old binder will produce incorrect output, so the diff reports are expected to grow in number significantly. --------- Co-authored-by: Beta Ziliani <beta@manas.tech>
1 parent b71eaa9 commit 617f127

File tree

338 files changed

+17631
-3413
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

338 files changed

+17631
-3413
lines changed

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/solidity/outputs/cargo/crate/src/backend/binder/definitions.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,20 @@ pub struct EventDefinition {
7474
pub parameters_scope_id: ScopeId,
7575
}
7676

77+
#[derive(Debug, Eq, PartialEq)]
78+
pub enum FunctionVisibility {
79+
External,
80+
Internal,
81+
Private,
82+
Public,
83+
}
84+
7785
#[derive(Debug)]
7886
pub struct FunctionDefinition {
7987
pub node_id: NodeId,
8088
pub identifier: Rc<TerminalNode>,
8189
pub parameters_scope_id: ScopeId,
90+
pub visibility: FunctionVisibility,
8291
}
8392

8493
#[derive(Debug)]
@@ -121,11 +130,19 @@ pub struct ParameterDefinition {
121130
pub identifier: Rc<TerminalNode>,
122131
}
123132

133+
#[derive(Debug, Eq, PartialEq)]
134+
pub enum StateVariableVisibility {
135+
Internal,
136+
Private,
137+
Public,
138+
}
139+
124140
#[derive(Debug)]
125141
pub struct StateVariableDefinition {
126142
pub node_id: NodeId,
127143
pub identifier: Rc<TerminalNode>,
128144
pub getter_type_id: Option<TypeId>,
145+
pub visibility: StateVariableVisibility,
129146
}
130147

131148
#[derive(Debug)]
@@ -240,6 +257,40 @@ impl Definition {
240257
}
241258
}
242259

260+
pub(crate) fn is_private_or_internally_visible(&self) -> bool {
261+
if let Self::Function(function_definition) = self {
262+
function_definition.visibility != FunctionVisibility::External
263+
} else {
264+
true
265+
}
266+
}
267+
268+
pub(crate) fn is_internally_visible(&self) -> bool {
269+
match self {
270+
Self::Function(function_definition) => {
271+
function_definition.visibility == FunctionVisibility::Internal
272+
|| function_definition.visibility == FunctionVisibility::Public
273+
}
274+
Self::StateVariable(variable_definition) => {
275+
variable_definition.visibility != StateVariableVisibility::Private
276+
}
277+
_ => true,
278+
}
279+
}
280+
281+
pub(crate) fn is_externally_visible(&self) -> bool {
282+
match self {
283+
Self::Function(function_definition) => {
284+
function_definition.visibility == FunctionVisibility::Public
285+
|| function_definition.visibility == FunctionVisibility::External
286+
}
287+
Self::StateVariable(variable_definition) => {
288+
variable_definition.visibility == StateVariableVisibility::Public
289+
}
290+
_ => true,
291+
}
292+
}
293+
243294
pub(crate) fn new_constant(node_id: NodeId, identifier: &Rc<TerminalNode>) -> Self {
244295
Self::Constant(ConstantDefinition {
245296
node_id,
@@ -298,11 +349,13 @@ impl Definition {
298349
node_id: NodeId,
299350
identifier: &Rc<TerminalNode>,
300351
parameters_scope_id: ScopeId,
352+
visibility: FunctionVisibility,
301353
) -> Self {
302354
Self::Function(FunctionDefinition {
303355
node_id,
304356
identifier: Rc::clone(identifier),
305357
parameters_scope_id,
358+
visibility,
306359
})
307360
}
308361

@@ -361,11 +414,16 @@ impl Definition {
361414
})
362415
}
363416

364-
pub(crate) fn new_state_variable(node_id: NodeId, identifier: &Rc<TerminalNode>) -> Self {
417+
pub(crate) fn new_state_variable(
418+
node_id: NodeId,
419+
identifier: &Rc<TerminalNode>,
420+
visibility: StateVariableVisibility,
421+
) -> Self {
365422
Self::StateVariable(StateVariableDefinition {
366423
node_id,
367424
identifier: Rc::clone(identifier),
368425
getter_type_id: None,
426+
visibility,
369427
})
370428
}
371429

crates/solidity/outputs/cargo/crate/src/backend/binder/mod.rs

Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ mod scopes;
1010

1111
pub use definitions::Definition;
1212
pub(crate) use definitions::{
13-
ContractDefinition, ImportDefinition, InterfaceDefinition, LibraryDefinition,
13+
ContractDefinition, FunctionVisibility, ImportDefinition, InterfaceDefinition,
14+
LibraryDefinition, StateVariableVisibility,
1415
};
1516
pub use references::{Reference, Resolution};
1617
use scopes::ContractScope;
17-
pub(crate) use scopes::{EitherIter, FileScope, Scope, UsingDirective};
18+
pub(crate) use scopes::{
19+
EitherIter, FileScope, ParameterDefinition, ParametersScope, Scope, UsingDirective,
20+
};
1821

1922
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2023
pub struct ScopeId(usize);
@@ -108,14 +111,21 @@ pub struct Binder {
108111
linearisations: HashMap<NodeId, Vec<NodeId>>,
109112
}
110113

111-
// This controls how to use the linearisation when performing a contract scope
112-
// resolution. `Normal` uses all the linearised bases in order; `This(node_id)`
113-
// starts at `node_id` and proceeds in order; `Super(node_id)` starts at the
114-
// contract _following_ `node_id` (ie. its parent)
114+
/// This controls visibility filtering and how to use the linearisation when
115+
/// performing a contract scope resolution.
115116
#[derive(Clone, Copy, Eq, PartialEq)]
116117
pub(crate) enum ResolveOptions {
117-
Normal,
118+
/// Use all the linearised bases in order for an internal lookup.
119+
Internal,
120+
/// Use all the linearised bases for an external lookup.
121+
External,
122+
/// Use all bases and only excludes private members in bases types (used for
123+
/// general qualified member access).
124+
Qualified,
125+
/// Starts at `node_id` and proceeds in order for an external lookup.
118126
This(NodeId),
127+
/// Starts at the contract _following_ `node_id` (ie. its parent) for an
128+
/// internal lookup.
119129
Super(NodeId),
120130
}
121131

@@ -212,6 +222,11 @@ impl Binder {
212222
}
213223
}
214224

225+
#[cfg(feature = "__private_testing_utils")]
226+
pub(crate) fn get_linearised_bases(&self, node_id: NodeId) -> Option<&Vec<NodeId>> {
227+
self.linearisations.get(&node_id)
228+
}
229+
215230
#[cfg(feature = "__private_testing_utils")]
216231
pub fn linearisations(&self) -> &HashMap<NodeId, Vec<NodeId>> {
217232
&self.linearisations
@@ -222,6 +237,11 @@ impl Binder {
222237
self.references.insert(node_id, reference);
223238
}
224239

240+
pub(crate) fn fixup_reference(&mut self, node_id: NodeId, resolution: Resolution) {
241+
let reference = self.references.get_mut(&node_id).unwrap();
242+
reference.resolution = resolution;
243+
}
244+
225245
pub fn find_reference_by_identifier_node_id(&self, node_id: NodeId) -> Option<&Reference> {
226246
self.references.get(&node_id)
227247
}
@@ -300,6 +320,14 @@ impl Binder {
300320
}
301321
}
302322

323+
pub(crate) fn fixup_node_typing(&mut self, node_id: NodeId, typing: Typing) {
324+
assert!(
325+
self.node_typing.contains_key(&node_id),
326+
"typing information for node {node_id:?} not set"
327+
);
328+
self.node_typing.insert(node_id, typing);
329+
}
330+
303331
// File scope resolution context
304332

305333
fn get_file_scope(&self, file_id: &str) -> &FileScope {
@@ -344,7 +372,9 @@ impl Binder {
344372
// Search for the symbol in the linearised bases *in-order*.
345373
if let Some(linearisations) = self.linearisations.get(&contract_scope.node_id) {
346374
let linearisations = match options {
347-
ResolveOptions::Normal => linearisations.as_slice(),
375+
ResolveOptions::Internal | ResolveOptions::External | ResolveOptions::Qualified => {
376+
linearisations.as_slice()
377+
}
348378
ResolveOptions::This(node_id) => {
349379
if let Some(index) = linearisations.iter().position(|id| *id == node_id) {
350380
&linearisations[index..]
@@ -361,7 +391,7 @@ impl Binder {
361391
}
362392
};
363393
let mut results = Vec::new();
364-
for node_id in linearisations {
394+
for (index, node_id) in linearisations.iter().enumerate() {
365395
let Some(base_scope_id) = self.scope_id_for_node_id(*node_id) else {
366396
continue;
367397
};
@@ -372,7 +402,30 @@ impl Binder {
372402
let Some(definitions) = base_contract_scope.definitions.get(symbol) else {
373403
continue;
374404
};
375-
results.extend(definitions);
405+
for definition_id in definitions {
406+
let definition = &self.definitions[definition_id];
407+
let visible = match options {
408+
ResolveOptions::Internal => {
409+
if index == 0 {
410+
definition.is_private_or_internally_visible()
411+
} else {
412+
definition.is_internally_visible()
413+
}
414+
}
415+
ResolveOptions::Qualified => {
416+
index == 0
417+
|| definition.is_internally_visible()
418+
|| definition.is_externally_visible()
419+
}
420+
ResolveOptions::This(_) | ResolveOptions::External => {
421+
definition.is_externally_visible()
422+
}
423+
ResolveOptions::Super(_) => definition.is_internally_visible(),
424+
};
425+
if visible {
426+
results.push(*definition_id);
427+
}
428+
}
376429
}
377430
Resolution::from(results)
378431
} else if let Some(definitions) = contract_scope.definitions.get(symbol) {
@@ -395,7 +448,7 @@ impl Binder {
395448
self.resolve_in_contract_scope_internal(
396449
contract_scope,
397450
symbol,
398-
ResolveOptions::Normal,
451+
ResolveOptions::Internal,
399452
)
400453
.or_else(|| {
401454
// Otherwise, delegate to the containing file scope.
@@ -423,7 +476,9 @@ impl Binder {
423476
)
424477
}
425478
}
426-
Scope::Parameters(parameters_scope) => parameters_scope.definitions.get(symbol).into(),
479+
Scope::Parameters(parameters_scope) => {
480+
parameters_scope.lookup_definition(symbol).into()
481+
}
427482
Scope::Struct(struct_scope) => struct_scope.definitions.get(symbol).into(),
428483
Scope::Using(using_scope) => using_scope
429484
.symbols
@@ -524,7 +579,7 @@ impl Binder {
524579
Scope::Contract(contract_scope) => self.resolve_in_contract_scope_internal(
525580
contract_scope,
526581
symbol,
527-
ResolveOptions::Normal,
582+
ResolveOptions::Qualified,
528583
),
529584
Scope::Enum(enum_scope) => enum_scope.definitions.get(symbol).into(),
530585
Scope::Struct(struct_scope) => struct_scope.definitions.get(symbol).into(),
@@ -533,7 +588,9 @@ impl Binder {
533588
.get(symbol)
534589
.cloned()
535590
.map_or(Resolution::Unresolved, Resolution::from),
536-
Scope::Parameters(parameters_scope) => parameters_scope.definitions.get(symbol).into(),
591+
Scope::Parameters(parameters_scope) => {
592+
parameters_scope.lookup_definition(symbol).into()
593+
}
537594

538595
Scope::Block(_)
539596
| Scope::File(_)

0 commit comments

Comments
 (0)