Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.

Commit 3b927f3

Browse files
committed
CFG: fix lastNode relating to assignments with underscores on the LHS
For example, "x, _ := a, b" would produce an incorrect CSV that branched to the next statement after evaluating "b", skipping the assignment to 'x'. We already had test coverage for function returns, so I'm reasonably confident this only affects parallel assigns, not destructuring ones like "x, y := f()".
1 parent 3c84f11 commit 3b927f3

File tree

6 files changed

+114
-24
lines changed

6 files changed

+114
-24
lines changed

ql/src/semmle/go/controlflow/ControlFlowGraphImpl.qll

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -759,18 +759,11 @@ module CFG {
759759
override predicate lastNode(ControlFlow::Node last, Completion cmpl) {
760760
ControlFlowTree.super.lastNode(last, cmpl)
761761
or
762-
exists(int nl, int nr | nl = getNumLhs() and nr = getNumRhs() |
763-
last = MkAssignNode(this, nl - 1)
762+
(
763+
last = max(int i | | epilogueNode(i) order by i)
764764
or
765-
not exists(MkAssignNode(this, nl - 1)) and
766-
(
767-
exists(ControlFlow::Node rhs | lastNode(getRhs(nr - 1), rhs, normalCompletion()) |
768-
if nl = nr then last = rhs else last = MkExtractNode(this, nl - 1)
769-
)
770-
or
771-
not exists(getRhs(nr - 1)) and
772-
lastNode(getLhs(nl - 1), last, normalCompletion())
773-
)
765+
not exists(epilogueNode(_)) and
766+
lastNode(getLastSubExprInEvalOrder(), last, normalCompletion())
774767
) and
775768
cmpl = Done()
776769
}
@@ -787,7 +780,7 @@ module CFG {
787780
firstNode(getRhs(0), succ)
788781
or
789782
not exists(getRhs(_)) and
790-
succ = epilogueNodeRanked(1)
783+
succ = epilogueNodeRanked(0)
791784
)
792785
)
793786
or
@@ -796,6 +789,10 @@ module CFG {
796789
firstNode(getRhs(i + 1), succ)
797790
)
798791
or
792+
not this instanceof RecvStmt and
793+
lastNode(getRhs(getNumRhs() - 1), pred, normalCompletion()) and
794+
succ = epilogueNodeRanked(0)
795+
or
799796
exists(int i |
800797
pred = epilogueNodeRanked(i) and
801798
succ = epilogueNodeRanked(i + 1)
@@ -809,16 +806,17 @@ module CFG {
809806
)
810807
}
811808

809+
private Expr getSubExprInEvalOrder(int evalOrder) {
810+
if evalOrder < getNumLhs()
811+
then result = getLhs(evalOrder)
812+
else result = getRhs(evalOrder - getNumLhs())
813+
}
814+
815+
private Expr getLastSubExprInEvalOrder() {
816+
result = max(int i | | getSubExprInEvalOrder(i) order by i)
817+
}
818+
812819
private ControlFlow::Node epilogueNode(int i) {
813-
not this instanceof RecvStmt and
814-
i = -2 and
815-
(
816-
lastNode(getRhs(getNumRhs() - 1), result, normalCompletion())
817-
or
818-
not exists(getRhs(_)) and
819-
lastNode(getLhs(getNumLhs() - 1), result, normalCompletion())
820-
)
821-
or
822820
i = -1 and
823821
result = MkCompoundAssignRhsNode(this)
824822
or

ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/ControlFlowNode_getASuccessor.expected

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,7 @@
635635
| main.go:79:2:79:13 | call to Print | main.go:80:1:80:1 | exit |
636636
| main.go:79:12:79:12 | z | main.go:79:2:79:13 | call to Print |
637637
| main.go:82:1:82:1 | entry | main.go:82:18:82:18 | zero value for a |
638-
| main.go:82:1:86:1 | function declaration | main.go:0:0:0:0 | exit |
638+
| main.go:82:1:86:1 | function declaration | main.go:88:6:88:23 | skip |
639639
| main.go:82:6:82:13 | skip | main.go:82:1:86:1 | function declaration |
640640
| main.go:82:18:82:18 | implicit read of a | main.go:82:25:82:25 | implicit read of b |
641641
| main.go:82:18:82:18 | initialization of a | main.go:82:25:82:25 | zero value for b |
@@ -655,6 +655,36 @@
655655
| main.go:84:11:84:12 | 19 | main.go:84:9:84:12 | ...+... |
656656
| main.go:84:15:84:15 | x | main.go:84:2:84:2 | assignment to x |
657657
| main.go:85:2:85:7 | return statement | main.go:82:18:82:18 | implicit read of a |
658+
| main.go:88:1:88:1 | entry | main.go:88:25:88:25 | argument corresponding to x |
659+
| main.go:88:1:96:1 | function declaration | main.go:0:0:0:0 | exit |
660+
| main.go:88:6:88:23 | skip | main.go:88:1:96:1 | function declaration |
661+
| main.go:88:25:88:25 | argument corresponding to x | main.go:88:25:88:25 | initialization of x |
662+
| main.go:88:25:88:25 | initialization of x | main.go:89:2:89:2 | skip |
663+
| main.go:89:2:89:2 | assignment to a | main.go:89:5:89:5 | assignment to b |
664+
| main.go:89:2:89:2 | skip | main.go:89:5:89:5 | skip |
665+
| main.go:89:5:89:5 | assignment to b | main.go:90:5:90:8 | cond |
666+
| main.go:89:5:89:5 | skip | main.go:89:10:89:10 | x |
667+
| main.go:89:10:89:10 | x | main.go:89:13:89:13 | 0 |
668+
| main.go:89:13:89:13 | 0 | main.go:89:2:89:2 | assignment to a |
669+
| main.go:90:5:90:8 | cond | main.go:90:5:90:10 | call to cond |
670+
| main.go:90:5:90:10 | call to cond | main.go:90:10:90:10 | call to cond is false |
671+
| main.go:90:5:90:10 | call to cond | main.go:90:10:90:10 | call to cond is true |
672+
| main.go:90:5:90:10 | call to cond | main.go:96:1:96:1 | exit |
673+
| main.go:90:10:90:10 | call to cond is false | main.go:93:3:93:3 | skip |
674+
| main.go:90:10:90:10 | call to cond is true | main.go:91:3:91:3 | skip |
675+
| main.go:91:3:91:3 | assignment to a | main.go:95:9:95:9 | a |
676+
| main.go:91:3:91:3 | skip | main.go:91:6:91:6 | skip |
677+
| main.go:91:6:91:6 | skip | main.go:91:10:91:10 | b |
678+
| main.go:91:10:91:10 | b | main.go:91:13:91:13 | a |
679+
| main.go:91:13:91:13 | a | main.go:91:3:91:3 | assignment to a |
680+
| main.go:93:3:93:3 | skip | main.go:93:6:93:6 | skip |
681+
| main.go:93:6:93:6 | assignment to b | main.go:95:9:95:9 | a |
682+
| main.go:93:6:93:6 | skip | main.go:93:10:93:10 | b |
683+
| main.go:93:10:93:10 | b | main.go:93:13:93:13 | a |
684+
| main.go:93:13:93:13 | a | main.go:93:6:93:6 | assignment to b |
685+
| main.go:95:2:95:12 | return statement | main.go:96:1:96:1 | exit |
686+
| main.go:95:9:95:9 | a | main.go:95:12:95:12 | b |
687+
| main.go:95:12:95:12 | b | main.go:95:2:95:12 | return statement |
658688
| noretfunctions.go:0:0:0:0 | entry | noretfunctions.go:3:1:6:1 | skip |
659689
| noretfunctions.go:3:1:6:1 | skip | noretfunctions.go:8:6:8:12 | skip |
660690
| noretfunctions.go:8:1:8:1 | entry | noretfunctions.go:9:2:9:8 | selection of Exit |
@@ -734,7 +764,7 @@
734764
| stmts2.go:12:9:12:13 | ...+... | stmts2.go:12:2:12:13 | return statement |
735765
| stmts2.go:12:13:12:13 | y | stmts2.go:12:9:12:13 | ...+... |
736766
| stmts2.go:15:1:15:1 | entry | stmts2.go:15:13:15:14 | argument corresponding to ch |
737-
| stmts2.go:15:1:28:1 | function declaration | stmts2.go:0:0:0:0 | exit |
767+
| stmts2.go:15:1:28:1 | function declaration | stmts2.go:30:6:30:12 | skip |
738768
| stmts2.go:15:6:15:11 | skip | stmts2.go:15:1:28:1 | function declaration |
739769
| stmts2.go:15:13:15:14 | argument corresponding to ch | stmts2.go:15:13:15:14 | initialization of ch |
740770
| stmts2.go:15:13:15:14 | initialization of ch | stmts2.go:17:13:17:14 | ch |
@@ -778,6 +808,29 @@
778808
| stmts2.go:25:16:25:17 | ch | stmts2.go:16:2:26:2 | select statement |
779809
| stmts2.go:27:2:27:9 | return statement | stmts2.go:28:1:28:1 | exit |
780810
| stmts2.go:27:9:27:9 | 1 | stmts2.go:27:2:27:9 | return statement |
811+
| stmts2.go:30:1:30:1 | entry | stmts2.go:31:2:31:2 | skip |
812+
| stmts2.go:30:1:34:1 | function declaration | stmts2.go:0:0:0:0 | exit |
813+
| stmts2.go:30:6:30:12 | skip | stmts2.go:30:1:34:1 | function declaration |
814+
| stmts2.go:31:2:31:2 | assignment to x | stmts2.go:31:2:31:14 | ... := ...[1] |
815+
| stmts2.go:31:2:31:2 | skip | stmts2.go:31:5:31:5 | skip |
816+
| stmts2.go:31:2:31:14 | ... := ...[0] | stmts2.go:31:2:31:2 | assignment to x |
817+
| stmts2.go:31:2:31:14 | ... := ...[1] | stmts2.go:32:6:32:6 | skip |
818+
| stmts2.go:31:5:31:5 | skip | stmts2.go:31:10:31:12 | gen |
819+
| stmts2.go:31:10:31:12 | gen | stmts2.go:31:10:31:14 | call to gen |
820+
| stmts2.go:31:10:31:14 | call to gen | stmts2.go:31:2:31:14 | ... := ...[0] |
821+
| stmts2.go:31:10:31:14 | call to gen | stmts2.go:34:1:34:1 | exit |
822+
| stmts2.go:32:6:32:6 | assignment to y | stmts2.go:32:6:32:17 | value declaration specifier[1] |
823+
| stmts2.go:32:6:32:6 | skip | stmts2.go:32:9:32:9 | skip |
824+
| stmts2.go:32:6:32:17 | value declaration specifier[0] | stmts2.go:32:6:32:6 | assignment to y |
825+
| stmts2.go:32:6:32:17 | value declaration specifier[1] | stmts2.go:33:9:33:9 | x |
826+
| stmts2.go:32:9:32:9 | skip | stmts2.go:32:13:32:15 | gen |
827+
| stmts2.go:32:13:32:15 | gen | stmts2.go:32:13:32:17 | call to gen |
828+
| stmts2.go:32:13:32:17 | call to gen | stmts2.go:32:6:32:17 | value declaration specifier[0] |
829+
| stmts2.go:32:13:32:17 | call to gen | stmts2.go:34:1:34:1 | exit |
830+
| stmts2.go:33:2:33:13 | return statement | stmts2.go:34:1:34:1 | exit |
831+
| stmts2.go:33:9:33:9 | x | stmts2.go:33:13:33:13 | y |
832+
| stmts2.go:33:9:33:13 | ...+... | stmts2.go:33:2:33:13 | return statement |
833+
| stmts2.go:33:13:33:13 | y | stmts2.go:33:9:33:13 | ...+... |
781834
| stmts3.go:0:0:0:0 | entry | stmts3.go:3:1:3:13 | skip |
782835
| stmts3.go:3:1:3:13 | skip | stmts3.go:5:6:5:11 | skip |
783836
| stmts3.go:5:1:5:1 | entry | stmts3.go:7:3:7:5 | skip |
@@ -814,7 +867,7 @@
814867
| stmts3.go:19:22:19:23 | skip | stmts3.go:19:23:19:23 | exit |
815868
| stmts4.go:0:0:0:0 | entry | stmts4.go:3:5:3:5 | skip |
816869
| stmts4.go:3:5:3:5 | skip | stmts4.go:3:5:3:5 | zero value for _ |
817-
| stmts4.go:3:5:3:5 | skip | stmts4.go:5:6:5:11 | skip |
870+
| stmts4.go:3:5:3:5 | zero value for _ | stmts4.go:5:6:5:11 | skip |
818871
| stmts4.go:5:1:5:26 | function declaration | stmts4.go:0:0:0:0 | exit |
819872
| stmts4.go:5:6:5:11 | skip | stmts4.go:5:1:5:26 | function declaration |
820873
| stmts5.go:0:0:0:0 | entry | stmts5.go:3:1:5:1 | skip |

ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/main.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,13 @@ func multiRes() (a int, b float32) {
8484
x, a = x+19, x
8585
return
8686
}
87+
88+
func fooWithUnderscores(x int) (int, int) {
89+
a, b := x, 0
90+
if cond() {
91+
a, _ = b, a
92+
} else {
93+
_, b = b, a
94+
}
95+
return a, b
96+
}

ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts2.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,9 @@ func test14(ch chan int) int {
2626
}
2727
return 1
2828
}
29+
30+
func test13b() int {
31+
x, _ := gen()
32+
var y, _ = gen()
33+
return x + y
34+
}

ql/test/query-tests/RedundantCode/DeadStoreOfLocal/DeadStoreOfLocal.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@
2727
| testdata.go:488:3:488:3 | assignment to x | This definition of x is never used. |
2828
| testdata.go:542:3:542:3 | assignment to x | This definition of x is never used. |
2929
| testdata.go:580:4:580:4 | assignment to x | This definition of x is never used. |
30+
| testdata.go:629:3:629:4 | assignment to v1 | This definition of v1 is never used. |

ql/test/query-tests/RedundantCode/DeadStoreOfLocal/testdata.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,4 +610,26 @@ func _() {
610610
_ = x
611611
}
612612

613+
func _(v1, v2 int32) (int32, int32) {
614+
if v1 > v2 {
615+
v1, _ = v2, v1
616+
}
617+
return v1, v2
618+
}
619+
620+
func _(v1, v2 int32) (int32, int32) {
621+
if v1 > v2 {
622+
_, v1 = v2, v1
623+
}
624+
return v1, v2
625+
}
626+
627+
func _(v1, v2 int32) (int32, int32) {
628+
if v1 > v2 {
629+
v1, _ = v2, v1
630+
}
631+
v1, v2 = 0, 0
632+
return v1, v2
633+
}
634+
613635
func anyFunctionMightPanic()

0 commit comments

Comments
 (0)