-
Notifications
You must be signed in to change notification settings - Fork 1k
/
Copy pathtest_ssa_generation.py
1193 lines (1010 loc) · 39.1 KB
/
test_ssa_generation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# # pylint: disable=too-many-lines
import pathlib
from argparse import ArgumentTypeError
from collections import defaultdict
from inspect import getsourcefile
from typing import Callable, Dict, List, Union
import pytest
from solc_select.solc_select import valid_version as solc_valid_version
from slither import Slither
from slither.core.cfg.node import Node, NodeType
from slither.core.declarations import Contract, Function
from slither.core.solidity_types import ArrayType, ElementaryType
from slither.core.variables.local_variable import LocalVariable, VariableLocation
from slither.core.variables.state_variable import StateVariable
from slither.slithir.operations import (
Assignment,
Binary,
BinaryType,
HighLevelCall,
Index,
InitArray,
InternalCall,
NewArray,
Operation,
OperationWithLValue,
Phi,
Return,
)
from slither.slithir.utils.ssa import is_used_later
from slither.slithir.variables import (
Constant,
LocalIRVariable,
ReferenceVariable,
StateIRVariable,
TemporaryVariableSSA,
)
# Directory of currently executing script. Will be used as basis for temporary file names.
SCRIPT_DIR = pathlib.Path(getsourcefile(lambda: 0)).parent # type:ignore
def valid_version(ver: str) -> bool:
"""Wrapper function to check if the solc-version is valid
The solc_select function raises and exception but for checks below,
only a bool is needed.
"""
try:
solc_valid_version(ver)
return True
except ArgumentTypeError:
return False
def have_ssa_if_ir(function: Function) -> None:
"""Verifies that all nodes in a function that have IR also have SSA IR"""
for n in function.nodes:
if n.irs:
assert n.irs_ssa
# pylint: disable=too-many-branches, too-many-locals
def ssa_basic_properties(function: Function) -> None:
"""Verifies that basic properties of ssa holds
1. Every name is defined only once
2. A l-value is never index zero - there is always a zero-value available for each var
3. Every r-value is at least defined at some point
4. The number of ssa defs is >= the number of assignments to var
5. Function parameters SSA are stored in function.parameters_ssa
- if function parameter is_storage it refers to a fake variable
6. Function returns SSA are stored in function.returns_ssa
- if function return is_storage it refers to a fake variable
"""
ssa_lvalues = set()
ssa_rvalues = set()
lvalue_assignments: Dict[str, int] = {}
for n in function.nodes:
for ir in n.irs:
if isinstance(ir, OperationWithLValue) and ir.lvalue:
name = ir.lvalue.name
if name is None:
continue
if name in lvalue_assignments:
lvalue_assignments[name] += 1
else:
lvalue_assignments[name] = 1
for ssa in n.irs_ssa:
if isinstance(ssa, OperationWithLValue):
# 1
assert ssa.lvalue not in ssa_lvalues
ssa_lvalues.add(ssa.lvalue)
# 2 (if Local/State Var)
ssa_lvalue = ssa.lvalue
if isinstance(ssa_lvalue, (StateIRVariable, LocalIRVariable)):
assert ssa_lvalue.index > 0
for rvalue in filter(
lambda x: not isinstance(x, (StateIRVariable, Constant)), ssa.read
):
ssa_rvalues.add(rvalue)
# 3
# Each var can have one non-defined value, the value initially held. Typically,
# var_0, i_0, state_0 or similar.
undef_vars = set()
for rvalue in ssa_rvalues:
if rvalue not in ssa_lvalues:
assert rvalue.non_ssa_version not in undef_vars
undef_vars.add(rvalue.non_ssa_version)
# 4
ssa_defs: Dict[str, int] = defaultdict(int)
for v in ssa_lvalues:
if v and v.name:
ssa_defs[v.name] += 1
for k, count in lvalue_assignments.items():
assert ssa_defs[k] >= count
# Helper 5/6
def check_property_5_and_6(
variables: List[LocalVariable], ssavars: List[LocalIRVariable]
) -> None:
for var in filter(lambda x: x.name, variables):
ssa_vars = [x for x in ssavars if x.non_ssa_version == var]
assert len(ssa_vars) == 1
ssa_var = ssa_vars[0]
assert var.is_storage == ssa_var.is_storage
if ssa_var.is_storage:
assert len(ssa_var.refers_to) == 1
assert ssa_var.refers_to[0].location == VariableLocation.REFERENCE_TO_STORAGE.value
# 5
check_property_5_and_6(function.parameters, function.parameters_ssa)
# 6
check_property_5_and_6(function.returns, function.returns_ssa)
def ssa_phi_node_properties(f: Function) -> None:
"""Every phi-function should have as many args as predecessors
This does not apply if the phi-node refers to state variables,
they make use os special phi-nodes for tracking potential values
a state variable can have
"""
for node in f.nodes:
for ssa in node.irs_ssa:
if isinstance(ssa, Phi):
n = len(ssa.read)
if not isinstance(ssa.lvalue, StateIRVariable):
assert len(node.fathers) == n
# TODO (hbrodin): This should probably go into another file, not specific to SSA
def dominance_properties(f: Function) -> None:
"""Verifies properties related to dominators holds
1. Every node have an immediate dominator except entry_node which have none
2. From every node immediate dominator there is a path via its successors to the node
"""
def find_path(from_node: Node, to: Node) -> bool:
visited = set()
worklist = list(from_node.sons)
while worklist:
first, *worklist = worklist
if first == to:
return True
visited.add(first)
for successor in first.sons:
if successor not in visited:
worklist.append(successor)
return False
for node in f.nodes:
if node is f.entry_point:
assert node.immediate_dominator is None
else:
assert node.immediate_dominator is not None
assert find_path(node.immediate_dominator, node)
def phi_values_inserted(f: Function) -> None:
"""Verifies that phi-values are inserted at the right places
For every node that has a dominance frontier, any def (including
phi) should be a phi function in its dominance frontier
"""
def have_phi_for_var(
node: Node, var: Union[StateIRVariable, LocalIRVariable, TemporaryVariableSSA]
) -> bool:
"""Checks if a node has a phi-instruction for var
The ssa version would ideally be checked, but then
more data flow analysis would be needed, for cases
where a new def for var is introduced before reaching
DF
"""
non_ssa = var.non_ssa_version
for ssa in node.irs_ssa:
if isinstance(ssa, Phi):
if non_ssa in map(
lambda ssa_var: ssa_var.non_ssa_version,
[
r
for r in ssa.read
if isinstance(r, (StateIRVariable, LocalIRVariable, TemporaryVariableSSA))
],
):
return True
return False
for node in filter(lambda n: n.dominance_frontier, f.nodes):
for df in node.dominance_frontier:
for ssa in node.irs_ssa:
if isinstance(ssa, OperationWithLValue):
ssa_lvalue = ssa.lvalue
if isinstance(
ssa_lvalue, (StateIRVariable, LocalIRVariable, TemporaryVariableSSA)
) and is_used_later(node, ssa_lvalue):
assert have_phi_for_var(df, ssa_lvalue)
def verify_properties_hold(slither: Slither) -> None:
"""Ensures that basic properties of SSA hold true"""
def verify_func(func: Function) -> None:
have_ssa_if_ir(func)
phi_values_inserted(func)
ssa_basic_properties(func)
ssa_phi_node_properties(func)
dominance_properties(func)
def verify(slither: Slither) -> None:
for cu in slither.compilation_units:
for func in cu.functions_and_modifiers:
_dump_function(func)
verify_func(func)
for contract in cu.contracts:
for f in contract.functions:
if f.is_constructor or f.is_constructor_variables:
_dump_function(f)
verify_func(f)
assert isinstance(slither, Slither)
verify(slither)
def _dump_function(f: Function) -> None:
"""Helper function to print nodes/ssa ir for a function or modifier"""
print(f"---- {f.name} ----")
for n in f.nodes:
print(n)
for ir in n.irs_ssa:
print(f"\t{ir}")
print("")
def _dump_functions(c: Contract) -> None:
"""Helper function to print functions and modifiers of a contract"""
for f in c.functions_and_modifiers:
_dump_function(f)
def get_filtered_ssa(f: Union[Function, Node], flt: Callable) -> List[Operation]:
"""Returns a list of all ssanodes filtered by filter for all nodes in function f"""
if isinstance(f, Function):
return [ssanode for node in f.nodes for ssanode in node.irs_ssa if flt(ssanode)]
assert isinstance(f, Node)
return [ssanode for ssanode in f.irs_ssa if flt(ssanode)]
def get_ssa_of_type(f: Union[Function, Node], ssatype) -> List[Operation]:
"""Returns a list of all ssanodes of a specific type for all nodes in function f"""
return get_filtered_ssa(f, lambda ssanode: isinstance(ssanode, ssatype))
def test_multi_write(slither_from_solidity_source) -> None:
source = """
pragma solidity ^0.8.11;
contract Test {
function multi_write(uint val) external pure returns(uint) {
val = 1;
val = 2;
val = 3;
}
}"""
with slither_from_solidity_source(source) as slither:
verify_properties_hold(slither)
def test_single_branch_phi(slither_from_solidity_source) -> None:
source = """
pragma solidity ^0.8.11;
contract Test {
function single_branch_phi(uint val) external pure returns(uint) {
if (val == 3) {
val = 9;
}
return val;
}
}
"""
with slither_from_solidity_source(source) as slither:
verify_properties_hold(slither)
def test_basic_phi(slither_from_solidity_source) -> None:
source = """
pragma solidity ^0.8.11;
contract Test {
function basic_phi(uint val) external pure returns(uint) {
if (val == 3) {
val = 9;
} else {
val = 1;
}
return val;
}
}
"""
with slither_from_solidity_source(source) as slither:
verify_properties_hold(slither)
def test_basic_loop_phi(slither_from_solidity_source) -> None:
source = """
pragma solidity ^0.8.11;
contract Test {
function basic_loop_phi(uint val) external pure returns(uint) {
for (uint i=0;i<128;i++) {
val = val + 1;
}
return val;
}
}
"""
with slither_from_solidity_source(source) as slither:
verify_properties_hold(slither)
@pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.")
def test_phi_propagation_loop(slither_from_solidity_source):
source = """
pragma solidity ^0.8.11;
contract Test {
function looping(uint v) external pure returns(uint) {
uint val = 0;
for (uint i=0;i<v;i++) {
if (val > i) {
val = i;
} else {
val = 3;
}
}
return val;
}
}
"""
with slither_from_solidity_source(source) as slither:
verify_properties_hold(slither)
@pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.")
def test_free_function_properties(slither_from_solidity_source):
source = """
pragma solidity ^0.8.11;
function free_looping(uint v) returns(uint) {
uint val = 0;
for (uint i=0;i<v;i++) {
if (val > i) {
val = i;
} else {
val = 3;
}
}
return val;
}
contract Test {}
"""
with slither_from_solidity_source(source) as slither:
verify_properties_hold(slither)
def test_ssa_inter_transactional(slither_from_solidity_source) -> None:
source = """
pragma solidity ^0.8.11;
contract A {
uint my_var_A;
uint my_var_B;
function direct_set(uint i) public {
my_var_A = i;
}
function direct_set_plus_one(uint i) public {
my_var_A = i + 1;
}
function indirect_set() public {
my_var_B = my_var_A;
}
}
"""
with slither_from_solidity_source(source) as slither:
c = slither.contracts[0]
variables = c.variables_as_dict
funcs = c.available_functions_as_dict()
direct_set = funcs["direct_set(uint256)"]
# Skip entry point and go straight to assignment ir
assign1 = direct_set.nodes[1].irs_ssa[0]
assert isinstance(assign1, Assignment)
assign2 = direct_set.nodes[1].irs_ssa[0]
assert isinstance(assign2, Assignment)
indirect_set = funcs["indirect_set()"]
phi = indirect_set.entry_point.irs_ssa[0]
assert isinstance(phi, Phi)
# phi rvalues come from 1, initial value of my_var_a and 2, assignment in direct_set
assert len(phi.rvalues) == 3
assert all(x.non_ssa_version == variables["my_var_A"] for x in phi.rvalues)
assert assign1.lvalue in phi.rvalues
assert assign2.lvalue in phi.rvalues
@pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.")
def test_ssa_phi_callbacks(slither_from_solidity_source):
source = """
pragma solidity ^0.8.11;
contract A {
uint my_var_A;
uint my_var_B;
function direct_set(uint i) public {
my_var_A = i;
}
function use_a() public {
// Expect a phi-node here
my_var_B = my_var_A;
B b = new B();
my_var_A = 3;
b.do_stuff();
// Expect a phi-node here
my_var_B = my_var_A;
}
}
contract B {
function do_stuff() public returns (uint) {
// This could be calling back into A
}
}
"""
with slither_from_solidity_source(source) as slither:
c = slither.get_contract_from_name("A")[0]
_dump_functions(c)
f = [x for x in c.functions if x.name == "use_a"][0]
var_a = [x for x in c.variables if x.name == "my_var_A"][0]
entry_phi = [
x
for x in f.entry_point.irs_ssa
if isinstance(x, Phi) and x.lvalue.non_ssa_version == var_a
][0]
# The four potential sources are:
# 1. initial value
# 2. my_var_A = i;
# 3. my_var_A = 3;
# 4. phi-value after call to b.do_stuff(), which could be reentrant.
assert len(entry_phi.rvalues) == 4
# Locate the first high-level call (should be b.do_stuff())
call_node = [x for y in f.nodes for x in y.irs_ssa if isinstance(x, HighLevelCall)][0]
n = call_node.node
# Get phi-node after call
after_call_phi = n.irs_ssa[n.irs_ssa.index(call_node) + 1]
# The two sources for this phi node is
# 1. my_var_A = i;
# 2. my_var_A = 3;
assert isinstance(after_call_phi, Phi)
assert len(after_call_phi.rvalues) == 2
@pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.")
def test_storage_refers_to(slither_from_solidity_source):
"""Test the storage aspects of the SSA IR
When declaring a var as being storage, start tracking what storage it refers_to.
When a phi-node is created, ensure refers_to is propagated to the phi-node.
Assignments also propagate refers_to.
Whenever a ReferenceVariable is the destination of an assignment (e.g. s.v = 10)
below, create additional versions of the variables it refers to record that a
write was made. In the current implementation, this is referenced by phis.
"""
source = """
contract A{
struct St{
int v;
}
St state0;
St state1;
function f() public{
St storage s = state0;
if(true){
s = state1;
}
s.v = 10;
}
}
"""
with slither_from_solidity_source(source) as slither:
c = slither.contracts[0]
f = c.functions[0]
phinodes = get_ssa_of_type(f, Phi)
# Expect 2 in entrypoint (state0/state1 initial values), 1 at 'ENDIF' and two related to the
# ReferenceVariable write s.v = 10.
assert len(phinodes) == 5
# Assign s to state0, s to state1, s.v to 10
assigns = get_ssa_of_type(f, Assignment)
assert len(assigns) == 3
# The IR variables have is_storage
assert all(x.lvalue.is_storage for x in assigns if isinstance(x, LocalIRVariable))
# s.v ReferenceVariable points to one of the phi vars...
ref0 = [x.lvalue for x in assigns if isinstance(x.lvalue, ReferenceVariable)][0]
sphis = [x for x in phinodes if x.lvalue == ref0.points_to]
assert len(sphis) == 1
sphi = sphis[0]
# ...and that phi refers to the two entry phi-values
entryphi = [x for x in phinodes if x.lvalue in sphi.lvalue.refers_to]
assert len(entryphi) == 2
# The remaining two phis are the ones recording that write through ReferenceVariable occurred
for ephi in entryphi:
phinodes.remove(ephi)
phinodes.remove(sphi)
assert len(phinodes) == 2
# And they are recorded in one of the entry phis
assert phinodes[0].lvalue in entryphi[0].rvalues or entryphi[1].rvalues
assert phinodes[1].lvalue in entryphi[0].rvalues or entryphi[1].rvalues
@pytest.mark.skipif(
not valid_version("0.4.0"), reason="Solidity version 0.4.0 not available on this platform"
)
def test_initial_version_exists_for_locals(slither_from_solidity_source):
"""
In solidity you can write statements such as
uint a = a + 1, this test ensures that can be handled for local variables.
"""
src = """
contract C {
function func() internal {
uint a = a + 1;
}
}
"""
with slither_from_solidity_source(src, "0.4.0") as slither:
verify_properties_hold(slither)
c = slither.contracts[0]
f = c.functions[0]
addition = get_ssa_of_type(f, Binary)[0]
assert addition.type == BinaryType.ADDITION
assert isinstance(addition.variable_right, Constant)
a_0 = addition.variable_left
assert a_0.index == 0
assert a_0.name == "a"
assignment = get_ssa_of_type(f, Assignment)[0]
a_1 = assignment.lvalue
assert a_1.index == 1
assert a_1.name == "a"
assert assignment.rvalue == addition.lvalue
assert a_0.non_ssa_version == a_1.non_ssa_version
@pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.")
@pytest.mark.skipif(
not valid_version("0.4.0"), reason="Solidity version 0.4.0 not available on this platform"
)
def test_initial_version_exists_for_state_variables(slither_from_solidity_source):
"""
In solidity you can write statements such as
uint a = a + 1, this test ensures that can be handled for state variables.
"""
src = """
contract C {
uint a = a + 1;
}
"""
with slither_from_solidity_source(src, "0.4.0") as slither:
verify_properties_hold(slither)
c = slither.contracts[0]
f = c.functions[0] # There will be one artificial ctor function for the state vars
addition = get_ssa_of_type(f, Binary)[0]
assert addition.type == BinaryType.ADDITION
assert isinstance(addition.variable_right, Constant)
a_0 = addition.variable_left
assert isinstance(a_0, StateIRVariable)
assert a_0.name == "a"
assignment = get_ssa_of_type(f, Assignment)[0]
a_1 = assignment.lvalue
assert isinstance(a_1, StateIRVariable)
assert a_1.index == a_0.index + 1
assert a_1.name == "a"
assert assignment.rvalue == addition.lvalue
assert a_0.non_ssa_version == a_1.non_ssa_version
assert isinstance(a_0.non_ssa_version, StateVariable)
# No conditional/other function interaction so no phis
assert len(get_ssa_of_type(f, Phi)) == 0
@pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.")
def test_initial_version_exists_for_state_variables_function_assign(slither_from_solidity_source):
"""
In solidity you can write statements such as
uint a = a + 1, this test ensures that can be handled for local variables.
"""
# TODO (hbrodin): Could be a detector that a is not used in f
src = """
contract C {
uint a = f();
function f() internal returns(uint) {
return a;
}
}
"""
with slither_from_solidity_source(src) as slither:
verify_properties_hold(slither)
c = slither.contracts[0]
f, ctor = c.functions
if f.is_constructor_variables:
f, ctor = ctor, f
# ctor should have a single call to f that assigns to a
# temporary variable, that is then assigned to a
call = get_ssa_of_type(ctor, InternalCall)[0]
assert call.node.function == f
assign = get_ssa_of_type(ctor, Assignment)[0]
assert assign.rvalue == call.lvalue
assert isinstance(assign.lvalue, StateIRVariable)
assert assign.lvalue.name == "a"
# f should have a phi node on entry of a0, a1 and should return
# a2
phi = get_ssa_of_type(f, Phi)[0]
assert len(phi.rvalues) == 2
assert assign.lvalue in phi.rvalues
@pytest.mark.skipif(
not valid_version("0.4.0"), reason="Solidity version 0.4.0 not available on this platform"
)
def test_return_local_before_assign(slither_from_solidity_source):
src = """
// this require solidity < 0.5
// a variable can be returned before declared. Ensure it can be
// handled by Slither.
contract A {
function local(bool my_bool) internal returns(uint){
if(my_bool){
return a_local;
}
uint a_local = 10;
}
}
"""
with slither_from_solidity_source(src, "0.4.0") as slither:
f = slither.contracts[0].functions[0]
ret = get_ssa_of_type(f, Return)[0]
assert len(ret.values) == 1
assert ret.values[0].index == 0
assign = get_ssa_of_type(f, Assignment)[0]
assert assign.lvalue.index == 1
assert assign.lvalue.non_ssa_version == ret.values[0].non_ssa_version
@pytest.mark.skipif(
not valid_version("0.5.0"), reason="Solidity version 0.5.0 not available on this platform"
)
def test_shadow_local(slither_from_solidity_source):
src = """
contract A {
// this require solidity 0.5
function shadowing_local() internal{
uint local = 0;
{
uint local = 1;
{
uint local = 2;
}
}
}
}
"""
with slither_from_solidity_source(src, "0.5.0") as slither:
_dump_functions(slither.contracts[0])
f = slither.contracts[0].functions[0]
# Ensure all assignments are to a variable of index 1
# not using the same IR var.
assert all(map(lambda x: x.lvalue.index == 1, get_ssa_of_type(f, Assignment)))
@pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.")
def test_multiple_named_args_returns(slither_from_solidity_source):
"""Verifies that named arguments and return values have correct versions
Each arg/ret have an initial version, version 0, and is written once and should
then have version 1.
"""
src = """
contract A {
function multi(uint arg1, uint arg2) internal returns (uint ret1, uint ret2) {
arg1 = arg1 + 1;
arg2 = arg2 + 2;
ret1 = arg1 + 3;
ret2 = arg2 + 4;
}
}"""
with slither_from_solidity_source(src) as slither:
verify_properties_hold(slither)
f = slither.contracts[0].functions[0]
# Ensure all LocalIRVariables (not TemporaryVariables) have index 1
assert all(
map(
lambda x: x.lvalue.index == 1 or not isinstance(x.lvalue, LocalIRVariable),
get_ssa_of_type(f, OperationWithLValue),
)
)
@pytest.mark.xfail(reason="Tests for wanted state of SSA IR, not current.", strict=True)
def test_memory_array(slither_from_solidity_source):
src = """
contract MemArray {
struct A {
uint val1;
uint val2;
}
function test_array() internal {
A[] memory a= new A[](4);
// Create REF_0 -> a_1[2]
accept_array_entry(a[2]);
// Create REF_1 -> a_1[3]
accept_array_entry(a[3]);
A memory alocal;
accept_array_entry(alocal);
}
// val_1 = ϕ(val_0, REF_0, REF_1, alocal_1)
// val_0 is an unknown external value
function accept_array_entry(A memory val) public returns (uint) {
uint zero = 0;
b(zero);
// Create REF_2 -> val_1.val1
return b(val.val1);
}
function b(uint arg) public returns (uint){
// arg_1 = ϕ(arg_0, zero_1, REF_2)
return arg + 1;
}
}"""
with slither_from_solidity_source(src) as slither:
c = slither.contracts[0]
ftest_array, faccept, fb = c.functions
# Locate REF_0/REF_1/alocal (they are all args to the call)
accept_args = [x.arguments[0] for x in get_ssa_of_type(ftest_array, InternalCall)]
# Check entrypoint of accept_array_entry, it should contain a phi-node
# of expected rvalues
[phi_entry_accept] = get_ssa_of_type(faccept.entry_point, Phi)
for arg in accept_args:
assert arg in phi_entry_accept.rvalues
# NOTE(hbrodin): There should be an additional val_0 in the phi-node.
# That additional val_0 indicates an external caller of this function.
assert len(phi_entry_accept.rvalues) == len(accept_args) + 1
# Args used to invoke b
b_args = [x.arguments[0] for x in get_ssa_of_type(faccept, InternalCall)]
# Check entrypoint of B, it should contain a phi-node of expected
# rvalues
[phi_entry_b] = get_ssa_of_type(fb.entry_point, Phi)
for arg in b_args:
assert arg in phi_entry_b.rvalues
# NOTE(hbrodin): There should be an additional arg_0 (see comment about phi_entry_accept).
assert len(phi_entry_b.rvalues) == len(b_args) + 1
@pytest.mark.xfail(reason="Tests for wanted state of SSA IR, not current.", strict=True)
def test_storage_array(slither_from_solidity_source):
src = """
contract StorageArray {
struct A {
uint val1;
uint val2;
}
// NOTE(hbrodin): a is never written, should only become a_0. Same for astorage (astorage_0). Phi-nodes at entry
// should only add new versions of a state variable if it is actually written.
A[] a;
A astorage;
function test_array() internal {
accept_array_entry(a[2]);
accept_array_entry(a[3]);
accept_array_entry(astorage);
}
function accept_array_entry(A storage val) internal returns (uint) {
// val is either a[2], a[3] or astorage_0. Ideally this could be identified.
uint five = 5;
// NOTE(hbrodin): If the following line is enabled, there would ideally be a phi-node representing writes
// to either a or astorage.
//val.val2 = 4;
b(five);
return b(val.val1);
}
function b(uint value) public returns (uint){
// Expect a phi-node at the entrypoint
// value_1 = ϕ(value_0, five_0, REF_x), where REF_x is the reference to val.val1 in accept_array_entry.
return value + 1;
}
}"""
with slither_from_solidity_source(src) as slither:
c = slither.contracts[0]
_dump_functions(c)
ftest, faccept, fb = c.functions
# None of a/astorage is written so expect that there are no phi-nodes at entrypoint.
assert len(get_ssa_of_type(ftest.entry_point, Phi)) == 0
# Expect all references to start from index 0 (no writes)
assert all(x.variable_left.index == 0 for x in get_ssa_of_type(ftest, Index))
[phi_entry_accept] = get_ssa_of_type(faccept.entry_point, Phi)
assert len(phi_entry_accept.rvalues) == 3 # See comment in b above
[phi_entry_b] = get_ssa_of_type(fb.entry_point, Phi)
assert len(phi_entry_b.rvalues) == 3 # See comment in b above
@pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.")
def test_issue_468(slither_from_solidity_source):
"""
Ensure issue 468 is corrected as per
https://github.com/crytic/slither/issues/468#issuecomment-620974151
The one difference is that we allow the phi-function at entry of f to
hold exit state which contains init state and state from branch, which
is a bit redundant. This could be further simplified.
"""
source = """
contract State {
int state = 0;
function f(int a) public returns (int) {
// phi-node here for state
if (a < 1) {
state += 1;
}
// phi-node here for state
return state;
}
}
"""
with slither_from_solidity_source(source) as slither:
c = slither.get_contract_from_name("State")[0]
f = [x for x in c.functions if x.name == "f"][0]
# Check that there is an entry point phi values for each later value
# plus one additional which is the initial value
entry_ssa = f.entry_point.irs_ssa
assert len(entry_ssa) == 1
phi_entry = entry_ssa[0]
assert isinstance(phi_entry, Phi)
# Find the second phi function
endif_node = [x for x in f.nodes if x.type == NodeType.ENDIF][0]
assert len(endif_node.irs_ssa) == 1
phi_endif = endif_node.irs_ssa[0]
assert isinstance(phi_endif, Phi)
# Ensure second phi-function contains init-phi and one additional
assert len(phi_endif.rvalues) == 2
assert phi_entry.lvalue in phi_endif.rvalues
# Find return-statement and ensure it returns the phi_endif
return_node = [x for x in f.nodes if x.type == NodeType.RETURN][0]
assert len(return_node.irs_ssa) == 1
ret = return_node.irs_ssa[0]
assert len(ret.values) == 1
assert phi_endif.lvalue in ret.values
# Ensure that the phi_endif (which is the end-state for function as well) is in the entry_phi
assert phi_endif.lvalue in phi_entry.rvalues
@pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.")
def test_issue_434(slither_from_solidity_source):
source = """
contract Contract {
int public a;
function f() public {
g();
a += 1;
}
function e() public {
a -= 1;
}
function g() public {
e();
}
}
"""
with slither_from_solidity_source(source) as slither:
c = slither.get_contract_from_name("Contract")[0]
e = [x for x in c.functions if x.name == "e"][0]
f = [x for x in c.functions if x.name == "f"][0]
g = [x for x in c.functions if x.name == "g"][0]
# Ensure there is a phi-node at the beginning of f and e
phi_entry_e = get_ssa_of_type(e.entry_point, Phi)[0]
phi_entry_f = get_ssa_of_type(f.entry_point, Phi)[0]
# But not at g
assert len(get_ssa_of_type(g, Phi)) == 0
# Ensure that the final states of f and e are in the entry-states
add_f = get_filtered_ssa(
f, lambda x: isinstance(x, Binary) and x.type == BinaryType.ADDITION
)[0]
sub_e = get_filtered_ssa(
e, lambda x: isinstance(x, Binary) and x.type == BinaryType.SUBTRACTION
)[0]
assert add_f.lvalue in phi_entry_f.rvalues
assert add_f.lvalue in phi_entry_e.rvalues
assert sub_e.lvalue in phi_entry_f.rvalues
assert sub_e.lvalue in phi_entry_e.rvalues
# Ensure there is a phi-node after call to g
call = get_ssa_of_type(f, InternalCall)[0]
idx = call.node.irs_ssa.index(call)
aftercall_phi = call.node.irs_ssa[idx + 1]
assert isinstance(aftercall_phi, Phi)
# Ensure that phi node ^ is used in the addition afterwards
assert aftercall_phi.lvalue in (add_f.variable_left, add_f.variable_right)
@pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.")
def test_issue_473(slither_from_solidity_source):
source = """
contract Contract {
function f() public returns (int) {
int a = 1;