Improve import alias support (#1133)

* Improve import alias
- Fix #962
- Fix #1106
- Fix #1067
pull/1149/head
Feist Josselin 3 years ago committed by GitHub
parent 61bcec4468
commit 81daa56f66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      slither/core/declarations/import_directive.py
  2. 8
      slither/core/scope/scope.py
  3. 3
      slither/solc_parsing/expressions/find_variable.py
  4. 45
      slither/solc_parsing/slither_compilation_unit_solc.py
  5. 30
      slither/solc_parsing/solidity_types/type_parsing.py
  6. BIN
      tests/ast-parsing/compile/complex_imports/import_aliases/test.sol-0.8.0-compact.zip
  7. BIN
      tests/ast-parsing/compile/complex_imports/import_aliases/test.sol-0.8.1-compact.zip
  8. BIN
      tests/ast-parsing/compile/complex_imports/import_aliases/test.sol-0.8.10-compact.zip
  9. BIN
      tests/ast-parsing/compile/complex_imports/import_aliases/test.sol-0.8.11-compact.zip
  10. BIN
      tests/ast-parsing/compile/complex_imports/import_aliases/test.sol-0.8.12-compact.zip
  11. BIN
      tests/ast-parsing/compile/complex_imports/import_aliases/test.sol-0.8.2-compact.zip
  12. BIN
      tests/ast-parsing/compile/complex_imports/import_aliases/test.sol-0.8.3-compact.zip
  13. BIN
      tests/ast-parsing/compile/complex_imports/import_aliases/test.sol-0.8.4-compact.zip
  14. BIN
      tests/ast-parsing/compile/complex_imports/import_aliases/test.sol-0.8.5-compact.zip
  15. BIN
      tests/ast-parsing/compile/complex_imports/import_aliases/test.sol-0.8.6-compact.zip
  16. BIN
      tests/ast-parsing/compile/complex_imports/import_aliases/test.sol-0.8.7-compact.zip
  17. BIN
      tests/ast-parsing/compile/complex_imports/import_aliases/test.sol-0.8.8-compact.zip
  18. BIN
      tests/ast-parsing/compile/complex_imports/import_aliases/test.sol-0.8.9-compact.zip
  19. 14
      tests/ast-parsing/complex_imports/import_aliases/import.sol
  20. 10
      tests/ast-parsing/complex_imports/import_aliases/test.sol
  21. 7
      tests/ast-parsing/expected/complex_imports/import_aliases/test.sol-0.8.0-compact.json
  22. 7
      tests/ast-parsing/expected/complex_imports/import_aliases/test.sol-0.8.1-compact.json
  23. 7
      tests/ast-parsing/expected/complex_imports/import_aliases/test.sol-0.8.10-compact.json
  24. 7
      tests/ast-parsing/expected/complex_imports/import_aliases/test.sol-0.8.11-compact.json
  25. 7
      tests/ast-parsing/expected/complex_imports/import_aliases/test.sol-0.8.12-compact.json
  26. 7
      tests/ast-parsing/expected/complex_imports/import_aliases/test.sol-0.8.2-compact.json
  27. 7
      tests/ast-parsing/expected/complex_imports/import_aliases/test.sol-0.8.3-compact.json
  28. 7
      tests/ast-parsing/expected/complex_imports/import_aliases/test.sol-0.8.4-compact.json
  29. 7
      tests/ast-parsing/expected/complex_imports/import_aliases/test.sol-0.8.5-compact.json
  30. 7
      tests/ast-parsing/expected/complex_imports/import_aliases/test.sol-0.8.6-compact.json
  31. 7
      tests/ast-parsing/expected/complex_imports/import_aliases/test.sol-0.8.7-compact.json
  32. 7
      tests/ast-parsing/expected/complex_imports/import_aliases/test.sol-0.8.8-compact.json
  33. 7
      tests/ast-parsing/expected/complex_imports/import_aliases/test.sol-0.8.9-compact.json
  34. 1
      tests/test_ast_parsing.py

@ -1,5 +1,5 @@
from pathlib import Path from pathlib import Path
from typing import Optional, TYPE_CHECKING from typing import Optional, TYPE_CHECKING, Dict
from slither.core.source_mapping.source_mapping import SourceMapping from slither.core.source_mapping.source_mapping import SourceMapping
@ -13,6 +13,8 @@ class Import(SourceMapping):
self._filename: Path = filename self._filename: Path = filename
self._alias: Optional[str] = None self._alias: Optional[str] = None
self.scope: "FileScope" = scope self.scope: "FileScope" = scope
# Map local name -> original name
self.renaming: Dict[str, str] = {}
@property @property
def filename(self) -> str: def filename(self) -> str:

@ -39,6 +39,11 @@ class FileScope:
self.structures: Dict[str, StructureTopLevel] = {} self.structures: Dict[str, StructureTopLevel] = {}
self.variables: Dict[str, TopLevelVariable] = {} self.variables: Dict[str, TopLevelVariable] = {}
# Renamed created by import
# import A as B
# local name -> original name (A -> B)
self.renaming: Dict[str, str] = {}
def add_accesible_scopes(self) -> bool: def add_accesible_scopes(self) -> bool:
""" """
Add information from accessible scopes. Return true if new information was obtained Add information from accessible scopes. Return true if new information was obtained
@ -74,6 +79,9 @@ class FileScope:
if not _dict_contain(new_scope.variables, self.variables): if not _dict_contain(new_scope.variables, self.variables):
self.variables.update(new_scope.variables) self.variables.update(new_scope.variables)
learn_something = True learn_something = True
if not _dict_contain(new_scope.renaming, self.renaming):
self.renaming.update(new_scope.renaming)
learn_something = True
return learn_something return learn_something

@ -334,6 +334,9 @@ def find_variable(
# Because functions are copied between contracts, two functions can have the same ref # Because functions are copied between contracts, two functions can have the same ref
# So we need to first look with respect to the direct context # So we need to first look with respect to the direct context
if var_name in current_scope.renaming:
var_name = current_scope.renaming[var_name]
# Use ret0/ret1 to help mypy # Use ret0/ret1 to help mypy
ret0 = _find_variable_from_ref_declaration( ret0 = _find_variable_from_ref_declaration(
referenced_declaration, direct_contracts, direct_functions referenced_declaration, direct_contracts, direct_functions

@ -14,6 +14,7 @@ from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.import_directive import Import from slither.core.declarations.import_directive import Import
from slither.core.declarations.pragma_directive import Pragma from slither.core.declarations.pragma_directive import Pragma
from slither.core.declarations.structure_top_level import StructureTopLevel from slither.core.declarations.structure_top_level import StructureTopLevel
from slither.core.scope.scope import FileScope
from slither.core.variables.top_level_variable import TopLevelVariable from slither.core.variables.top_level_variable import TopLevelVariable
from slither.exceptions import SlitherException from slither.exceptions import SlitherException
from slither.solc_parsing.declarations.contract import ContractSolc from slither.solc_parsing.declarations.contract import ContractSolc
@ -28,6 +29,33 @@ logger = logging.getLogger("SlitherSolcParsing")
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
def _handle_import_aliases(
symbol_aliases: Dict, import_directive: Import, scope: FileScope
) -> None:
"""
Handle the parsing of import aliases
Args:
symbol_aliases (Dict): json dict from solc
import_directive (Import): current import directive
scope (FileScope): current file scape
Returns:
"""
for symbol_alias in symbol_aliases:
if (
"foreign" in symbol_alias
and "name" in symbol_alias["foreign"]
and "local" in symbol_alias
):
original_name = symbol_alias["foreign"]["name"]
local_name = symbol_alias["local"]
import_directive.renaming[local_name] = original_name
# Assuming that two imports cannot collide in renaming
scope.renaming[local_name] = original_name
class SlitherCompilationUnitSolc: class SlitherCompilationUnitSolc:
# pylint: disable=no-self-use,too-many-instance-attributes # pylint: disable=no-self-use,too-many-instance-attributes
def __init__(self, compilation_unit: SlitherCompilationUnit): def __init__(self, compilation_unit: SlitherCompilationUnit):
@ -204,6 +232,9 @@ class SlitherCompilationUnitSolc:
# TODO investigate unitAlias in version < 0.7 and legacy ast # TODO investigate unitAlias in version < 0.7 and legacy ast
if "unitAlias" in top_level_data: if "unitAlias" in top_level_data:
import_directive.alias = top_level_data["unitAlias"] import_directive.alias = top_level_data["unitAlias"]
if "symbolAliases" in top_level_data:
symbol_aliases = top_level_data["symbolAliases"]
_handle_import_aliases(symbol_aliases, import_directive, scope)
else: else:
import_directive = Import( import_directive = Import(
Path( Path(
@ -356,12 +387,16 @@ Please rename it, this name is reserved for Slither's internals"""
for i in contract_parser.linearized_base_contracts[1:]: for i in contract_parser.linearized_base_contracts[1:]:
if i in contract_parser.remapping: if i in contract_parser.remapping:
ancestors.append( contract_name = contract_parser.remapping[i]
contract_parser.underlying_contract.file_scope.get_contract_from_name( if contract_name in contract_parser.underlying_contract.file_scope.renaming:
contract_parser.remapping[i] contract_name = contract_parser.underlying_contract.file_scope.renaming[
) contract_name
# self._compilation_unit.get_contract_from_name(contract_parser.remapping[i]) ]
target = contract_parser.underlying_contract.file_scope.get_contract_from_name(
contract_name
) )
assert target
ancestors.append(target)
elif i in self._contracts_by_id: elif i in self._contracts_by_id:
ancestors.append(self._contracts_by_id[i]) ancestors.append(self._contracts_by_id[i])
else: else:

@ -223,6 +223,7 @@ def parse_type(
from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc
sl: "SlitherCompilationUnit" sl: "SlitherCompilationUnit"
renaming: Dict[str, str]
# Note: for convenicence top level functions use the same parser than function in contract # Note: for convenicence top level functions use the same parser than function in contract
# but contract_parser is set to None # but contract_parser is set to None
if isinstance(caller_context, SlitherCompilationUnitSolc) or ( if isinstance(caller_context, SlitherCompilationUnitSolc) or (
@ -232,10 +233,12 @@ def parse_type(
if isinstance(caller_context, SlitherCompilationUnitSolc): if isinstance(caller_context, SlitherCompilationUnitSolc):
sl = caller_context.compilation_unit sl = caller_context.compilation_unit
next_context = caller_context next_context = caller_context
renaming = {}
else: else:
assert isinstance(caller_context, FunctionSolc) assert isinstance(caller_context, FunctionSolc)
sl = caller_context.underlying_function.compilation_unit sl = caller_context.underlying_function.compilation_unit
next_context = caller_context.slither_parser next_context = caller_context.slither_parser
renaming = caller_context.underlying_function.file_scope.renaming
structures_direct_access = sl.structures_top_level structures_direct_access = sl.structures_top_level
all_structuress = [c.structures for c in sl.contracts] all_structuress = [c.structures for c in sl.contracts]
all_structures = [item for sublist in all_structuress for item in sublist] all_structures = [item for sublist in all_structuress for item in sublist]
@ -269,6 +272,8 @@ def parse_type(
all_enums = scope.enums.values() all_enums = scope.enums.values()
contracts = scope.contracts.values() contracts = scope.contracts.values()
functions = list(scope.functions) functions = list(scope.functions)
renaming = scope.renaming
elif isinstance(caller_context, (ContractSolc, FunctionSolc)): elif isinstance(caller_context, (ContractSolc, FunctionSolc)):
if isinstance(caller_context, FunctionSolc): if isinstance(caller_context, FunctionSolc):
underlying_func = caller_context.underlying_function underlying_func = caller_context.underlying_function
@ -277,9 +282,11 @@ def parse_type(
assert isinstance(underlying_func, FunctionContract) assert isinstance(underlying_func, FunctionContract)
contract = underlying_func.contract contract = underlying_func.contract
next_context = caller_context.contract_parser next_context = caller_context.contract_parser
scope = caller_context.underlying_function.file_scope
else: else:
contract = caller_context.underlying_contract contract = caller_context.underlying_contract
next_context = caller_context next_context = caller_context
scope = caller_context.underlying_contract.file_scope
structures_direct_access = contract.structures structures_direct_access = contract.structures
structures_direct_access += contract.file_scope.structures.values() structures_direct_access += contract.file_scope.structures.values()
@ -293,6 +300,8 @@ def parse_type(
all_enums += contract.file_scope.enums.values() all_enums += contract.file_scope.enums.values()
contracts = contract.file_scope.contracts.values() contracts = contract.file_scope.contracts.values()
functions = contract.functions + contract.modifiers functions = contract.functions + contract.modifiers
renaming = scope.renaming
else: else:
raise ParsingError(f"Incorrect caller context: {type(caller_context)}") raise ParsingError(f"Incorrect caller context: {type(caller_context)}")
@ -303,8 +312,11 @@ def parse_type(
key = "name" key = "name"
if isinstance(t, UnknownType): if isinstance(t, UnknownType):
name = t.name
if name in renaming:
name = renaming[name]
return _find_from_type_name( return _find_from_type_name(
t.name, name,
functions, functions,
contracts, contracts,
structures_direct_access, structures_direct_access,
@ -320,8 +332,11 @@ def parse_type(
if t[key] == "UserDefinedTypeName": if t[key] == "UserDefinedTypeName":
if is_compact_ast: if is_compact_ast:
name = t["typeDescriptions"]["typeString"]
if name in renaming:
name = renaming[name]
return _find_from_type_name( return _find_from_type_name(
t["typeDescriptions"]["typeString"], name,
functions, functions,
contracts, contracts,
structures_direct_access, structures_direct_access,
@ -332,8 +347,12 @@ def parse_type(
# Determine if we have a type node (otherwise we use the name node, as some older solc did not have 'type'). # Determine if we have a type node (otherwise we use the name node, as some older solc did not have 'type').
type_name_key = "type" if "type" in t["attributes"] else key type_name_key = "type" if "type" in t["attributes"] else key
name = t["attributes"][type_name_key]
if name in renaming:
name = renaming[name]
return _find_from_type_name( return _find_from_type_name(
t["attributes"][type_name_key], name,
functions, functions,
contracts, contracts,
structures_direct_access, structures_direct_access,
@ -345,8 +364,11 @@ def parse_type(
# Introduced with Solidity 0.8 # Introduced with Solidity 0.8
if t[key] == "IdentifierPath": if t[key] == "IdentifierPath":
if is_compact_ast: if is_compact_ast:
name = t["name"]
if name in renaming:
name = renaming[name]
return _find_from_type_name( return _find_from_type_name(
t["name"], name,
functions, functions,
contracts, contracts,
structures_direct_access, structures_direct_access,

@ -0,0 +1,14 @@
contract Test{}
struct St{
uint v;
}
uint constant A = 0;
library Lib {
struct Data {
uint256 version;
}
}

@ -0,0 +1,10 @@
import {Test as TestAlias, St as StAlias, A as Aalias, Lib as LibAlias} from "./import.sol";
contract C is TestAlias{
using LibAlias for LibAlias.Data;
function f(StAlias storage s) internal{
s.v = Aalias;
}
}

@ -0,0 +1,7 @@
{
"Test": {},
"Lib": {},
"C": {
"f(St)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -0,0 +1,7 @@
{
"Test": {},
"Lib": {},
"C": {
"f(St)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -0,0 +1,7 @@
{
"Test": {},
"Lib": {},
"C": {
"f(St)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -0,0 +1,7 @@
{
"Test": {},
"Lib": {},
"C": {
"f(St)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -0,0 +1,7 @@
{
"Test": {},
"Lib": {},
"C": {
"f(St)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -0,0 +1,7 @@
{
"Test": {},
"Lib": {},
"C": {
"f(St)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -0,0 +1,7 @@
{
"Test": {},
"Lib": {},
"C": {
"f(St)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -0,0 +1,7 @@
{
"Test": {},
"Lib": {},
"C": {
"f(St)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -0,0 +1,7 @@
{
"Test": {},
"Lib": {},
"C": {
"f(St)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -0,0 +1,7 @@
{
"Test": {},
"Lib": {},
"C": {
"f(St)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -0,0 +1,7 @@
{
"Test": {},
"Lib": {},
"C": {
"f(St)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -0,0 +1,7 @@
{
"Test": {},
"Lib": {},
"C": {
"f(St)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -0,0 +1,7 @@
{
"Test": {},
"Lib": {},
"C": {
"f(St)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -400,6 +400,7 @@ ALL_TESTS = [
["0.8.2"], ["0.8.2"],
), ),
Test("custom_error_with_state_variable.sol", make_version(8, 4, 12)), Test("custom_error_with_state_variable.sol", make_version(8, 4, 12)),
Test("complex_imports/import_aliases/test.sol", VERSIONS_08),
] ]
# create the output folder if needed # create the output folder if needed
try: try:

Loading…
Cancel
Save