Skip to content

Commit

Permalink
Complete built-ins support for Solidity (NomicFoundation#1125)
Browse files Browse the repository at this point in the history
This PR adds support for the remaining built-ins in Solidity plus other
bug fixes and missing binding cases:

* Built-in types (both visible to the user and those used to create
namespace like definitions such as `tx.origin`) are declared by
prefixing them with `$` and renamed by transforming the CST tree and
replacing `$` by `%` before adding them to the stack graph. Because `%`
is not a valid identifier character, this ensures user code cannot
accidentally clash.

**This transformation process is explicit and must be done manually by
the user of the bindings API.** This has a potential drawback: if the
user is given the transformed CST tree, they cannot `unparse()` it and
obtain a valid Solidity file; if the user is given the untransformed CST
tree, the cursors they obtain from the bindings API will not apply
directly to their tree. An alternative is to perform the transformation
inside the rules and only for system (aka built-in) files. That would
restrict the visibility to the stack graph only, at the expense of
performance.

* `type()` expressions bind to an internal
`%typeIntType`/`%typeContractType`/`%typeInterfaceType` which allows
binding members such as `name`, `min`/`max` and `creationCode`.
* Added all global variables, built-in type members and functions and
enable them on the Solidity versions they were introduced (or disable
them if they were removed).
* Make external and public functions in contracts and interfaces
accessible through the enclosing type to be able to retrieve their
`.selector`.
* Arrays push a special scoped symbol `<>` that allows delay-resolving
the element type. We use this to correctly bind `push()` and the `[]`
operator to bind correctly.
* Bind field names when constructing structs using named arguments.
* Call options parameter names are bound to a special `%callOptions`
built-in type.
* The placeholder `_` modifier operator only binds inside a modifier
body.
* Minor optimization and clean up of the graph rules by making the
`lexical_scope` scoped variable inheritable and removing unnecessarily
created intermediate scopes.
* All built-in variables and functions can be shadowed by user defined
variables and functions of the same name.
* Support binding constructor parameters with the old syntax in Solidity
< 0.5.0.
* Support `using X for *` directive.
  • Loading branch information
ggiraldez authored Nov 14, 2024
1 parent 37f1893 commit de9302f
Show file tree
Hide file tree
Showing 124 changed files with 6,888 additions and 520 deletions.
4 changes: 2 additions & 2 deletions crates/metaslang/bindings/src/builder/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ mod resolver {
let path_to_resolve = parameters.param()?.into_string()?;
parameters.finish()?;

let context_file_descriptor = FileDescriptor::from_string(&context_path);
let context_file_descriptor = FileDescriptor::try_from(&context_path);
let Ok(FileDescriptor::User(context_user_path)) = context_file_descriptor else {
// Since the path resolver should only map to user paths from
// user paths, it is an error to attempt to resolve a path in
Expand Down Expand Up @@ -127,7 +127,7 @@ mod resolver {
let file_path = parameters.param()?.into_string()?;
parameters.finish()?;

let Ok(file_descriptor) = FileDescriptor::from_string(&file_path) else {
let Ok(file_descriptor) = FileDescriptor::try_from(&file_path) else {
return Err(ExecutionError::FunctionFailed(
"is-system-file".into(),
"Parameter is not a valid file path".into(),
Expand Down
18 changes: 18 additions & 0 deletions crates/metaslang/bindings/src/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,8 @@ static PRECEDENCE_ATTR: &str = "precedence";
// Global variables
/// Name of the variable used to pass the root node.
pub const ROOT_NODE_VAR: &str = "ROOT_NODE";
/// Name of the variable used to pass the jump to scope node.
pub const JUMP_TO_SCOPE_NODE_VAR: &str = "JUMP_TO_SCOPE_NODE";
/// Name of the variable used to pass the file path.
pub const FILE_PATH_VAR: &str = "FILE_PATH";

Expand Down Expand Up @@ -405,6 +407,11 @@ impl<'a, KT: KindTypes + 'static> Builder<'a, KT> {
.add(ROOT_NODE_VAR.into(), root_node.into())
.expect("Failed to set ROOT_NODE");

let jump_to_scope_node = self.inject_node(NodeID::jump_to());
variables
.add(JUMP_TO_SCOPE_NODE_VAR.into(), jump_to_scope_node.into())
.expect("Failed to set JUMP_TO_SCOPE_NODE");

#[cfg(feature = "__private_testing_utils")]
{
// For debugging purposes only
Expand All @@ -418,6 +425,17 @@ impl<'a, KT: KindTypes + 'static> Builder<'a, KT> {
ROOT_NODE_VAR.to_string(),
)
.expect("Failed to set ROOT_NODE variable name for debugging");

self.graph[jump_to_scope_node]
.attributes
.add(
[DEBUG_ATTR_PREFIX, "msgb_variable"]
.concat()
.as_str()
.into(),
JUMP_TO_SCOPE_NODE_VAR.to_string(),
)
.expect("Failed to set JUMP_TO_SCOPE_NODE variable name for debugging");
}

variables
Expand Down
32 changes: 20 additions & 12 deletions crates/metaslang/bindings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ pub enum FileDescriptor {
System(String),
}

#[derive(Debug)]
pub(crate) struct FileDescriptorError;

impl FileDescriptor {
Expand All @@ -73,14 +74,21 @@ impl FileDescriptor {
}
}

pub(crate) fn from_string(value: &str) -> Result<Self, FileDescriptorError> {
if let Some(path) = value.strip_prefix("user:") {
Ok(FileDescriptor::User(path.into()))
} else if let Some(path) = value.strip_prefix("system:") {
Ok(FileDescriptor::System(path.into()))
} else {
Err(FileDescriptorError)
}
pub(crate) fn try_from(value: &str) -> Result<Self, FileDescriptorError> {
value
.strip_prefix("user:")
.map(|path| FileDescriptor::User(path.into()))
.or_else(|| {
value
.strip_prefix("system:")
.map(|path| FileDescriptor::System(path.into()))
})
.ok_or(FileDescriptorError)
}

pub(crate) fn from(value: &str) -> Self {
Self::try_from(value)
.unwrap_or_else(|_| panic!("{value} should be a valid file descriptor"))
}

pub fn get_path(&self) -> &str {
Expand Down Expand Up @@ -372,8 +380,8 @@ impl<'a, KT: KindTypes + 'static> Definition<'a, KT> {
pub fn get_file(&self) -> FileDescriptor {
self.owner.stack_graph[self.handle]
.file()
.and_then(|file| FileDescriptor::from_string(self.owner.stack_graph[file].name()).ok())
.unwrap_or_else(|| unreachable!("Definition does not have a valid file descriptor"))
.map(|file| FileDescriptor::from(self.owner.stack_graph[file].name()))
.expect("Definition does not have a valid file descriptor")
}

pub(crate) fn has_tag(&self, tag: Tag) -> bool {
Expand Down Expand Up @@ -459,8 +467,8 @@ impl<'a, KT: KindTypes + 'static> Reference<'a, KT> {
pub fn get_file(&self) -> FileDescriptor {
self.owner.stack_graph[self.handle]
.file()
.and_then(|file| FileDescriptor::from_string(self.owner.stack_graph[file].name()).ok())
.unwrap_or_else(|| unreachable!("Reference does not have a valid file descriptor"))
.map(|file| FileDescriptor::from(self.owner.stack_graph[file].name()))
.expect("Reference does not have a valid file descriptor")
}

pub fn jump_to_definition(&self) -> Result<Definition<'a, KT>, ResolutionError<'a, KT>> {
Expand Down
16 changes: 14 additions & 2 deletions crates/metaslang/bindings/src/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ impl<'a, KT: KindTypes + 'static> Resolver<'a, KT> {
return;
}
self.mark_down_aliases();
self.mark_down_built_ins();
self.rank_c3_methods();
self.results.sort_by(|a, b| b.score.total_cmp(&a.score));
}
Expand Down Expand Up @@ -157,6 +158,14 @@ impl<'a, KT: KindTypes + 'static> Resolver<'a, KT> {
}
}

fn mark_down_built_ins(&mut self) {
for result in &mut self.results {
if result.definition.get_file().is_system() {
result.score -= 200.0;
}
}
}

fn rank_c3_methods(&mut self) {
// compute the linearisation to use for ranking
let caller_parents = self.reference.resolve_parents();
Expand Down Expand Up @@ -184,9 +193,12 @@ impl<'a, KT: KindTypes + 'static> Resolver<'a, KT> {
let caller_context_index = mro.iter().position(|x| x == caller_context);
let super_call = self.reference.has_tag(Tag::Super);

// mark up methods tagged C3 according to the computed linearisation
// Mark up user methods tagged C3 according to the computed linearisation.
// Because only contract functions are marked with the C3 tag, this has
// the added benefit of prioritizing them over globally defined
// functions.
for result in &mut self.results {
if result.definition.has_tag(Tag::C3) {
if result.definition.has_tag(Tag::C3) && result.definition.get_file().is_user() {
let definition_parents = result.definition.resolve_parents();
let Some(definition_context) = definition_parents.first() else {
// this should not normally happen: the definition is tagged
Expand Down
Loading

0 comments on commit de9302f

Please sign in to comment.