Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Binding rules to support contract inheritance (#1075)
This PR adds binding rules to resolve contract inheritance and virtual method lookups (both direct and via `super`). It does that by introducing a generalized strategy to deal with references that resolve to multiple definitions. It ranks the definitions according to selector tags found in the definitions. Currently implements two ranking algorithms: - A simple one, for aliasing definitions such as import aliases for symbols. In this case, the aliased definition is marked down, prioritizing the actual definitions the aliases point to. - One based on C3 linearisation, that can resolve calls to virtual methods in a hierarchy of contracts (either directly or via `super`). For the second algorithm, a substantial amount of extra infrastructure is added to the stack graph, in the form of extra attributes that the bindings system tracks. The newly introduced graph attributes are: - `tag` to mark definitions and references with a tag to change the behaviour of the ranking algorithm. - `parents` which is applicable to both definitions and references, contain an ordered list of definitions and references to construct a hierarchy. This is relevant in contract definitions, to enumerate the base contracts through their references in the inheritance list, and in function call expressions, to indicate from which contract they are being done. - `export_node` applies to contract definitions and provides a connection point for parent contracts to access virtual members defined in derived contracts. - `imports_node` also applies to contract definitions and provide the analogous connection point used for base contracts. These last two attributes are used in conjunction with a new property of the bindings module `context` which can be used to indicate the resolver what is the current binding context (ie. what contract is being compiled). With virtual methods, the actual definition of a reference can only be correctly resolved knowing the full inheritance hierarchy of the contract being compiled. When setting the context, the bindings module with patch its internal stack graph and create new links between the context contract and all the parents in its hierarchy. These are "reverse" links, in the sense that allow a reference in a base contract to resolve to a definition in a sub-contract down the hierarchy. All this machinery (parent links, context and import/export nodes) provide the necessary information for a reference to resolve to all possible definitions in the compilation contract hierarchy. Then, the C3 linearisation ranking algorithm helps decide which one to choose. In particular, for Solidity: - Contract definitions are linked to their bases via the `parents` attribute. - All contract functions definitions are linked to the contract via the `parents` attribute, and are also `tag`ged with the value `c3`. - Function call references that occur in contract functions are also linked to the enclosing contract via the `parents` attribute. - Super call references are `tag`ged with the value `super`. - All contracts have `export_node` set to its `@contract.members` scope node. - All contracts have `imports_node` set to both its `@contract.lexical_scope` and a `super_import` (which is similar to `@contract.super_scope`) scope nodes. - Import alias definitions are `tag`ged with the value `alias`. Issues remaining: - [x] Generalize the detection of `super` calls. Currently that is done crudely by inspecting the stack graph path from the reference to the definition and searching for a push node with the symbol `super`. - [x] Decide what to return when there are multiple definitions and the ranking algorithm cannot disambiguate them. Currently we panic and crash the program, which should be fine for well-formed Solidity programs. Probably makes sense to return a `Result<Definition, ResolutionError>` instead of an `Option<Definition>`.
- Loading branch information