From c241049384a6399ff447ccdf5c16e609ebb37e89 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Mon, 8 Jun 2026 15:26:19 +0100 Subject: [PATCH 1/9] Add control flow test for result read steps --- .../ControlFlowNode_getASuccessor.expected | 252 ++++++++++-------- .../go/controlflow/ControlFlowGraph/main.go | 10 + 2 files changed, 148 insertions(+), 114 deletions(-) diff --git a/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/ControlFlowNode_getASuccessor.expected b/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/ControlFlowNode_getASuccessor.expected index f3d27b4bf388..f14d1319795f 100644 --- a/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/ControlFlowNode_getASuccessor.expected +++ b/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/ControlFlowNode_getASuccessor.expected @@ -735,129 +735,153 @@ | main.go:48:11:48:12 | 42 | main.go:48:2:48:7 | assignment to result | | main.go:49:2:49:7 | return statement | main.go:47:13:47:18 | implicit read of result | | main.go:52:1:54:1 | entry | main.go:52:14:52:19 | zero value for result | -| main.go:52:1:54:1 | function declaration | main.go:56:6:56:10 | skip | +| main.go:52:1:54:1 | function declaration | main.go:56:6:56:9 | skip | | main.go:52:6:52:9 | skip | main.go:52:1:54:1 | function declaration | | main.go:52:14:52:19 | implicit read of result | main.go:52:1:54:1 | exit | | main.go:52:14:52:19 | initialization of result | main.go:53:2:53:7 | return statement | | main.go:52:14:52:19 | zero value for result | main.go:52:14:52:19 | initialization of result | | main.go:53:2:53:7 | return statement | main.go:52:14:52:19 | implicit read of result | -| main.go:56:1:80:1 | entry | main.go:57:6:57:6 | skip | -| main.go:56:1:80:1 | function declaration | main.go:82:6:82:13 | skip | -| main.go:56:6:56:10 | skip | main.go:56:1:80:1 | function declaration | -| main.go:57:6:57:6 | assignment to x | main.go:58:6:58:9 | cond | -| main.go:57:6:57:6 | skip | main.go:57:6:57:6 | zero value for x | -| main.go:57:6:57:6 | zero value for x | main.go:57:6:57:6 | assignment to x | -| main.go:58:6:58:9 | cond | main.go:58:6:58:11 | call to cond | -| main.go:58:6:58:11 | call to cond | main.go:56:1:80:1 | exit | -| main.go:58:6:58:11 | call to cond | main.go:58:6:58:11 | call to cond is false | -| main.go:58:6:58:11 | call to cond | main.go:58:6:58:11 | call to cond is true | -| main.go:58:6:58:11 | call to cond is false | main.go:61:2:61:10 | selection of Print | -| main.go:58:6:58:11 | call to cond is true | main.go:59:3:59:3 | skip | -| main.go:59:3:59:3 | assignment to x | main.go:58:6:58:9 | cond | -| main.go:59:3:59:3 | skip | main.go:59:7:59:7 | 2 | -| main.go:59:7:59:7 | 2 | main.go:59:3:59:3 | assignment to x | -| main.go:61:2:61:10 | selection of Print | main.go:61:12:61:12 | x | -| main.go:61:2:61:13 | call to Print | main.go:56:1:80:1 | exit | -| main.go:61:2:61:13 | call to Print | main.go:63:2:63:2 | skip | -| main.go:61:12:61:12 | x | main.go:61:2:61:13 | call to Print | -| main.go:63:2:63:2 | assignment to y | main.go:64:6:64:6 | skip | -| main.go:63:2:63:2 | skip | main.go:63:7:63:7 | 1 | -| main.go:63:7:63:7 | 1 | main.go:63:2:63:2 | assignment to y | -| main.go:64:6:64:6 | assignment to i | main.go:65:6:65:9 | cond | -| main.go:64:6:64:6 | skip | main.go:64:11:64:11 | 0 | -| main.go:64:11:64:11 | 0 | main.go:64:6:64:6 | assignment to i | -| main.go:64:16:64:16 | i | main.go:64:16:64:18 | 1 | -| main.go:64:16:64:18 | 1 | main.go:64:16:64:18 | rhs of increment statement | -| main.go:64:16:64:18 | increment statement | main.go:65:6:65:9 | cond | -| main.go:64:16:64:18 | rhs of increment statement | main.go:64:16:64:18 | increment statement | -| main.go:65:6:65:9 | cond | main.go:65:6:65:11 | call to cond | -| main.go:65:6:65:11 | call to cond | main.go:56:1:80:1 | exit | -| main.go:65:6:65:11 | call to cond | main.go:65:6:65:11 | call to cond is false | -| main.go:65:6:65:11 | call to cond | main.go:65:6:65:11 | call to cond is true | -| main.go:65:6:65:11 | call to cond is false | main.go:68:3:68:3 | skip | -| main.go:65:6:65:11 | call to cond is true | main.go:66:4:66:8 | skip | -| main.go:66:4:66:8 | skip | main.go:70:2:70:10 | selection of Print | -| main.go:68:3:68:3 | assignment to y | main.go:64:16:64:16 | i | -| main.go:68:3:68:3 | skip | main.go:68:7:68:7 | 2 | -| main.go:68:7:68:7 | 2 | main.go:68:3:68:3 | assignment to y | -| main.go:70:2:70:10 | selection of Print | main.go:70:12:70:12 | y | -| main.go:70:2:70:13 | call to Print | main.go:56:1:80:1 | exit | -| main.go:70:2:70:13 | call to Print | main.go:72:2:72:2 | skip | -| main.go:70:12:70:12 | y | main.go:70:2:70:13 | call to Print | -| main.go:72:2:72:2 | assignment to z | main.go:73:6:73:6 | skip | -| main.go:72:2:72:2 | skip | main.go:72:7:72:7 | 1 | -| main.go:72:7:72:7 | 1 | main.go:72:2:72:2 | assignment to z | -| main.go:73:6:73:6 | assignment to i | main.go:74:3:74:3 | skip | -| main.go:73:6:73:6 | skip | main.go:73:11:73:11 | 0 | -| main.go:73:11:73:11 | 0 | main.go:73:6:73:6 | assignment to i | -| main.go:73:16:73:16 | i | main.go:73:16:73:18 | 1 | -| main.go:73:16:73:18 | 1 | main.go:73:16:73:18 | rhs of increment statement | -| main.go:73:16:73:18 | increment statement | main.go:74:3:74:3 | skip | -| main.go:73:16:73:18 | rhs of increment statement | main.go:73:16:73:18 | increment statement | -| main.go:74:3:74:3 | assignment to z | main.go:75:6:75:9 | cond | -| main.go:74:3:74:3 | skip | main.go:74:7:74:7 | 2 | -| main.go:74:7:74:7 | 2 | main.go:74:3:74:3 | assignment to z | +| main.go:56:1:64:1 | entry | main.go:56:11:56:18 | argument corresponding to selector | +| main.go:56:1:64:1 | function declaration | main.go:66:6:66:10 | skip | +| main.go:56:6:56:9 | skip | main.go:56:1:64:1 | function declaration | +| main.go:56:11:56:18 | argument corresponding to selector | main.go:56:11:56:18 | initialization of selector | +| main.go:56:11:56:18 | initialization of selector | main.go:56:26:56:31 | zero value for result | +| main.go:56:26:56:31 | implicit read of result | main.go:56:1:64:1 | exit | +| main.go:56:26:56:31 | initialization of result | main.go:57:2:57:7 | skip | +| main.go:56:26:56:31 | zero value for result | main.go:56:26:56:31 | initialization of result | +| main.go:57:2:57:7 | assignment to result | main.go:58:5:58:12 | selector | +| main.go:57:2:57:7 | skip | main.go:57:11:57:11 | 0 | +| main.go:57:11:57:11 | 0 | main.go:57:2:57:7 | assignment to result | +| main.go:58:5:58:12 | selector | main.go:58:17:58:17 | 1 | +| main.go:58:5:58:17 | ...==... | main.go:58:5:58:17 | ...==... is false | +| main.go:58:5:58:17 | ...==... | main.go:58:5:58:17 | ...==... is true | +| main.go:58:5:58:17 | ...==... is false | main.go:61:3:61:8 | skip | +| main.go:58:5:58:17 | ...==... is true | main.go:59:10:59:10 | 1 | +| main.go:58:17:58:17 | 1 | main.go:58:5:58:17 | ...==... | +| main.go:59:3:59:10 | return statement | main.go:56:26:56:31 | implicit read of result | +| main.go:59:10:59:10 | 1 | main.go:59:10:59:10 | implicit write of result | +| main.go:59:10:59:10 | implicit write of result | main.go:59:3:59:10 | return statement | +| main.go:61:3:61:8 | assignment to result | main.go:63:2:63:7 | return statement | +| main.go:61:3:61:8 | skip | main.go:61:12:61:12 | 2 | +| main.go:61:12:61:12 | 2 | main.go:61:3:61:8 | assignment to result | +| main.go:63:2:63:7 | return statement | main.go:56:26:56:31 | implicit read of result | +| main.go:66:1:90:1 | entry | main.go:67:6:67:6 | skip | +| main.go:66:1:90:1 | function declaration | main.go:92:6:92:13 | skip | +| main.go:66:6:66:10 | skip | main.go:66:1:90:1 | function declaration | +| main.go:67:6:67:6 | assignment to x | main.go:68:6:68:9 | cond | +| main.go:67:6:67:6 | skip | main.go:67:6:67:6 | zero value for x | +| main.go:67:6:67:6 | zero value for x | main.go:67:6:67:6 | assignment to x | +| main.go:68:6:68:9 | cond | main.go:68:6:68:11 | call to cond | +| main.go:68:6:68:11 | call to cond | main.go:66:1:90:1 | exit | +| main.go:68:6:68:11 | call to cond | main.go:68:6:68:11 | call to cond is false | +| main.go:68:6:68:11 | call to cond | main.go:68:6:68:11 | call to cond is true | +| main.go:68:6:68:11 | call to cond is false | main.go:71:2:71:10 | selection of Print | +| main.go:68:6:68:11 | call to cond is true | main.go:69:3:69:3 | skip | +| main.go:69:3:69:3 | assignment to x | main.go:68:6:68:9 | cond | +| main.go:69:3:69:3 | skip | main.go:69:7:69:7 | 2 | +| main.go:69:7:69:7 | 2 | main.go:69:3:69:3 | assignment to x | +| main.go:71:2:71:10 | selection of Print | main.go:71:12:71:12 | x | +| main.go:71:2:71:13 | call to Print | main.go:66:1:90:1 | exit | +| main.go:71:2:71:13 | call to Print | main.go:73:2:73:2 | skip | +| main.go:71:12:71:12 | x | main.go:71:2:71:13 | call to Print | +| main.go:73:2:73:2 | assignment to y | main.go:74:6:74:6 | skip | +| main.go:73:2:73:2 | skip | main.go:73:7:73:7 | 1 | +| main.go:73:7:73:7 | 1 | main.go:73:2:73:2 | assignment to y | +| main.go:74:6:74:6 | assignment to i | main.go:75:6:75:9 | cond | +| main.go:74:6:74:6 | skip | main.go:74:11:74:11 | 0 | +| main.go:74:11:74:11 | 0 | main.go:74:6:74:6 | assignment to i | +| main.go:74:16:74:16 | i | main.go:74:16:74:18 | 1 | +| main.go:74:16:74:18 | 1 | main.go:74:16:74:18 | rhs of increment statement | +| main.go:74:16:74:18 | increment statement | main.go:75:6:75:9 | cond | +| main.go:74:16:74:18 | rhs of increment statement | main.go:74:16:74:18 | increment statement | | main.go:75:6:75:9 | cond | main.go:75:6:75:11 | call to cond | -| main.go:75:6:75:11 | call to cond | main.go:56:1:80:1 | exit | +| main.go:75:6:75:11 | call to cond | main.go:66:1:90:1 | exit | | main.go:75:6:75:11 | call to cond | main.go:75:6:75:11 | call to cond is false | | main.go:75:6:75:11 | call to cond | main.go:75:6:75:11 | call to cond is true | -| main.go:75:6:75:11 | call to cond is false | main.go:73:16:73:16 | i | +| main.go:75:6:75:11 | call to cond is false | main.go:78:3:78:3 | skip | | main.go:75:6:75:11 | call to cond is true | main.go:76:4:76:8 | skip | -| main.go:76:4:76:8 | skip | main.go:79:2:79:10 | selection of Print | -| main.go:79:2:79:10 | selection of Print | main.go:79:12:79:12 | z | -| main.go:79:2:79:13 | call to Print | main.go:56:1:80:1 | exit | -| main.go:79:12:79:12 | z | main.go:79:2:79:13 | call to Print | -| main.go:82:1:86:1 | entry | main.go:82:18:82:18 | zero value for a | -| main.go:82:1:86:1 | function declaration | main.go:88:6:88:23 | skip | -| main.go:82:6:82:13 | skip | main.go:82:1:86:1 | function declaration | -| main.go:82:18:82:18 | implicit read of a | main.go:82:25:82:25 | implicit read of b | -| main.go:82:18:82:18 | initialization of a | main.go:82:25:82:25 | zero value for b | -| main.go:82:18:82:18 | zero value for a | main.go:82:18:82:18 | initialization of a | -| main.go:82:25:82:25 | implicit read of b | main.go:82:1:86:1 | exit | -| main.go:82:25:82:25 | initialization of b | main.go:83:2:83:2 | skip | -| main.go:82:25:82:25 | zero value for b | main.go:82:25:82:25 | initialization of b | -| main.go:83:2:83:2 | assignment to x | main.go:84:2:84:2 | skip | -| main.go:83:2:83:2 | skip | main.go:83:7:83:8 | 23 | -| main.go:83:7:83:8 | 23 | main.go:83:2:83:2 | assignment to x | -| main.go:84:2:84:2 | assignment to x | main.go:84:5:84:5 | assignment to a | -| main.go:84:2:84:2 | skip | main.go:84:5:84:5 | skip | -| main.go:84:5:84:5 | assignment to a | main.go:85:2:85:7 | return statement | -| main.go:84:5:84:5 | skip | main.go:84:9:84:9 | x | -| main.go:84:9:84:9 | x | main.go:84:11:84:12 | 19 | -| main.go:84:9:84:12 | ...+... | main.go:84:15:84:15 | x | -| main.go:84:11:84:12 | 19 | main.go:84:9:84:12 | ...+... | -| main.go:84:15:84:15 | x | main.go:84:2:84:2 | assignment to x | -| main.go:85:2:85:7 | return statement | main.go:82:18:82:18 | implicit read of a | -| main.go:88:1:96:1 | entry | main.go:88:25:88:25 | argument corresponding to x | -| main.go:88:1:96:1 | function declaration | main.go:0:0:0:0 | exit | -| main.go:88:6:88:23 | skip | main.go:88:1:96:1 | function declaration | -| main.go:88:25:88:25 | argument corresponding to x | main.go:88:25:88:25 | initialization of x | -| main.go:88:25:88:25 | initialization of x | main.go:89:2:89:2 | skip | -| main.go:89:2:89:2 | assignment to a | main.go:89:5:89:5 | assignment to b | -| main.go:89:2:89:2 | skip | main.go:89:5:89:5 | skip | -| main.go:89:5:89:5 | assignment to b | main.go:90:5:90:8 | cond | -| main.go:89:5:89:5 | skip | main.go:89:10:89:10 | x | -| main.go:89:10:89:10 | x | main.go:89:13:89:13 | 0 | -| main.go:89:13:89:13 | 0 | main.go:89:2:89:2 | assignment to a | -| main.go:90:5:90:8 | cond | main.go:90:5:90:10 | call to cond | -| main.go:90:5:90:10 | call to cond | main.go:88:1:96:1 | exit | -| main.go:90:5:90:10 | call to cond | main.go:90:5:90:10 | call to cond is false | -| main.go:90:5:90:10 | call to cond | main.go:90:5:90:10 | call to cond is true | -| main.go:90:5:90:10 | call to cond is false | main.go:93:3:93:3 | skip | -| main.go:90:5:90:10 | call to cond is true | main.go:91:3:91:3 | skip | -| main.go:91:3:91:3 | assignment to a | main.go:95:9:95:9 | a | -| main.go:91:3:91:3 | skip | main.go:91:6:91:6 | skip | -| main.go:91:6:91:6 | skip | main.go:91:10:91:10 | b | -| main.go:91:10:91:10 | b | main.go:91:13:91:13 | a | -| main.go:91:13:91:13 | a | main.go:91:3:91:3 | assignment to a | -| main.go:93:3:93:3 | skip | main.go:93:6:93:6 | skip | -| main.go:93:6:93:6 | assignment to b | main.go:95:9:95:9 | a | -| main.go:93:6:93:6 | skip | main.go:93:10:93:10 | b | -| main.go:93:10:93:10 | b | main.go:93:13:93:13 | a | -| main.go:93:13:93:13 | a | main.go:93:6:93:6 | assignment to b | -| main.go:95:2:95:12 | return statement | main.go:88:1:96:1 | exit | -| main.go:95:9:95:9 | a | main.go:95:12:95:12 | b | -| main.go:95:12:95:12 | b | main.go:95:2:95:12 | return statement | +| main.go:76:4:76:8 | skip | main.go:80:2:80:10 | selection of Print | +| main.go:78:3:78:3 | assignment to y | main.go:74:16:74:16 | i | +| main.go:78:3:78:3 | skip | main.go:78:7:78:7 | 2 | +| main.go:78:7:78:7 | 2 | main.go:78:3:78:3 | assignment to y | +| main.go:80:2:80:10 | selection of Print | main.go:80:12:80:12 | y | +| main.go:80:2:80:13 | call to Print | main.go:66:1:90:1 | exit | +| main.go:80:2:80:13 | call to Print | main.go:82:2:82:2 | skip | +| main.go:80:12:80:12 | y | main.go:80:2:80:13 | call to Print | +| main.go:82:2:82:2 | assignment to z | main.go:83:6:83:6 | skip | +| main.go:82:2:82:2 | skip | main.go:82:7:82:7 | 1 | +| main.go:82:7:82:7 | 1 | main.go:82:2:82:2 | assignment to z | +| main.go:83:6:83:6 | assignment to i | main.go:84:3:84:3 | skip | +| main.go:83:6:83:6 | skip | main.go:83:11:83:11 | 0 | +| main.go:83:11:83:11 | 0 | main.go:83:6:83:6 | assignment to i | +| main.go:83:16:83:16 | i | main.go:83:16:83:18 | 1 | +| main.go:83:16:83:18 | 1 | main.go:83:16:83:18 | rhs of increment statement | +| main.go:83:16:83:18 | increment statement | main.go:84:3:84:3 | skip | +| main.go:83:16:83:18 | rhs of increment statement | main.go:83:16:83:18 | increment statement | +| main.go:84:3:84:3 | assignment to z | main.go:85:6:85:9 | cond | +| main.go:84:3:84:3 | skip | main.go:84:7:84:7 | 2 | +| main.go:84:7:84:7 | 2 | main.go:84:3:84:3 | assignment to z | +| main.go:85:6:85:9 | cond | main.go:85:6:85:11 | call to cond | +| main.go:85:6:85:11 | call to cond | main.go:66:1:90:1 | exit | +| main.go:85:6:85:11 | call to cond | main.go:85:6:85:11 | call to cond is false | +| main.go:85:6:85:11 | call to cond | main.go:85:6:85:11 | call to cond is true | +| main.go:85:6:85:11 | call to cond is false | main.go:83:16:83:16 | i | +| main.go:85:6:85:11 | call to cond is true | main.go:86:4:86:8 | skip | +| main.go:86:4:86:8 | skip | main.go:89:2:89:10 | selection of Print | +| main.go:89:2:89:10 | selection of Print | main.go:89:12:89:12 | z | +| main.go:89:2:89:13 | call to Print | main.go:66:1:90:1 | exit | +| main.go:89:12:89:12 | z | main.go:89:2:89:13 | call to Print | +| main.go:92:1:96:1 | entry | main.go:92:18:92:18 | zero value for a | +| main.go:92:1:96:1 | function declaration | main.go:98:6:98:23 | skip | +| main.go:92:6:92:13 | skip | main.go:92:1:96:1 | function declaration | +| main.go:92:18:92:18 | implicit read of a | main.go:92:25:92:25 | implicit read of b | +| main.go:92:18:92:18 | initialization of a | main.go:92:25:92:25 | zero value for b | +| main.go:92:18:92:18 | zero value for a | main.go:92:18:92:18 | initialization of a | +| main.go:92:25:92:25 | implicit read of b | main.go:92:1:96:1 | exit | +| main.go:92:25:92:25 | initialization of b | main.go:93:2:93:2 | skip | +| main.go:92:25:92:25 | zero value for b | main.go:92:25:92:25 | initialization of b | +| main.go:93:2:93:2 | assignment to x | main.go:94:2:94:2 | skip | +| main.go:93:2:93:2 | skip | main.go:93:7:93:8 | 23 | +| main.go:93:7:93:8 | 23 | main.go:93:2:93:2 | assignment to x | +| main.go:94:2:94:2 | assignment to x | main.go:94:5:94:5 | assignment to a | +| main.go:94:2:94:2 | skip | main.go:94:5:94:5 | skip | +| main.go:94:5:94:5 | assignment to a | main.go:95:2:95:7 | return statement | +| main.go:94:5:94:5 | skip | main.go:94:9:94:9 | x | +| main.go:94:9:94:9 | x | main.go:94:11:94:12 | 19 | +| main.go:94:9:94:12 | ...+... | main.go:94:15:94:15 | x | +| main.go:94:11:94:12 | 19 | main.go:94:9:94:12 | ...+... | +| main.go:94:15:94:15 | x | main.go:94:2:94:2 | assignment to x | +| main.go:95:2:95:7 | return statement | main.go:92:18:92:18 | implicit read of a | +| main.go:98:1:106:1 | entry | main.go:98:25:98:25 | argument corresponding to x | +| main.go:98:1:106:1 | function declaration | main.go:0:0:0:0 | exit | +| main.go:98:6:98:23 | skip | main.go:98:1:106:1 | function declaration | +| main.go:98:25:98:25 | argument corresponding to x | main.go:98:25:98:25 | initialization of x | +| main.go:98:25:98:25 | initialization of x | main.go:99:2:99:2 | skip | +| main.go:99:2:99:2 | assignment to a | main.go:99:5:99:5 | assignment to b | +| main.go:99:2:99:2 | skip | main.go:99:5:99:5 | skip | +| main.go:99:5:99:5 | assignment to b | main.go:100:5:100:8 | cond | +| main.go:99:5:99:5 | skip | main.go:99:10:99:10 | x | +| main.go:99:10:99:10 | x | main.go:99:13:99:13 | 0 | +| main.go:99:13:99:13 | 0 | main.go:99:2:99:2 | assignment to a | +| main.go:100:5:100:8 | cond | main.go:100:5:100:10 | call to cond | +| main.go:100:5:100:10 | call to cond | main.go:98:1:106:1 | exit | +| main.go:100:5:100:10 | call to cond | main.go:100:5:100:10 | call to cond is false | +| main.go:100:5:100:10 | call to cond | main.go:100:5:100:10 | call to cond is true | +| main.go:100:5:100:10 | call to cond is false | main.go:103:3:103:3 | skip | +| main.go:100:5:100:10 | call to cond is true | main.go:101:3:101:3 | skip | +| main.go:101:3:101:3 | assignment to a | main.go:105:9:105:9 | a | +| main.go:101:3:101:3 | skip | main.go:101:6:101:6 | skip | +| main.go:101:6:101:6 | skip | main.go:101:10:101:10 | b | +| main.go:101:10:101:10 | b | main.go:101:13:101:13 | a | +| main.go:101:13:101:13 | a | main.go:101:3:101:3 | assignment to a | +| main.go:103:3:103:3 | skip | main.go:103:6:103:6 | skip | +| main.go:103:6:103:6 | assignment to b | main.go:105:9:105:9 | a | +| main.go:103:6:103:6 | skip | main.go:103:10:103:10 | b | +| main.go:103:10:103:10 | b | main.go:103:13:103:13 | a | +| main.go:103:13:103:13 | a | main.go:103:6:103:6 | assignment to b | +| main.go:105:2:105:12 | return statement | main.go:98:1:106:1 | exit | +| main.go:105:9:105:9 | a | main.go:105:12:105:12 | b | +| main.go:105:12:105:12 | b | main.go:105:2:105:12 | return statement | | noretfunctions.go:0:0:0:0 | entry | noretfunctions.go:3:1:6:1 | skip | | noretfunctions.go:3:1:6:1 | skip | noretfunctions.go:8:6:8:12 | skip | | noretfunctions.go:8:1:10:1 | entry | noretfunctions.go:9:2:9:8 | selection of Exit | diff --git a/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/main.go b/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/main.go index 7345560670ba..9bef6a909ea8 100644 --- a/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/main.go +++ b/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/main.go @@ -53,6 +53,16 @@ func baz2() (result int) { return } +func baz3(selector int) (result int) { + result = 0 + if selector == 1 { + return 1 + } else { + result = 2 + } + return +} + func loops() { var x int for cond() { From 1c47084479ea040ecf3920019a3718542fa1a7a6 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Mon, 8 Jun 2026 15:43:58 +0100 Subject: [PATCH 2/9] Add result node test with SPURIOUS result --- .../go/dataflow/Nodes/ResultNode.expected | 9 +++++++ .../semmle/go/dataflow/Nodes/ResultNode.ql | 9 +++++++ .../semmle/go/dataflow/Nodes/ResultNode.qlref | 2 ++ .../semmle/go/dataflow/Nodes/main.go | 2 +- .../go/dataflow/Nodes/resultParameters.go | 27 +++++++++++++++++++ 5 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 go/ql/test/library-tests/semmle/go/dataflow/Nodes/ResultNode.expected create mode 100644 go/ql/test/library-tests/semmle/go/dataflow/Nodes/ResultNode.ql create mode 100644 go/ql/test/library-tests/semmle/go/dataflow/Nodes/ResultNode.qlref create mode 100644 go/ql/test/library-tests/semmle/go/dataflow/Nodes/resultParameters.go diff --git a/go/ql/test/library-tests/semmle/go/dataflow/Nodes/ResultNode.expected b/go/ql/test/library-tests/semmle/go/dataflow/Nodes/ResultNode.expected new file mode 100644 index 000000000000..ae728dd5939d --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/Nodes/ResultNode.expected @@ -0,0 +1,9 @@ +| main.go:21:9:21:10 | 23 | Result node with index 0 | +| main.go:21:13:21:14 | 42 | Result node with index 1 | +| resultParameters.go:5:10:5:10 | 0 | Result node with index 0 | +| resultParameters.go:9:10:9:10 | 1 | Result node with index 0 | +| resultParameters.go:11:10:11:10 | 2 | Result node with index 0 | +| resultParameters.go:13:9:13:9 | 3 | Result node with index 0 | +| resultParameters.go:16:26:16:26 | implicit read of r | Result node with index 0 | +| resultParameters.go:21:38:21:38 | implicit read of r | Result node with index 0 | +| resultParameters.go:24:10:24:10 | 1 | Result node with index 0 | diff --git a/go/ql/test/library-tests/semmle/go/dataflow/Nodes/ResultNode.ql b/go/ql/test/library-tests/semmle/go/dataflow/Nodes/ResultNode.ql new file mode 100644 index 000000000000..f2afd3ea4304 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/Nodes/ResultNode.ql @@ -0,0 +1,9 @@ +/** + * @kind problem + * @id result-node + */ + +import go + +from DataFlow::ResultNode r +select r, "Result node with index " + r.getIndex() diff --git a/go/ql/test/library-tests/semmle/go/dataflow/Nodes/ResultNode.qlref b/go/ql/test/library-tests/semmle/go/dataflow/Nodes/ResultNode.qlref new file mode 100644 index 000000000000..effcf9ce1d9e --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/Nodes/ResultNode.qlref @@ -0,0 +1,2 @@ +query: ResultNode.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/go/ql/test/library-tests/semmle/go/dataflow/Nodes/main.go b/go/ql/test/library-tests/semmle/go/dataflow/Nodes/main.go index 1fb3466820c5..dcfb9fb8c04b 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/Nodes/main.go +++ b/go/ql/test/library-tests/semmle/go/dataflow/Nodes/main.go @@ -18,5 +18,5 @@ func f() { } func test() (int, int) { - return 23, 42 + return 23, 42 // $ Alert[result-node] } diff --git a/go/ql/test/library-tests/semmle/go/dataflow/Nodes/resultParameters.go b/go/ql/test/library-tests/semmle/go/dataflow/Nodes/resultParameters.go new file mode 100644 index 000000000000..af48b1be5403 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/Nodes/resultParameters.go @@ -0,0 +1,27 @@ +package main + +func multipleReturns(selector int) int { + if selector == 0 { + return 0 // $ Alert[result-node] + } + switch selector { + case 1: + return 1 // $ Alert[result-node] + case 2: + return 2 // $ Alert[result-node] + } + return 3 // $ Alert[result-node] +} + +func resultParameter1() (r int) { // $ Alert[result-node] // implicit reads of result parameters are located at the result parameter declaration + r = 0 + return +} + +func resultParameter2(selector int) (r int) { // $ Alert[result-node] // implicit reads of result parameters are located at the result parameter declaration + r = 0 + if selector == 1 { + return 1 // $ SPURIOUS: Alert[result-node] + } + return +} From f4f17b01c168c77b1a48f8321d628acb4624a826 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Mon, 8 Jun 2026 15:54:27 +0100 Subject: [PATCH 3/9] Fix result node and remove SPURIOUS test result --- go/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll | 4 ++++ .../semmle/go/dataflow/Nodes/BinaryOperationNodes.expected | 2 ++ .../semmle/go/dataflow/Nodes/ResultNode.expected | 1 - .../semmle/go/dataflow/Nodes/resultParameters.go | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll index 603da6364df7..90c216545566 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll @@ -932,6 +932,10 @@ module Public { ResultNode() { exists(FuncDef fd | + // When a function has any (named) result variables, then the + // `ReadResultInstruction`s at the end of the function are the correct + // result nodes. + not exists(fd.getAResultVar()) and exists(IR::ReturnInstruction ret | ret.getRoot() = fd | insn = ret.getResult(i)) or insn.(IR::ReadResultInstruction).reads(fd.getResultVar(i)) diff --git a/go/ql/test/library-tests/semmle/go/dataflow/Nodes/BinaryOperationNodes.expected b/go/ql/test/library-tests/semmle/go/dataflow/Nodes/BinaryOperationNodes.expected index 9d996bdd020d..9315b269125d 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/Nodes/BinaryOperationNodes.expected +++ b/go/ql/test/library-tests/semmle/go/dataflow/Nodes/BinaryOperationNodes.expected @@ -2,3 +2,5 @@ | main.go:7:19:7:23 | ...+... | + | main.go:7:19:7:19 | y | main.go:7:23:7:23 | z | | main.go:10:14:10:18 | ...+... | + | main.go:10:14:10:14 | x | main.go:10:18:10:18 | y | | main.go:17:2:17:13 | ... += ... | + | main.go:17:2:17:6 | index expression | main.go:17:11:17:13 | "!" | +| resultParameters.go:4:5:4:17 | ...==... | == | resultParameters.go:4:5:4:12 | selector | resultParameters.go:4:17:4:17 | 0 | +| resultParameters.go:23:5:23:17 | ...==... | == | resultParameters.go:23:5:23:12 | selector | resultParameters.go:23:17:23:17 | 1 | diff --git a/go/ql/test/library-tests/semmle/go/dataflow/Nodes/ResultNode.expected b/go/ql/test/library-tests/semmle/go/dataflow/Nodes/ResultNode.expected index ae728dd5939d..093fcdbdae13 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/Nodes/ResultNode.expected +++ b/go/ql/test/library-tests/semmle/go/dataflow/Nodes/ResultNode.expected @@ -6,4 +6,3 @@ | resultParameters.go:13:9:13:9 | 3 | Result node with index 0 | | resultParameters.go:16:26:16:26 | implicit read of r | Result node with index 0 | | resultParameters.go:21:38:21:38 | implicit read of r | Result node with index 0 | -| resultParameters.go:24:10:24:10 | 1 | Result node with index 0 | diff --git a/go/ql/test/library-tests/semmle/go/dataflow/Nodes/resultParameters.go b/go/ql/test/library-tests/semmle/go/dataflow/Nodes/resultParameters.go index af48b1be5403..c404b8199142 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/Nodes/resultParameters.go +++ b/go/ql/test/library-tests/semmle/go/dataflow/Nodes/resultParameters.go @@ -21,7 +21,7 @@ func resultParameter1() (r int) { // $ Alert[result-node] // implicit reads of r func resultParameter2(selector int) (r int) { // $ Alert[result-node] // implicit reads of result parameters are located at the result parameter declaration r = 0 if selector == 1 { - return 1 // $ SPURIOUS: Alert[result-node] + return 1 } return } From da777a455d50bc9e334f4148e80b9c9c0eeedecf Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Mon, 8 Jun 2026 23:09:45 +0200 Subject: [PATCH 4/9] Improve QLDoc --- go/ql/lib/semmle/go/Expr.qll | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/go/ql/lib/semmle/go/Expr.qll b/go/ql/lib/semmle/go/Expr.qll index 0dcc707b19d8..d50456411fd5 100644 --- a/go/ql/lib/semmle/go/Expr.qll +++ b/go/ql/lib/semmle/go/Expr.qll @@ -1049,16 +1049,28 @@ class FuncTypeExpr extends @functypeexpr, TypeExpr, ScopeNode, FieldParent { */ int getNumParameter() { result = count(this.getAParameterDecl().getANameExpr()) } - /** Gets the `i`th result of this function type (0-based). */ + /** + * Gets the `i`th result declaration of this function type (0-based). + * + * Note: `x, y int` is a single `ResultVariableDecl`. + */ ResultVariableDecl getResultDecl(int i) { result = this.getField(-(i + 1)) } - /** Gets a result of this function type. */ + /** + * Gets a result declaration of this function type. + * + * Note: `x, y int` is a single `ResultVariableDecl`. + */ ResultVariableDecl getAResultDecl() { result = this.getResultDecl(_) } /** Gets the number of results of this function type. */ int getNumResult() { result = count(this.getAResultDecl()) } - /** Gets the result of this function type, if there is only one. */ + /** + * Gets the result of this function type, if there is only one. + * + * Note: `x, y int` is a single `ResultVariableDecl`. + */ ResultVariableDecl getResultDecl() { this.getNumResult() = 1 and result = this.getAResultDecl() } override string toString() { result = "function type" } From 8ce543bf4ddc2127abf249c7042347a55314c296 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Mon, 8 Jun 2026 23:15:55 +0200 Subject: [PATCH 5/9] Fix: `getNumResult()` was wrong in some cases It was the number of result declarations, which is different from the number of results when one result declaration declares more than one variable, as in `x, y int`. --- go/ql/lib/semmle/go/Expr.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go/ql/lib/semmle/go/Expr.qll b/go/ql/lib/semmle/go/Expr.qll index d50456411fd5..0d3955a80df5 100644 --- a/go/ql/lib/semmle/go/Expr.qll +++ b/go/ql/lib/semmle/go/Expr.qll @@ -1063,8 +1063,8 @@ class FuncTypeExpr extends @functypeexpr, TypeExpr, ScopeNode, FieldParent { */ ResultVariableDecl getAResultDecl() { result = this.getResultDecl(_) } - /** Gets the number of results of this function type. */ - int getNumResult() { result = count(this.getAResultDecl()) } + /** Gets the number of result parameters of this function type. */ + int getNumResult() { result = count(this.getAResultDecl().getANameExpr()) } /** * Gets the result of this function type, if there is only one. From a92349683e1f32f02dd9ef2c2aaaac45023d77a9 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Mon, 8 Jun 2026 23:18:37 +0200 Subject: [PATCH 6/9] Deprecate `FuncTypeExpr.getResultDecl()` It is unused in this library. It could easily be used incorrectly and silently omit results when `getNumResult() > 1`. --- go/ql/lib/semmle/go/Expr.qll | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/go/ql/lib/semmle/go/Expr.qll b/go/ql/lib/semmle/go/Expr.qll index 0d3955a80df5..9a8481a2dcc8 100644 --- a/go/ql/lib/semmle/go/Expr.qll +++ b/go/ql/lib/semmle/go/Expr.qll @@ -1067,11 +1067,11 @@ class FuncTypeExpr extends @functypeexpr, TypeExpr, ScopeNode, FieldParent { int getNumResult() { result = count(this.getAResultDecl().getANameExpr()) } /** - * Gets the result of this function type, if there is only one. - * - * Note: `x, y int` is a single `ResultVariableDecl`. + * DEPRECATED: Use `getResultDecl(int i)` instead. */ - ResultVariableDecl getResultDecl() { this.getNumResult() = 1 and result = this.getAResultDecl() } + deprecated ResultVariableDecl getResultDecl() { + this.getNumResult() = 1 and result = this.getAResultDecl() + } override string toString() { result = "function type" } From 071a0e3d7d5185c3367361c847c51813fb9234de Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Mon, 8 Jun 2026 23:28:46 +0200 Subject: [PATCH 7/9] Add change notes --- .../2026-06-08-deprecate-functypeexpr-getresultdecl.md | 4 ++++ go/ql/lib/change-notes/2026-06-08-fix-result-nodes.md | 4 ++++ .../lib/change-notes/2026-06-08-functypeexpr-getnumresult.md | 4 ++++ 3 files changed, 12 insertions(+) create mode 100644 go/ql/lib/change-notes/2026-06-08-deprecate-functypeexpr-getresultdecl.md create mode 100644 go/ql/lib/change-notes/2026-06-08-fix-result-nodes.md create mode 100644 go/ql/lib/change-notes/2026-06-08-functypeexpr-getnumresult.md diff --git a/go/ql/lib/change-notes/2026-06-08-deprecate-functypeexpr-getresultdecl.md b/go/ql/lib/change-notes/2026-06-08-deprecate-functypeexpr-getresultdecl.md new file mode 100644 index 000000000000..157fa33bf6ad --- /dev/null +++ b/go/ql/lib/change-notes/2026-06-08-deprecate-functypeexpr-getresultdecl.md @@ -0,0 +1,4 @@ +--- +category: deprecated +--- +* `FuncTypeExpr.getResultDecl()` has been deprecated. Use `FuncTypeExpr.getResultDecl(int i)` instead. diff --git a/go/ql/lib/change-notes/2026-06-08-fix-result-nodes.md b/go/ql/lib/change-notes/2026-06-08-fix-result-nodes.md new file mode 100644 index 000000000000..bc7aed562619 --- /dev/null +++ b/go/ql/lib/change-notes/2026-06-08-fix-result-nodes.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* `DataFlow::ResultNode`s are no longer created for returned expressions in functions with named result parameters. In this case there are already result nodes corresponding to `IR::ResultReadInstruction`s at the end of the function body. diff --git a/go/ql/lib/change-notes/2026-06-08-functypeexpr-getnumresult.md b/go/ql/lib/change-notes/2026-06-08-functypeexpr-getnumresult.md new file mode 100644 index 000000000000..974f604a041c --- /dev/null +++ b/go/ql/lib/change-notes/2026-06-08-functypeexpr-getnumresult.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* `FuncTypeExpr.getNumResults()` now gets the number of result parameters. It previously got the number of result declarations, which is different when one result declaration declares more than one variable, as in `x, y int`. All uses of it expected the number of result parameters. Its QLDoc has been updated. From e22f9fadd7852d67d45e16764ea23e6aacfbd2ca Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan <62447351+owen-mc@users.noreply.github.com> Date: Tue, 9 Jun 2026 05:26:56 +0100 Subject: [PATCH 8/9] Fix mistakes in change notes Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- go/ql/lib/change-notes/2026-06-08-fix-result-nodes.md | 2 +- go/ql/lib/change-notes/2026-06-08-functypeexpr-getnumresult.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go/ql/lib/change-notes/2026-06-08-fix-result-nodes.md b/go/ql/lib/change-notes/2026-06-08-fix-result-nodes.md index bc7aed562619..a567dd4edda7 100644 --- a/go/ql/lib/change-notes/2026-06-08-fix-result-nodes.md +++ b/go/ql/lib/change-notes/2026-06-08-fix-result-nodes.md @@ -1,4 +1,4 @@ --- category: minorAnalysis --- -* `DataFlow::ResultNode`s are no longer created for returned expressions in functions with named result parameters. In this case there are already result nodes corresponding to `IR::ResultReadInstruction`s at the end of the function body. +* `DataFlow::ResultNode`s are no longer created for returned expressions in functions with named result parameters. In this case there are already result nodes corresponding to `IR::ReadResultInstruction`s at the end of the function body. diff --git a/go/ql/lib/change-notes/2026-06-08-functypeexpr-getnumresult.md b/go/ql/lib/change-notes/2026-06-08-functypeexpr-getnumresult.md index 974f604a041c..70564beef113 100644 --- a/go/ql/lib/change-notes/2026-06-08-functypeexpr-getnumresult.md +++ b/go/ql/lib/change-notes/2026-06-08-functypeexpr-getnumresult.md @@ -1,4 +1,4 @@ --- category: minorAnalysis --- -* `FuncTypeExpr.getNumResults()` now gets the number of result parameters. It previously got the number of result declarations, which is different when one result declaration declares more than one variable, as in `x, y int`. All uses of it expected the number of result parameters. Its QLDoc has been updated. +* `FuncTypeExpr.getNumResult()` now gets the number of result parameters. It previously got the number of result declarations, which is different when one result declaration declares more than one variable, as in `x, y int`. All uses of it expected the number of result parameters. Its QLDoc has been updated. From 990913519da651573d726c649a70c47d824e62e9 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 9 Jun 2026 06:29:45 +0200 Subject: [PATCH 9/9] Make comment clearer --- go/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll index 90c216545566..9a26beb5b313 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll @@ -932,9 +932,10 @@ module Public { ResultNode() { exists(FuncDef fd | - // When a function has any (named) result variables, then the - // `ReadResultInstruction`s at the end of the function are the correct - // result nodes. + // If the function has named result variables, then the + // `IR::ReadResultInstruction` nodes at the end of the function are + // the correct result nodes. Otherwise, the returned expressions are + // the result nodes. not exists(fd.getAResultVar()) and exists(IR::ReturnInstruction ret | ret.getRoot() = fd | insn = ret.getResult(i)) or