mirror of https://github.com/crytic/slither
Merge pull request #2376 from crytic/feat/virtual-override-with-refs
This PR improves the reference API to account for virtual inheritance and abstract contracts. It treats each virtual override defintion and contract definition that inherits from an interface or abstract contract as an "implementation". This lays the groundwork for LSP features and unused definition/ import detector.pull/2385/head
commit
2ad318c7c0
@ -0,0 +1,16 @@ |
||||
type T is uint256; |
||||
uint constant U = 1; |
||||
error V(T); |
||||
event W(T); |
||||
|
||||
contract E { |
||||
type X is int256; |
||||
function f() public { |
||||
T t = T.wrap(U); |
||||
if (T.unwrap(t) == 0) { |
||||
revert V(t); |
||||
} |
||||
emit W(t); |
||||
X x = X.wrap(1); |
||||
} |
||||
} |
@ -0,0 +1,65 @@ |
||||
contract Test { |
||||
function myVirtualFunction() virtual external { |
||||
} |
||||
} |
||||
|
||||
contract A is Test { |
||||
function myVirtualFunction() virtual override external { |
||||
} |
||||
} |
||||
|
||||
contract B is A { |
||||
function myVirtualFunction() override external { |
||||
} |
||||
|
||||
} |
||||
|
||||
contract C is Test { |
||||
function myVirtualFunction() override external { |
||||
} |
||||
} |
||||
|
||||
contract X is Test { |
||||
function myVirtualFunction() virtual override external { |
||||
} |
||||
} |
||||
|
||||
contract Y { |
||||
function myVirtualFunction() virtual external { |
||||
} |
||||
} |
||||
|
||||
contract Z is Y, X{ |
||||
function myVirtualFunction() virtual override(Y, X) external { |
||||
} |
||||
} |
||||
|
||||
|
||||
abstract contract Name { |
||||
constructor() { |
||||
|
||||
} |
||||
} |
||||
|
||||
contract Name2 is Name { |
||||
constructor() { |
||||
|
||||
} |
||||
} |
||||
|
||||
abstract contract Test2 { |
||||
function f() virtual public; |
||||
} |
||||
|
||||
contract A2 is Test2 { |
||||
function f() virtual override public { |
||||
} |
||||
} |
||||
|
||||
abstract contract I { |
||||
function a() public virtual {} |
||||
} |
||||
contract J is I {} |
||||
contract K is J { |
||||
function a() public override {} |
||||
} |
@ -0,0 +1,151 @@ |
||||
from pathlib import Path |
||||
from slither import Slither |
||||
|
||||
TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" |
||||
|
||||
|
||||
def test_overrides(solc_binary_path) -> None: |
||||
# pylint: disable=too-many-statements,too-many-locals |
||||
solc_path = solc_binary_path("0.8.15") |
||||
slither = Slither(Path(TEST_DATA_DIR, "virtual_overrides.sol").as_posix(), solc=solc_path) |
||||
|
||||
test = slither.get_contract_from_name("Test")[0] |
||||
test_virtual_func = test.get_function_from_full_name("myVirtualFunction()") |
||||
assert test_virtual_func.is_virtual |
||||
assert not test_virtual_func.is_override |
||||
x = test.get_functions_overridden_by(test_virtual_func) |
||||
assert len(x) == 0 |
||||
x = test_virtual_func.overridden_by |
||||
assert len(x) == 5 |
||||
assert set(i.canonical_name for i in x) == set( |
||||
["A.myVirtualFunction()", "C.myVirtualFunction()", "X.myVirtualFunction()"] |
||||
) |
||||
|
||||
a = slither.get_contract_from_name("A")[0] |
||||
a_virtual_func = a.get_function_from_full_name("myVirtualFunction()") |
||||
assert a_virtual_func.is_virtual |
||||
assert a_virtual_func.is_override |
||||
x = a.get_functions_overridden_by(a_virtual_func) |
||||
assert len(x) == 2 |
||||
assert set(i.canonical_name for i in x) == set(["Test.myVirtualFunction()"]) |
||||
|
||||
b = slither.get_contract_from_name("B")[0] |
||||
b_virtual_func = b.get_function_from_full_name("myVirtualFunction()") |
||||
assert not b_virtual_func.is_virtual |
||||
assert b_virtual_func.is_override |
||||
x = b.get_functions_overridden_by(b_virtual_func) |
||||
assert len(x) == 2 |
||||
assert set(i.canonical_name for i in x) == set(["A.myVirtualFunction()"]) |
||||
assert len(b_virtual_func.overridden_by) == 0 |
||||
|
||||
c = slither.get_contract_from_name("C")[0] |
||||
c_virtual_func = c.get_function_from_full_name("myVirtualFunction()") |
||||
assert not c_virtual_func.is_virtual |
||||
assert c_virtual_func.is_override |
||||
x = c.get_functions_overridden_by(c_virtual_func) |
||||
assert len(x) == 2 |
||||
# C should not override B as they are distinct leaves in the inheritance tree |
||||
assert set(i.canonical_name for i in x) == set(["Test.myVirtualFunction()"]) |
||||
|
||||
y = slither.get_contract_from_name("Y")[0] |
||||
y_virtual_func = y.get_function_from_full_name("myVirtualFunction()") |
||||
assert y_virtual_func.is_virtual |
||||
assert not y_virtual_func.is_override |
||||
x = y_virtual_func.overridden_by |
||||
assert len(x) == 1 |
||||
assert x[0].canonical_name == "Z.myVirtualFunction()" |
||||
|
||||
z = slither.get_contract_from_name("Z")[0] |
||||
z_virtual_func = z.get_function_from_full_name("myVirtualFunction()") |
||||
assert z_virtual_func.is_virtual |
||||
assert z_virtual_func.is_override |
||||
x = z.get_functions_overridden_by(z_virtual_func) |
||||
assert len(x) == 4 |
||||
assert set(i.canonical_name for i in x) == set( |
||||
["Y.myVirtualFunction()", "X.myVirtualFunction()"] |
||||
) |
||||
|
||||
k = slither.get_contract_from_name("K")[0] |
||||
k_virtual_func = k.get_function_from_full_name("a()") |
||||
assert not k_virtual_func.is_virtual |
||||
assert k_virtual_func.is_override |
||||
assert len(k_virtual_func.overrides) == 3 |
||||
x = k_virtual_func.overrides |
||||
assert set(i.canonical_name for i in x) == set(["I.a()"]) |
||||
|
||||
i = slither.get_contract_from_name("I")[0] |
||||
i_virtual_func = i.get_function_from_full_name("a()") |
||||
assert i_virtual_func.is_virtual |
||||
assert not i_virtual_func.is_override |
||||
assert len(i_virtual_func.overrides) == 0 |
||||
x = i_virtual_func.overridden_by |
||||
assert len(x) == 1 |
||||
assert x[0].canonical_name == "K.a()" |
||||
|
||||
|
||||
def test_virtual_override_references_and_implementations(solc_binary_path) -> None: |
||||
solc_path = solc_binary_path("0.8.15") |
||||
file = Path(TEST_DATA_DIR, "virtual_overrides.sol").as_posix() |
||||
slither = Slither(file, solc=solc_path) |
||||
funcs = slither.offset_to_objects(file, 29) |
||||
assert len(funcs) == 1 |
||||
func = funcs.pop() |
||||
assert func.canonical_name == "Test.myVirtualFunction()" |
||||
assert {(x.start, x.end) for x in slither.offset_to_implementations(file, 29)} == { |
||||
(20, 73), |
||||
(102, 164), |
||||
(274, 328), |
||||
(357, 419), |
||||
} |
||||
|
||||
funcs = slither.offset_to_objects(file, 111) |
||||
assert len(funcs) == 1 |
||||
func = funcs.pop() |
||||
assert func.canonical_name == "A.myVirtualFunction()" |
||||
# A.myVirtualFunction() is implemented in A and also overridden in B |
||||
assert {(x.start, x.end) for x in slither.offset_to_implementations(file, 111)} == { |
||||
(102, 164), |
||||
(190, 244), |
||||
} |
||||
|
||||
# X is inherited by Z and Z.myVirtualFunction() overrides X.myVirtualFunction() |
||||
assert {(x.start, x.end) for x in slither.offset_to_references(file, 341)} == { |
||||
(514, 515), |
||||
(570, 571), |
||||
} |
||||
# The reference to X in inheritance specifier is the definition of Z |
||||
assert {(x.start, x.end) for x in slither.offset_to_definitions(file, 514)} == {(341, 343)} |
||||
# The reference to X in the function override specifier is the definition of Z |
||||
assert {(x.start, x.end) for x in slither.offset_to_definitions(file, 570)} == {(341, 343)} |
||||
|
||||
# Y is inherited by Z and Z.myVirtualFunction() overrides Y.myVirtualFunction() |
||||
assert {(x.start, x.end) for x in slither.offset_to_references(file, 432)} == { |
||||
(511, 512), |
||||
(567, 568), |
||||
} |
||||
# The reference to Y in inheritance specifier is the definition of Z |
||||
assert {(x.start, x.end) for x in slither.offset_to_definitions(file, 511)} == {(432, 434)} |
||||
# The reference to Y in the function override specifier is the definition of Z |
||||
assert {(x.start, x.end) for x in slither.offset_to_definitions(file, 567)} == {(432, 434)} |
||||
|
||||
# Name is abstract and has no implementation. It is inherited and implemented by Name2 |
||||
assert {(x.start, x.end) for x in slither.offset_to_implementations(file, 612)} == {(657, 718)} |
||||
|
||||
|
||||
def test_virtual_is_implemented(solc_binary_path): |
||||
solc_path = solc_binary_path("0.8.15") |
||||
file = Path(TEST_DATA_DIR, "virtual_overrides.sol").as_posix() |
||||
slither = Slither(file, solc=solc_path) |
||||
|
||||
test2 = slither.get_contract_from_name("Test2")[0] |
||||
f = test2.get_function_from_full_name("f()") |
||||
assert f.is_virtual |
||||
assert not f.is_implemented |
||||
|
||||
a2 = slither.get_contract_from_name("A2")[0] |
||||
f = a2.get_function_from_full_name("f()") |
||||
assert f.is_virtual |
||||
assert f.is_implemented |
||||
|
||||
# Test.2f() is not implemented, but A2 inherits from Test2 and overrides f() |
||||
assert {(x.start, x.end) for x in slither.offset_to_implementations(file, 759)} == {(809, 853)} |
Loading…
Reference in new issue