From 5a0376f67ee8fdba5e491ce8e5f70b4e2358f47b Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 7 Dec 2020 17:21:46 +0100 Subject: [PATCH 1/8] CFG: More tests --- .../controlflow/graph/Cfg.expected | 522 +++++++++++++++++- .../controlflow/graph/break_ensure.rb | 42 ++ .../library-tests/controlflow/graph/ifs.rb | 4 + .../library-tests/controlflow/graph/raise.rb | 118 ++++ 4 files changed, 656 insertions(+), 30 deletions(-) create mode 100644 ql/test/library-tests/controlflow/graph/break_ensure.rb diff --git a/ql/test/library-tests/controlflow/graph/Cfg.expected b/ql/test/library-tests/controlflow/graph/Cfg.expected index cfb7974c1e6..1ff670286ec 100644 --- a/ql/test/library-tests/controlflow/graph/Cfg.expected +++ b/ql/test/library-tests/controlflow/graph/Cfg.expected @@ -1,4 +1,47 @@ nodes +| break_ensure.rb:1:1:11:3 | enter m1 | +| break_ensure.rb:1:1:11:3 | exit m1 | +| break_ensure.rb:1:1:11:3 | exit m1 (normal) | +| break_ensure.rb:1:8:1:15 | elements | +| break_ensure.rb:2:3:6:5 | For | +| break_ensure.rb:2:7:2:13 | element | +| break_ensure.rb:2:18:2:25 | elements | +| break_ensure.rb:3:5:5:7 | If | +| break_ensure.rb:3:8:3:8 | x | +| break_ensure.rb:3:8:3:12 | Binary | +| break_ensure.rb:3:12:3:12 | 0 | +| break_ensure.rb:4:7:4:11 | Break | +| break_ensure.rb:8:3:10:5 | If | +| break_ensure.rb:8:6:8:13 | elements | +| break_ensure.rb:8:6:8:18 | Call | +| break_ensure.rb:8:15:8:18 | nil? | +| break_ensure.rb:9:5:9:8 | puts | +| break_ensure.rb:9:5:9:23 | MethodCall | +| break_ensure.rb:9:10:9:23 | String | +| break_ensure.rb:13:1:25:3 | enter m2 | +| break_ensure.rb:13:1:25:3 | exit m2 | +| break_ensure.rb:13:1:25:3 | exit m2 (normal) | +| break_ensure.rb:13:8:13:15 | elements | +| break_ensure.rb:14:3:24:5 | For | +| break_ensure.rb:14:7:14:13 | element | +| break_ensure.rb:14:18:14:25 | elements | +| break_ensure.rb:16:7:18:9 | If | +| break_ensure.rb:16:10:16:10 | x | +| break_ensure.rb:16:10:16:14 | Binary | +| break_ensure.rb:16:14:16:14 | 0 | +| break_ensure.rb:17:9:17:13 | Break | +| break_ensure.rb:27:1:42:3 | enter m3 | +| break_ensure.rb:27:1:42:3 | exit m3 | +| break_ensure.rb:27:1:42:3 | exit m3 (normal) | +| break_ensure.rb:27:8:27:15 | elements | +| break_ensure.rb:29:5:31:7 | If | +| break_ensure.rb:29:8:29:15 | elements | +| break_ensure.rb:29:8:29:20 | Call | +| break_ensure.rb:29:17:29:20 | nil? | +| break_ensure.rb:30:7:30:12 | Return | +| break_ensure.rb:41:3:41:6 | puts | +| break_ensure.rb:41:3:41:13 | MethodCall | +| break_ensure.rb:41:8:41:13 | String | | case.rb:1:1:6:3 | enter if_in_case | | case.rb:1:1:6:3 | exit if_in_case | | case.rb:1:1:6:3 | exit if_in_case (normal) | @@ -622,6 +665,21 @@ nodes | ifs.rb:25:3:25:6 | puts | | ifs.rb:25:3:25:8 | MethodCall | | ifs.rb:25:8:25:8 | x | +| ifs.rb:28:1:30:3 | enter m4 | +| ifs.rb:28:1:30:3 | exit m4 | +| ifs.rb:28:1:30:3 | exit m4 (normal) | +| ifs.rb:28:9:28:10 | b1 | +| ifs.rb:28:13:28:14 | b2 | +| ifs.rb:28:17:28:18 | b3 | +| ifs.rb:29:3:29:51 | Return | +| ifs.rb:29:10:29:23 | ParenthesizedStatements | +| ifs.rb:29:10:29:51 | Conditional | +| ifs.rb:29:11:29:12 | b1 | +| ifs.rb:29:11:29:22 | Conditional | +| ifs.rb:29:16:29:17 | b2 | +| ifs.rb:29:21:29:22 | b3 | +| ifs.rb:29:27:29:36 | String | +| ifs.rb:29:40:29:51 | String | | loops.rb:1:1:6:3 | enter m1 | | loops.rb:1:1:6:3 | exit m1 | | loops.rb:1:1:6:3 | exit m1 (normal) | @@ -688,22 +746,238 @@ nodes | loops.rb:26:5:26:8 | puts | | loops.rb:26:5:26:10 | MethodCall | | loops.rb:26:10:26:10 | x | -| raise.rb:1:1:6:3 | enter m1 | -| raise.rb:1:1:6:3 | exit m1 | -| raise.rb:1:1:6:3 | exit m1 (abnormal) | -| raise.rb:1:1:6:3 | exit m1 (normal) | -| raise.rb:1:8:1:8 | x | -| raise.rb:2:3:4:5 | If | -| raise.rb:2:6:2:6 | x | -| raise.rb:2:6:2:10 | Binary | -| raise.rb:2:10:2:10 | 2 | -| raise.rb:3:5:3:9 | raise | -| raise.rb:3:5:3:17 | MethodCall | -| raise.rb:3:11:3:17 | String | -| raise.rb:5:3:5:6 | puts | -| raise.rb:5:3:5:15 | MethodCall | -| raise.rb:5:8:5:15 | String | +| raise.rb:1:1:2:3 | Class | +| raise.rb:1:1:124:3 | enter top-level | +| raise.rb:1:1:124:3 | exit top-level | +| raise.rb:1:1:124:3 | exit top-level (normal) | +| raise.rb:1:7:1:16 | ExceptionA | +| raise.rb:1:18:1:28 | Superclass | +| raise.rb:1:20:1:28 | Exception | +| raise.rb:4:1:5:3 | Class | +| raise.rb:4:7:4:16 | ExceptionB | +| raise.rb:4:18:4:28 | Superclass | +| raise.rb:4:20:4:28 | Exception | +| raise.rb:7:1:12:3 | enter m1 | +| raise.rb:7:1:12:3 | exit m1 | +| raise.rb:7:1:12:3 | exit m1 (abnormal) | +| raise.rb:7:1:12:3 | exit m1 (normal) | +| raise.rb:7:8:7:8 | x | +| raise.rb:8:3:10:5 | If | +| raise.rb:8:6:8:6 | x | +| raise.rb:8:6:8:10 | Binary | +| raise.rb:8:10:8:10 | 2 | +| raise.rb:9:5:9:9 | raise | +| raise.rb:9:5:9:17 | MethodCall | +| raise.rb:9:11:9:17 | String | +| raise.rb:11:3:11:6 | puts | +| raise.rb:11:3:11:15 | MethodCall | +| raise.rb:11:8:11:15 | String | +| raise.rb:14:1:23:3 | enter m2 | +| raise.rb:14:1:23:3 | exit m2 | +| raise.rb:14:1:23:3 | exit m2 (abnormal) | +| raise.rb:14:1:23:3 | exit m2 (normal) | +| raise.rb:14:8:14:8 | b | +| raise.rb:16:5:18:7 | If | +| raise.rb:16:8:16:8 | b | +| raise.rb:17:7:17:11 | raise | +| raise.rb:17:7:17:22 | MethodCall | +| raise.rb:17:13:17:22 | ExceptionA | +| raise.rb:22:3:22:6 | puts | +| raise.rb:22:3:22:15 | MethodCall | +| raise.rb:22:8:22:15 | String | +| raise.rb:25:1:34:3 | enter m3 | +| raise.rb:25:1:34:3 | exit m3 | +| raise.rb:25:1:34:3 | exit m3 (abnormal) | +| raise.rb:25:1:34:3 | exit m3 (normal) | +| raise.rb:25:8:25:8 | b | +| raise.rb:27:5:29:7 | If | +| raise.rb:27:8:27:8 | b | +| raise.rb:28:7:28:11 | raise | +| raise.rb:28:7:28:22 | MethodCall | +| raise.rb:28:13:28:22 | ExceptionA | +| raise.rb:33:3:33:6 | puts | +| raise.rb:33:3:33:15 | MethodCall | +| raise.rb:33:8:33:15 | String | +| raise.rb:36:1:45:3 | enter m4 | +| raise.rb:36:1:45:3 | exit m4 | +| raise.rb:36:1:45:3 | exit m4 (abnormal) | +| raise.rb:36:1:45:3 | exit m4 (normal) | +| raise.rb:36:8:36:8 | b | +| raise.rb:38:5:40:7 | If | +| raise.rb:38:8:38:8 | b | +| raise.rb:39:7:39:11 | raise | +| raise.rb:39:7:39:22 | MethodCall | +| raise.rb:39:13:39:22 | ExceptionA | +| raise.rb:44:3:44:6 | puts | +| raise.rb:44:3:44:15 | MethodCall | +| raise.rb:44:8:44:15 | String | +| raise.rb:47:1:55:3 | enter m5 | +| raise.rb:47:1:55:3 | exit m5 | +| raise.rb:47:1:55:3 | exit m5 (abnormal) | +| raise.rb:47:1:55:3 | exit m5 (normal) | +| raise.rb:47:8:47:8 | b | +| raise.rb:49:5:51:7 | If | +| raise.rb:49:8:49:8 | b | +| raise.rb:50:7:50:11 | raise | +| raise.rb:50:7:50:22 | MethodCall | +| raise.rb:50:13:50:22 | ExceptionA | +| raise.rb:54:3:54:6 | puts | +| raise.rb:54:3:54:15 | MethodCall | +| raise.rb:54:8:54:15 | String | +| raise.rb:57:1:66:3 | enter m6 | +| raise.rb:57:1:66:3 | exit m6 | +| raise.rb:57:1:66:3 | exit m6 (abnormal) | +| raise.rb:57:1:66:3 | exit m6 (normal) | +| raise.rb:57:8:57:8 | b | +| raise.rb:59:5:61:7 | If | +| raise.rb:59:8:59:8 | b | +| raise.rb:60:7:60:11 | raise | +| raise.rb:60:7:60:22 | MethodCall | +| raise.rb:60:13:60:22 | ExceptionA | +| raise.rb:65:3:65:6 | puts | +| raise.rb:65:3:65:15 | MethodCall | +| raise.rb:65:8:65:15 | String | +| raise.rb:68:1:77:3 | enter m7 | +| raise.rb:68:1:77:3 | exit m7 | +| raise.rb:68:1:77:3 | exit m7 (abnormal) | +| raise.rb:68:1:77:3 | exit m7 (normal) | +| raise.rb:68:8:68:8 | x | +| raise.rb:69:3:73:5 | If | +| raise.rb:69:6:69:6 | x | +| raise.rb:69:6:69:10 | Binary | +| raise.rb:69:10:69:10 | 2 | +| raise.rb:70:5:70:9 | raise | +| raise.rb:70:5:70:17 | MethodCall | +| raise.rb:70:11:70:17 | String | +| raise.rb:71:3:72:18 | Elsif | +| raise.rb:71:9:71:9 | x | +| raise.rb:71:9:71:13 | Binary | +| raise.rb:71:13:71:13 | 0 | +| raise.rb:72:5:72:18 | Return | +| raise.rb:72:12:72:18 | String | +| raise.rb:74:3:74:6 | puts | +| raise.rb:74:3:74:20 | MethodCall | +| raise.rb:74:8:74:20 | String | +| raise.rb:76:3:76:6 | puts | +| raise.rb:76:3:76:15 | MethodCall | +| raise.rb:76:8:76:15 | String | +| raise.rb:79:1:92:3 | enter m8 | +| raise.rb:79:1:92:3 | exit m8 | +| raise.rb:79:1:92:3 | exit m8 (abnormal) | +| raise.rb:79:1:92:3 | exit m8 (normal) | +| raise.rb:79:8:79:8 | x | +| raise.rb:80:3:80:6 | puts | +| raise.rb:80:3:80:17 | MethodCall | +| raise.rb:80:8:80:17 | String | +| raise.rb:82:5:86:7 | If | +| raise.rb:82:8:82:8 | x | +| raise.rb:82:8:82:12 | Binary | +| raise.rb:82:12:82:12 | 2 | +| raise.rb:83:7:83:11 | raise | +| raise.rb:83:7:83:19 | MethodCall | +| raise.rb:83:13:83:19 | String | +| raise.rb:84:5:85:20 | Elsif | +| raise.rb:84:11:84:11 | x | +| raise.rb:84:11:84:15 | Binary | +| raise.rb:84:15:84:15 | 0 | +| raise.rb:85:7:85:20 | Return | +| raise.rb:85:14:85:20 | String | +| raise.rb:87:5:87:8 | puts | +| raise.rb:87:5:87:22 | MethodCall | +| raise.rb:87:10:87:22 | String | +| raise.rb:91:3:91:6 | puts | +| raise.rb:91:3:91:15 | MethodCall | +| raise.rb:91:8:91:15 | String | +| raise.rb:94:1:119:3 | enter m9 | +| raise.rb:94:1:119:3 | exit m9 | +| raise.rb:94:1:119:3 | exit m9 (abnormal) | +| raise.rb:94:1:119:3 | exit m9 (normal) | +| raise.rb:94:8:94:8 | x | +| raise.rb:94:11:94:12 | b1 | +| raise.rb:94:15:94:16 | b2 | +| raise.rb:95:3:95:6 | puts | +| raise.rb:95:3:95:17 | MethodCall | +| raise.rb:95:8:95:17 | String | +| raise.rb:97:5:101:7 | If | +| raise.rb:97:8:97:8 | x | +| raise.rb:97:8:97:12 | Binary | +| raise.rb:97:12:97:12 | 2 | +| raise.rb:98:7:98:11 | raise | +| raise.rb:98:7:98:19 | MethodCall | +| raise.rb:98:13:98:19 | String | +| raise.rb:99:5:100:20 | Elsif | +| raise.rb:99:11:99:11 | x | +| raise.rb:99:11:99:15 | Binary | +| raise.rb:99:15:99:15 | 0 | +| raise.rb:100:7:100:20 | Return | +| raise.rb:100:14:100:20 | String | +| raise.rb:102:5:102:8 | puts | +| raise.rb:102:5:102:22 | MethodCall | +| raise.rb:102:10:102:22 | String | +| raise.rb:113:3:113:6 | puts | +| raise.rb:113:3:113:15 | MethodCall | +| raise.rb:113:8:113:15 | String | +| raise.rb:115:3:115:6 | puts | +| raise.rb:115:3:115:22 | MethodCall | +| raise.rb:115:8:115:22 | String | +| raise.rb:116:3:118:5 | If | +| raise.rb:116:6:116:7 | b2 | +| raise.rb:117:5:117:9 | raise | +| raise.rb:117:5:117:22 | MethodCall | +| raise.rb:117:11:117:22 | String | +| raise.rb:121:1:124:3 | enter m10 | +| raise.rb:121:1:124:3 | exit m10 | +| raise.rb:121:1:124:3 | exit m10 (abnormal) | +| raise.rb:121:14:121:18 | raise | +| raise.rb:121:14:121:30 | MethodCall | +| raise.rb:121:20:121:30 | String | edges +| break_ensure.rb:1:1:11:3 | enter m1 | break_ensure.rb:1:8:1:15 | elements | semmle.label | successor | +| break_ensure.rb:1:1:11:3 | exit m1 (normal) | break_ensure.rb:1:1:11:3 | exit m1 | semmle.label | successor | +| break_ensure.rb:1:8:1:15 | elements | break_ensure.rb:2:18:2:25 | elements | semmle.label | successor | +| break_ensure.rb:2:3:6:5 | For | break_ensure.rb:2:7:2:13 | element | semmle.label | non-empty | +| break_ensure.rb:2:3:6:5 | For | break_ensure.rb:8:3:10:5 | If | semmle.label | empty | +| break_ensure.rb:2:7:2:13 | element | break_ensure.rb:3:5:5:7 | If | semmle.label | successor | +| break_ensure.rb:2:18:2:25 | elements | break_ensure.rb:2:3:6:5 | For | semmle.label | successor | +| break_ensure.rb:3:5:5:7 | If | break_ensure.rb:3:8:3:8 | x | semmle.label | successor | +| break_ensure.rb:3:8:3:8 | x | break_ensure.rb:3:12:3:12 | 0 | semmle.label | successor | +| break_ensure.rb:3:8:3:12 | Binary | break_ensure.rb:2:3:6:5 | For | semmle.label | false | +| break_ensure.rb:3:8:3:12 | Binary | break_ensure.rb:4:7:4:11 | Break | semmle.label | true | +| break_ensure.rb:3:12:3:12 | 0 | break_ensure.rb:3:8:3:12 | Binary | semmle.label | successor | +| break_ensure.rb:4:7:4:11 | Break | break_ensure.rb:8:3:10:5 | If | semmle.label | break | +| break_ensure.rb:8:3:10:5 | If | break_ensure.rb:8:6:8:13 | elements | semmle.label | successor | +| break_ensure.rb:8:6:8:13 | elements | break_ensure.rb:8:15:8:18 | nil? | semmle.label | successor | +| break_ensure.rb:8:6:8:18 | Call | break_ensure.rb:1:1:11:3 | exit m1 (normal) | semmle.label | false | +| break_ensure.rb:8:6:8:18 | Call | break_ensure.rb:9:10:9:23 | String | semmle.label | true | +| break_ensure.rb:8:15:8:18 | nil? | break_ensure.rb:8:6:8:18 | Call | semmle.label | successor | +| break_ensure.rb:9:5:9:8 | puts | break_ensure.rb:9:5:9:23 | MethodCall | semmle.label | successor | +| break_ensure.rb:9:5:9:23 | MethodCall | break_ensure.rb:1:1:11:3 | exit m1 (normal) | semmle.label | successor | +| break_ensure.rb:9:10:9:23 | String | break_ensure.rb:9:5:9:8 | puts | semmle.label | successor | +| break_ensure.rb:13:1:25:3 | enter m2 | break_ensure.rb:13:8:13:15 | elements | semmle.label | successor | +| break_ensure.rb:13:1:25:3 | exit m2 (normal) | break_ensure.rb:13:1:25:3 | exit m2 | semmle.label | successor | +| break_ensure.rb:13:8:13:15 | elements | break_ensure.rb:14:18:14:25 | elements | semmle.label | successor | +| break_ensure.rb:14:3:24:5 | For | break_ensure.rb:13:1:25:3 | exit m2 (normal) | semmle.label | empty | +| break_ensure.rb:14:3:24:5 | For | break_ensure.rb:14:7:14:13 | element | semmle.label | non-empty | +| break_ensure.rb:14:7:14:13 | element | break_ensure.rb:16:7:18:9 | If | semmle.label | successor | +| break_ensure.rb:14:18:14:25 | elements | break_ensure.rb:14:3:24:5 | For | semmle.label | successor | +| break_ensure.rb:16:7:18:9 | If | break_ensure.rb:16:10:16:10 | x | semmle.label | successor | +| break_ensure.rb:16:10:16:10 | x | break_ensure.rb:16:14:16:14 | 0 | semmle.label | successor | +| break_ensure.rb:16:10:16:14 | Binary | break_ensure.rb:14:3:24:5 | For | semmle.label | false | +| break_ensure.rb:16:10:16:14 | Binary | break_ensure.rb:17:9:17:13 | Break | semmle.label | true | +| break_ensure.rb:16:14:16:14 | 0 | break_ensure.rb:16:10:16:14 | Binary | semmle.label | successor | +| break_ensure.rb:17:9:17:13 | Break | break_ensure.rb:13:1:25:3 | exit m2 (normal) | semmle.label | break | +| break_ensure.rb:27:1:42:3 | enter m3 | break_ensure.rb:27:8:27:15 | elements | semmle.label | successor | +| break_ensure.rb:27:1:42:3 | exit m3 (normal) | break_ensure.rb:27:1:42:3 | exit m3 | semmle.label | successor | +| break_ensure.rb:27:8:27:15 | elements | break_ensure.rb:29:5:31:7 | If | semmle.label | successor | +| break_ensure.rb:29:5:31:7 | If | break_ensure.rb:29:8:29:15 | elements | semmle.label | successor | +| break_ensure.rb:29:8:29:15 | elements | break_ensure.rb:29:17:29:20 | nil? | semmle.label | successor | +| break_ensure.rb:29:8:29:20 | Call | break_ensure.rb:30:7:30:12 | Return | semmle.label | true | +| break_ensure.rb:29:8:29:20 | Call | break_ensure.rb:41:8:41:13 | String | semmle.label | false | +| break_ensure.rb:29:17:29:20 | nil? | break_ensure.rb:29:8:29:20 | Call | semmle.label | successor | +| break_ensure.rb:30:7:30:12 | Return | break_ensure.rb:27:1:42:3 | exit m3 (normal) | semmle.label | return | +| break_ensure.rb:41:3:41:6 | puts | break_ensure.rb:41:3:41:13 | MethodCall | semmle.label | successor | +| break_ensure.rb:41:3:41:13 | MethodCall | break_ensure.rb:27:1:42:3 | exit m3 (normal) | semmle.label | successor | +| break_ensure.rb:41:8:41:13 | String | break_ensure.rb:41:3:41:6 | puts | semmle.label | successor | | case.rb:1:1:6:3 | enter if_in_case | case.rb:2:3:5:5 | Case | semmle.label | successor | | case.rb:1:1:6:3 | exit if_in_case (normal) | case.rb:1:1:6:3 | exit if_in_case | semmle.label | successor | | case.rb:2:3:5:5 | Case | case.rb:2:8:2:9 | x1 | semmle.label | successor | @@ -1339,6 +1613,22 @@ edges | ifs.rb:25:3:25:6 | puts | ifs.rb:25:3:25:8 | MethodCall | semmle.label | successor | | ifs.rb:25:3:25:8 | MethodCall | ifs.rb:18:1:26:3 | exit m3 (normal) | semmle.label | successor | | ifs.rb:25:8:25:8 | x | ifs.rb:25:3:25:6 | puts | semmle.label | successor | +| ifs.rb:28:1:30:3 | enter m4 | ifs.rb:28:9:28:10 | b1 | semmle.label | successor | +| ifs.rb:28:1:30:3 | exit m4 (normal) | ifs.rb:28:1:30:3 | exit m4 | semmle.label | successor | +| ifs.rb:28:9:28:10 | b1 | ifs.rb:28:13:28:14 | b2 | semmle.label | successor | +| ifs.rb:28:13:28:14 | b2 | ifs.rb:28:17:28:18 | b3 | semmle.label | successor | +| ifs.rb:28:17:28:18 | b3 | ifs.rb:29:10:29:51 | Conditional | semmle.label | successor | +| ifs.rb:29:3:29:51 | Return | ifs.rb:28:1:30:3 | exit m4 (normal) | semmle.label | return | +| ifs.rb:29:10:29:23 | ParenthesizedStatements | ifs.rb:29:27:29:36 | String | semmle.label | true | +| ifs.rb:29:10:29:23 | ParenthesizedStatements | ifs.rb:29:40:29:51 | String | semmle.label | false | +| ifs.rb:29:10:29:51 | Conditional | ifs.rb:29:11:29:22 | Conditional | semmle.label | successor | +| ifs.rb:29:11:29:12 | b1 | ifs.rb:29:16:29:17 | b2 | semmle.label | true | +| ifs.rb:29:11:29:12 | b1 | ifs.rb:29:21:29:22 | b3 | semmle.label | false | +| ifs.rb:29:11:29:22 | Conditional | ifs.rb:29:11:29:12 | b1 | semmle.label | successor | +| ifs.rb:29:16:29:17 | b2 | ifs.rb:29:10:29:23 | ParenthesizedStatements | semmle.label | successor | +| ifs.rb:29:21:29:22 | b3 | ifs.rb:29:10:29:23 | ParenthesizedStatements | semmle.label | successor | +| ifs.rb:29:27:29:36 | String | ifs.rb:29:3:29:51 | Return | semmle.label | successor | +| ifs.rb:29:40:29:51 | String | ifs.rb:29:3:29:51 | Return | semmle.label | successor | | loops.rb:1:1:6:3 | enter m1 | loops.rb:1:8:1:8 | x | semmle.label | successor | | loops.rb:1:1:6:3 | exit m1 (normal) | loops.rb:1:1:6:3 | exit m1 | semmle.label | successor | | loops.rb:1:8:1:8 | x | loops.rb:2:3:5:5 | While | semmle.label | successor | @@ -1406,18 +1696,190 @@ edges | loops.rb:26:5:26:8 | puts | loops.rb:26:5:26:10 | MethodCall | semmle.label | successor | | loops.rb:26:5:26:10 | MethodCall | loops.rb:25:16:27:5 | exit do block (normal) | semmle.label | successor | | loops.rb:26:10:26:10 | x | loops.rb:26:5:26:8 | puts | semmle.label | successor | -| raise.rb:1:1:6:3 | enter m1 | raise.rb:1:8:1:8 | x | semmle.label | successor | -| raise.rb:1:1:6:3 | exit m1 (abnormal) | raise.rb:1:1:6:3 | exit m1 | semmle.label | successor | -| raise.rb:1:1:6:3 | exit m1 (normal) | raise.rb:1:1:6:3 | exit m1 | semmle.label | successor | -| raise.rb:1:8:1:8 | x | raise.rb:2:3:4:5 | If | semmle.label | successor | -| raise.rb:2:3:4:5 | If | raise.rb:2:6:2:6 | x | semmle.label | successor | -| raise.rb:2:6:2:6 | x | raise.rb:2:10:2:10 | 2 | semmle.label | successor | -| raise.rb:2:6:2:10 | Binary | raise.rb:3:11:3:17 | String | semmle.label | true | -| raise.rb:2:6:2:10 | Binary | raise.rb:5:8:5:15 | String | semmle.label | false | -| raise.rb:2:10:2:10 | 2 | raise.rb:2:6:2:10 | Binary | semmle.label | successor | -| raise.rb:3:5:3:9 | raise | raise.rb:3:5:3:17 | MethodCall | semmle.label | successor | -| raise.rb:3:5:3:17 | MethodCall | raise.rb:1:1:6:3 | exit m1 (abnormal) | semmle.label | raise | -| raise.rb:3:11:3:17 | String | raise.rb:3:5:3:9 | raise | semmle.label | successor | -| raise.rb:5:3:5:6 | puts | raise.rb:5:3:5:15 | MethodCall | semmle.label | successor | -| raise.rb:5:3:5:15 | MethodCall | raise.rb:1:1:6:3 | exit m1 (normal) | semmle.label | successor | -| raise.rb:5:8:5:15 | String | raise.rb:5:3:5:6 | puts | semmle.label | successor | +| raise.rb:1:1:2:3 | Class | raise.rb:1:7:1:16 | ExceptionA | semmle.label | successor | +| raise.rb:1:1:124:3 | enter top-level | raise.rb:1:1:2:3 | Class | semmle.label | successor | +| raise.rb:1:1:124:3 | exit top-level (normal) | raise.rb:1:1:124:3 | exit top-level | semmle.label | successor | +| raise.rb:1:7:1:16 | ExceptionA | raise.rb:1:20:1:28 | Exception | semmle.label | successor | +| raise.rb:1:18:1:28 | Superclass | raise.rb:4:1:5:3 | Class | semmle.label | successor | +| raise.rb:1:20:1:28 | Exception | raise.rb:1:18:1:28 | Superclass | semmle.label | successor | +| raise.rb:4:1:5:3 | Class | raise.rb:4:7:4:16 | ExceptionB | semmle.label | successor | +| raise.rb:4:7:4:16 | ExceptionB | raise.rb:4:20:4:28 | Exception | semmle.label | successor | +| raise.rb:4:18:4:28 | Superclass | raise.rb:1:1:124:3 | exit top-level (normal) | semmle.label | successor | +| raise.rb:4:20:4:28 | Exception | raise.rb:4:18:4:28 | Superclass | semmle.label | successor | +| raise.rb:7:1:12:3 | enter m1 | raise.rb:7:8:7:8 | x | semmle.label | successor | +| raise.rb:7:1:12:3 | exit m1 (abnormal) | raise.rb:7:1:12:3 | exit m1 | semmle.label | successor | +| raise.rb:7:1:12:3 | exit m1 (normal) | raise.rb:7:1:12:3 | exit m1 | semmle.label | successor | +| raise.rb:7:8:7:8 | x | raise.rb:8:3:10:5 | If | semmle.label | successor | +| raise.rb:8:3:10:5 | If | raise.rb:8:6:8:6 | x | semmle.label | successor | +| raise.rb:8:6:8:6 | x | raise.rb:8:10:8:10 | 2 | semmle.label | successor | +| raise.rb:8:6:8:10 | Binary | raise.rb:9:11:9:17 | String | semmle.label | true | +| raise.rb:8:6:8:10 | Binary | raise.rb:11:8:11:15 | String | semmle.label | false | +| raise.rb:8:10:8:10 | 2 | raise.rb:8:6:8:10 | Binary | semmle.label | successor | +| raise.rb:9:5:9:9 | raise | raise.rb:9:5:9:17 | MethodCall | semmle.label | successor | +| raise.rb:9:5:9:17 | MethodCall | raise.rb:7:1:12:3 | exit m1 (abnormal) | semmle.label | raise | +| raise.rb:9:11:9:17 | String | raise.rb:9:5:9:9 | raise | semmle.label | successor | +| raise.rb:11:3:11:6 | puts | raise.rb:11:3:11:15 | MethodCall | semmle.label | successor | +| raise.rb:11:3:11:15 | MethodCall | raise.rb:7:1:12:3 | exit m1 (normal) | semmle.label | successor | +| raise.rb:11:8:11:15 | String | raise.rb:11:3:11:6 | puts | semmle.label | successor | +| raise.rb:14:1:23:3 | enter m2 | raise.rb:14:8:14:8 | b | semmle.label | successor | +| raise.rb:14:1:23:3 | exit m2 (abnormal) | raise.rb:14:1:23:3 | exit m2 | semmle.label | successor | +| raise.rb:14:1:23:3 | exit m2 (normal) | raise.rb:14:1:23:3 | exit m2 | semmle.label | successor | +| raise.rb:14:8:14:8 | b | raise.rb:16:5:18:7 | If | semmle.label | successor | +| raise.rb:16:5:18:7 | If | raise.rb:16:8:16:8 | b | semmle.label | successor | +| raise.rb:16:8:16:8 | b | raise.rb:17:13:17:22 | ExceptionA | semmle.label | true | +| raise.rb:16:8:16:8 | b | raise.rb:22:8:22:15 | String | semmle.label | false | +| raise.rb:17:7:17:11 | raise | raise.rb:17:7:17:22 | MethodCall | semmle.label | successor | +| raise.rb:17:7:17:22 | MethodCall | raise.rb:14:1:23:3 | exit m2 (abnormal) | semmle.label | raise | +| raise.rb:17:13:17:22 | ExceptionA | raise.rb:17:7:17:11 | raise | semmle.label | successor | +| raise.rb:22:3:22:6 | puts | raise.rb:22:3:22:15 | MethodCall | semmle.label | successor | +| raise.rb:22:3:22:15 | MethodCall | raise.rb:14:1:23:3 | exit m2 (normal) | semmle.label | successor | +| raise.rb:22:8:22:15 | String | raise.rb:22:3:22:6 | puts | semmle.label | successor | +| raise.rb:25:1:34:3 | enter m3 | raise.rb:25:8:25:8 | b | semmle.label | successor | +| raise.rb:25:1:34:3 | exit m3 (abnormal) | raise.rb:25:1:34:3 | exit m3 | semmle.label | successor | +| raise.rb:25:1:34:3 | exit m3 (normal) | raise.rb:25:1:34:3 | exit m3 | semmle.label | successor | +| raise.rb:25:8:25:8 | b | raise.rb:27:5:29:7 | If | semmle.label | successor | +| raise.rb:27:5:29:7 | If | raise.rb:27:8:27:8 | b | semmle.label | successor | +| raise.rb:27:8:27:8 | b | raise.rb:28:13:28:22 | ExceptionA | semmle.label | true | +| raise.rb:27:8:27:8 | b | raise.rb:33:8:33:15 | String | semmle.label | false | +| raise.rb:28:7:28:11 | raise | raise.rb:28:7:28:22 | MethodCall | semmle.label | successor | +| raise.rb:28:7:28:22 | MethodCall | raise.rb:25:1:34:3 | exit m3 (abnormal) | semmle.label | raise | +| raise.rb:28:13:28:22 | ExceptionA | raise.rb:28:7:28:11 | raise | semmle.label | successor | +| raise.rb:33:3:33:6 | puts | raise.rb:33:3:33:15 | MethodCall | semmle.label | successor | +| raise.rb:33:3:33:15 | MethodCall | raise.rb:25:1:34:3 | exit m3 (normal) | semmle.label | successor | +| raise.rb:33:8:33:15 | String | raise.rb:33:3:33:6 | puts | semmle.label | successor | +| raise.rb:36:1:45:3 | enter m4 | raise.rb:36:8:36:8 | b | semmle.label | successor | +| raise.rb:36:1:45:3 | exit m4 (abnormal) | raise.rb:36:1:45:3 | exit m4 | semmle.label | successor | +| raise.rb:36:1:45:3 | exit m4 (normal) | raise.rb:36:1:45:3 | exit m4 | semmle.label | successor | +| raise.rb:36:8:36:8 | b | raise.rb:38:5:40:7 | If | semmle.label | successor | +| raise.rb:38:5:40:7 | If | raise.rb:38:8:38:8 | b | semmle.label | successor | +| raise.rb:38:8:38:8 | b | raise.rb:39:13:39:22 | ExceptionA | semmle.label | true | +| raise.rb:38:8:38:8 | b | raise.rb:44:8:44:15 | String | semmle.label | false | +| raise.rb:39:7:39:11 | raise | raise.rb:39:7:39:22 | MethodCall | semmle.label | successor | +| raise.rb:39:7:39:22 | MethodCall | raise.rb:36:1:45:3 | exit m4 (abnormal) | semmle.label | raise | +| raise.rb:39:13:39:22 | ExceptionA | raise.rb:39:7:39:11 | raise | semmle.label | successor | +| raise.rb:44:3:44:6 | puts | raise.rb:44:3:44:15 | MethodCall | semmle.label | successor | +| raise.rb:44:3:44:15 | MethodCall | raise.rb:36:1:45:3 | exit m4 (normal) | semmle.label | successor | +| raise.rb:44:8:44:15 | String | raise.rb:44:3:44:6 | puts | semmle.label | successor | +| raise.rb:47:1:55:3 | enter m5 | raise.rb:47:8:47:8 | b | semmle.label | successor | +| raise.rb:47:1:55:3 | exit m5 (abnormal) | raise.rb:47:1:55:3 | exit m5 | semmle.label | successor | +| raise.rb:47:1:55:3 | exit m5 (normal) | raise.rb:47:1:55:3 | exit m5 | semmle.label | successor | +| raise.rb:47:8:47:8 | b | raise.rb:49:5:51:7 | If | semmle.label | successor | +| raise.rb:49:5:51:7 | If | raise.rb:49:8:49:8 | b | semmle.label | successor | +| raise.rb:49:8:49:8 | b | raise.rb:50:13:50:22 | ExceptionA | semmle.label | true | +| raise.rb:49:8:49:8 | b | raise.rb:54:8:54:15 | String | semmle.label | false | +| raise.rb:50:7:50:11 | raise | raise.rb:50:7:50:22 | MethodCall | semmle.label | successor | +| raise.rb:50:7:50:22 | MethodCall | raise.rb:47:1:55:3 | exit m5 (abnormal) | semmle.label | raise | +| raise.rb:50:13:50:22 | ExceptionA | raise.rb:50:7:50:11 | raise | semmle.label | successor | +| raise.rb:54:3:54:6 | puts | raise.rb:54:3:54:15 | MethodCall | semmle.label | successor | +| raise.rb:54:3:54:15 | MethodCall | raise.rb:47:1:55:3 | exit m5 (normal) | semmle.label | successor | +| raise.rb:54:8:54:15 | String | raise.rb:54:3:54:6 | puts | semmle.label | successor | +| raise.rb:57:1:66:3 | enter m6 | raise.rb:57:8:57:8 | b | semmle.label | successor | +| raise.rb:57:1:66:3 | exit m6 (abnormal) | raise.rb:57:1:66:3 | exit m6 | semmle.label | successor | +| raise.rb:57:1:66:3 | exit m6 (normal) | raise.rb:57:1:66:3 | exit m6 | semmle.label | successor | +| raise.rb:57:8:57:8 | b | raise.rb:59:5:61:7 | If | semmle.label | successor | +| raise.rb:59:5:61:7 | If | raise.rb:59:8:59:8 | b | semmle.label | successor | +| raise.rb:59:8:59:8 | b | raise.rb:60:13:60:22 | ExceptionA | semmle.label | true | +| raise.rb:59:8:59:8 | b | raise.rb:65:8:65:15 | String | semmle.label | false | +| raise.rb:60:7:60:11 | raise | raise.rb:60:7:60:22 | MethodCall | semmle.label | successor | +| raise.rb:60:7:60:22 | MethodCall | raise.rb:57:1:66:3 | exit m6 (abnormal) | semmle.label | raise | +| raise.rb:60:13:60:22 | ExceptionA | raise.rb:60:7:60:11 | raise | semmle.label | successor | +| raise.rb:65:3:65:6 | puts | raise.rb:65:3:65:15 | MethodCall | semmle.label | successor | +| raise.rb:65:3:65:15 | MethodCall | raise.rb:57:1:66:3 | exit m6 (normal) | semmle.label | successor | +| raise.rb:65:8:65:15 | String | raise.rb:65:3:65:6 | puts | semmle.label | successor | +| raise.rb:68:1:77:3 | enter m7 | raise.rb:68:8:68:8 | x | semmle.label | successor | +| raise.rb:68:1:77:3 | exit m7 (abnormal) | raise.rb:68:1:77:3 | exit m7 | semmle.label | successor | +| raise.rb:68:1:77:3 | exit m7 (normal) | raise.rb:68:1:77:3 | exit m7 | semmle.label | successor | +| raise.rb:68:8:68:8 | x | raise.rb:69:3:73:5 | If | semmle.label | successor | +| raise.rb:69:3:73:5 | If | raise.rb:69:6:69:6 | x | semmle.label | successor | +| raise.rb:69:6:69:6 | x | raise.rb:69:10:69:10 | 2 | semmle.label | successor | +| raise.rb:69:6:69:10 | Binary | raise.rb:70:11:70:17 | String | semmle.label | true | +| raise.rb:69:6:69:10 | Binary | raise.rb:71:3:72:18 | Elsif | semmle.label | false | +| raise.rb:69:10:69:10 | 2 | raise.rb:69:6:69:10 | Binary | semmle.label | successor | +| raise.rb:70:5:70:9 | raise | raise.rb:70:5:70:17 | MethodCall | semmle.label | successor | +| raise.rb:70:5:70:17 | MethodCall | raise.rb:68:1:77:3 | exit m7 (abnormal) | semmle.label | raise | +| raise.rb:70:11:70:17 | String | raise.rb:70:5:70:9 | raise | semmle.label | successor | +| raise.rb:71:3:72:18 | Elsif | raise.rb:71:9:71:9 | x | semmle.label | successor | +| raise.rb:71:9:71:9 | x | raise.rb:71:13:71:13 | 0 | semmle.label | successor | +| raise.rb:71:9:71:13 | Binary | raise.rb:72:12:72:18 | String | semmle.label | true | +| raise.rb:71:9:71:13 | Binary | raise.rb:74:8:74:20 | String | semmle.label | false | +| raise.rb:71:13:71:13 | 0 | raise.rb:71:9:71:13 | Binary | semmle.label | successor | +| raise.rb:72:5:72:18 | Return | raise.rb:68:1:77:3 | exit m7 (normal) | semmle.label | return | +| raise.rb:72:12:72:18 | String | raise.rb:72:5:72:18 | Return | semmle.label | successor | +| raise.rb:74:3:74:6 | puts | raise.rb:74:3:74:20 | MethodCall | semmle.label | successor | +| raise.rb:74:3:74:20 | MethodCall | raise.rb:76:8:76:15 | String | semmle.label | successor | +| raise.rb:74:8:74:20 | String | raise.rb:74:3:74:6 | puts | semmle.label | successor | +| raise.rb:76:3:76:6 | puts | raise.rb:76:3:76:15 | MethodCall | semmle.label | successor | +| raise.rb:76:3:76:15 | MethodCall | raise.rb:68:1:77:3 | exit m7 (normal) | semmle.label | successor | +| raise.rb:76:8:76:15 | String | raise.rb:76:3:76:6 | puts | semmle.label | successor | +| raise.rb:79:1:92:3 | enter m8 | raise.rb:79:8:79:8 | x | semmle.label | successor | +| raise.rb:79:1:92:3 | exit m8 (abnormal) | raise.rb:79:1:92:3 | exit m8 | semmle.label | successor | +| raise.rb:79:1:92:3 | exit m8 (normal) | raise.rb:79:1:92:3 | exit m8 | semmle.label | successor | +| raise.rb:79:8:79:8 | x | raise.rb:80:8:80:17 | String | semmle.label | successor | +| raise.rb:80:3:80:6 | puts | raise.rb:80:3:80:17 | MethodCall | semmle.label | successor | +| raise.rb:80:3:80:17 | MethodCall | raise.rb:82:5:86:7 | If | semmle.label | successor | +| raise.rb:80:8:80:17 | String | raise.rb:80:3:80:6 | puts | semmle.label | successor | +| raise.rb:82:5:86:7 | If | raise.rb:82:8:82:8 | x | semmle.label | successor | +| raise.rb:82:8:82:8 | x | raise.rb:82:12:82:12 | 2 | semmle.label | successor | +| raise.rb:82:8:82:12 | Binary | raise.rb:83:13:83:19 | String | semmle.label | true | +| raise.rb:82:8:82:12 | Binary | raise.rb:84:5:85:20 | Elsif | semmle.label | false | +| raise.rb:82:12:82:12 | 2 | raise.rb:82:8:82:12 | Binary | semmle.label | successor | +| raise.rb:83:7:83:11 | raise | raise.rb:83:7:83:19 | MethodCall | semmle.label | successor | +| raise.rb:83:7:83:19 | MethodCall | raise.rb:79:1:92:3 | exit m8 (abnormal) | semmle.label | raise | +| raise.rb:83:13:83:19 | String | raise.rb:83:7:83:11 | raise | semmle.label | successor | +| raise.rb:84:5:85:20 | Elsif | raise.rb:84:11:84:11 | x | semmle.label | successor | +| raise.rb:84:11:84:11 | x | raise.rb:84:15:84:15 | 0 | semmle.label | successor | +| raise.rb:84:11:84:15 | Binary | raise.rb:85:14:85:20 | String | semmle.label | true | +| raise.rb:84:11:84:15 | Binary | raise.rb:87:10:87:22 | String | semmle.label | false | +| raise.rb:84:15:84:15 | 0 | raise.rb:84:11:84:15 | Binary | semmle.label | successor | +| raise.rb:85:7:85:20 | Return | raise.rb:79:1:92:3 | exit m8 (normal) | semmle.label | return | +| raise.rb:85:14:85:20 | String | raise.rb:85:7:85:20 | Return | semmle.label | successor | +| raise.rb:87:5:87:8 | puts | raise.rb:87:5:87:22 | MethodCall | semmle.label | successor | +| raise.rb:87:5:87:22 | MethodCall | raise.rb:91:8:91:15 | String | semmle.label | successor | +| raise.rb:87:10:87:22 | String | raise.rb:87:5:87:8 | puts | semmle.label | successor | +| raise.rb:91:3:91:6 | puts | raise.rb:91:3:91:15 | MethodCall | semmle.label | successor | +| raise.rb:91:3:91:15 | MethodCall | raise.rb:79:1:92:3 | exit m8 (normal) | semmle.label | successor | +| raise.rb:91:8:91:15 | String | raise.rb:91:3:91:6 | puts | semmle.label | successor | +| raise.rb:94:1:119:3 | enter m9 | raise.rb:94:8:94:8 | x | semmle.label | successor | +| raise.rb:94:1:119:3 | exit m9 (abnormal) | raise.rb:94:1:119:3 | exit m9 | semmle.label | successor | +| raise.rb:94:1:119:3 | exit m9 (normal) | raise.rb:94:1:119:3 | exit m9 | semmle.label | successor | +| raise.rb:94:8:94:8 | x | raise.rb:94:11:94:12 | b1 | semmle.label | successor | +| raise.rb:94:11:94:12 | b1 | raise.rb:94:15:94:16 | b2 | semmle.label | successor | +| raise.rb:94:15:94:16 | b2 | raise.rb:95:8:95:17 | String | semmle.label | successor | +| raise.rb:95:3:95:6 | puts | raise.rb:95:3:95:17 | MethodCall | semmle.label | successor | +| raise.rb:95:3:95:17 | MethodCall | raise.rb:97:5:101:7 | If | semmle.label | successor | +| raise.rb:95:8:95:17 | String | raise.rb:95:3:95:6 | puts | semmle.label | successor | +| raise.rb:97:5:101:7 | If | raise.rb:97:8:97:8 | x | semmle.label | successor | +| raise.rb:97:8:97:8 | x | raise.rb:97:12:97:12 | 2 | semmle.label | successor | +| raise.rb:97:8:97:12 | Binary | raise.rb:98:13:98:19 | String | semmle.label | true | +| raise.rb:97:8:97:12 | Binary | raise.rb:99:5:100:20 | Elsif | semmle.label | false | +| raise.rb:97:12:97:12 | 2 | raise.rb:97:8:97:12 | Binary | semmle.label | successor | +| raise.rb:98:7:98:11 | raise | raise.rb:98:7:98:19 | MethodCall | semmle.label | successor | +| raise.rb:98:7:98:19 | MethodCall | raise.rb:94:1:119:3 | exit m9 (abnormal) | semmle.label | raise | +| raise.rb:98:13:98:19 | String | raise.rb:98:7:98:11 | raise | semmle.label | successor | +| raise.rb:99:5:100:20 | Elsif | raise.rb:99:11:99:11 | x | semmle.label | successor | +| raise.rb:99:11:99:11 | x | raise.rb:99:15:99:15 | 0 | semmle.label | successor | +| raise.rb:99:11:99:15 | Binary | raise.rb:100:14:100:20 | String | semmle.label | true | +| raise.rb:99:11:99:15 | Binary | raise.rb:102:10:102:22 | String | semmle.label | false | +| raise.rb:99:15:99:15 | 0 | raise.rb:99:11:99:15 | Binary | semmle.label | successor | +| raise.rb:100:7:100:20 | Return | raise.rb:94:1:119:3 | exit m9 (normal) | semmle.label | return | +| raise.rb:100:14:100:20 | String | raise.rb:100:7:100:20 | Return | semmle.label | successor | +| raise.rb:102:5:102:8 | puts | raise.rb:102:5:102:22 | MethodCall | semmle.label | successor | +| raise.rb:102:5:102:22 | MethodCall | raise.rb:113:8:113:15 | String | semmle.label | successor | +| raise.rb:102:10:102:22 | String | raise.rb:102:5:102:8 | puts | semmle.label | successor | +| raise.rb:113:3:113:6 | puts | raise.rb:113:3:113:15 | MethodCall | semmle.label | successor | +| raise.rb:113:3:113:15 | MethodCall | raise.rb:115:8:115:22 | String | semmle.label | successor | +| raise.rb:113:8:113:15 | String | raise.rb:113:3:113:6 | puts | semmle.label | successor | +| raise.rb:115:3:115:6 | puts | raise.rb:115:3:115:22 | MethodCall | semmle.label | successor | +| raise.rb:115:3:115:22 | MethodCall | raise.rb:116:3:118:5 | If | semmle.label | successor | +| raise.rb:115:8:115:22 | String | raise.rb:115:3:115:6 | puts | semmle.label | successor | +| raise.rb:116:3:118:5 | If | raise.rb:116:6:116:7 | b2 | semmle.label | successor | +| raise.rb:116:6:116:7 | b2 | raise.rb:94:1:119:3 | exit m9 (normal) | semmle.label | false | +| raise.rb:116:6:116:7 | b2 | raise.rb:117:11:117:22 | String | semmle.label | true | +| raise.rb:117:5:117:9 | raise | raise.rb:117:5:117:22 | MethodCall | semmle.label | successor | +| raise.rb:117:5:117:22 | MethodCall | raise.rb:94:1:119:3 | exit m9 (abnormal) | semmle.label | raise | +| raise.rb:117:11:117:22 | String | raise.rb:117:5:117:9 | raise | semmle.label | successor | +| raise.rb:121:1:124:3 | enter m10 | raise.rb:121:20:121:30 | String | semmle.label | successor | +| raise.rb:121:1:124:3 | exit m10 (abnormal) | raise.rb:121:1:124:3 | exit m10 | semmle.label | successor | +| raise.rb:121:14:121:18 | raise | raise.rb:121:14:121:30 | MethodCall | semmle.label | successor | +| raise.rb:121:14:121:30 | MethodCall | raise.rb:121:1:124:3 | exit m10 (abnormal) | semmle.label | raise | +| raise.rb:121:20:121:30 | String | raise.rb:121:14:121:18 | raise | semmle.label | successor | diff --git a/ql/test/library-tests/controlflow/graph/break_ensure.rb b/ql/test/library-tests/controlflow/graph/break_ensure.rb new file mode 100644 index 00000000000..cf181e3fd38 --- /dev/null +++ b/ql/test/library-tests/controlflow/graph/break_ensure.rb @@ -0,0 +1,42 @@ +def m1 elements + for element in elements do + if x > 0 then + break + end + end +ensure + if elements.nil? then + puts "elements nil" + end +end + +def m2 elements + for element in elements do + begin + if x > 0 then + break + end + ensure + if elements.nil? then + puts "elements nil" + end + end + end +end + +def m3 elements + begin + if elements.nil? then + return + end + ensure + for element in elements do + begin + if x > 0 then + break + end + end + end + end + puts "Done" +end diff --git a/ql/test/library-tests/controlflow/graph/ifs.rb b/ql/test/library-tests/controlflow/graph/ifs.rb index fdd3ae21928..4ea7922b1ae 100644 --- a/ql/test/library-tests/controlflow/graph/ifs.rb +++ b/ql/test/library-tests/controlflow/graph/ifs.rb @@ -24,3 +24,7 @@ def m3 x end puts x end + +def m4 (b1, b2, b3) + return (b1 ? b2 : b3) ? "b2 || b3" : "!b2 || !b3" +end \ No newline at end of file diff --git a/ql/test/library-tests/controlflow/graph/raise.rb b/ql/test/library-tests/controlflow/graph/raise.rb index 9b92eafdcf6..d9db0cc2821 100644 --- a/ql/test/library-tests/controlflow/graph/raise.rb +++ b/ql/test/library-tests/controlflow/graph/raise.rb @@ -1,6 +1,124 @@ +class ExceptionA < Exception +end + +class ExceptionB < Exception +end + def m1 x if x > 2 raise "x > 2" end puts "x <= 2" end + +def m2 b + begin + if b + raise ExceptionA + end + rescue ExceptionA + puts "Rescued" + end + puts "End m2" +end + +def m3 b + begin + if b + raise ExceptionA + end + rescue + puts "Rescued" + end + puts "End m3" +end + +def m4 b + begin + if b + raise ExceptionA + end + rescue => e + puts "Rescued {e}" + end + puts "End m4" +end + +def m5 b + begin + if b + raise ExceptionA + end + rescue => e + end + puts "End m5" +end + +def m6 b + begin + if b + raise ExceptionA + end + rescue ExceptionA, ExceptionB => e + puts "Rescued {e}" + end + puts "End m6" +end + +def m7 x + if x > 2 + raise "x > 2" + elsif x < 0 + return "x < 0" + end + puts "0 <= x <= 2" +ensure + puts "ensure" +end + +def m8 x + puts "Begin m8" + begin + if x > 2 + raise "x > 2" + elsif x < 0 + return "x < 0" + end + puts "0 <= x <= 2" + ensure + puts "ensure" + end + puts "End m8" +end + +def m9(x, b1, b2) + puts "Begin m9" + begin + if x > 2 + raise "x > 2" + elsif x < 0 + return "x < 0" + end + puts "0 <= x <= 2" + ensure + puts "outer ensure" + begin + if b1 + raise "b1 is true" + end + ensure + puts "inner ensure" + end + end + puts "End m9" +ensure + puts "method ensure" + if b2 + raise "b2 is true" + end +end + +def m10(p = (raise "Exception")) +ensure + puts "Will not get executed if p is not supplied" +end \ No newline at end of file From b6ea5c5eab220c2306f2dd5e1f20a5e378a2a515 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 7 Dec 2020 17:21:05 +0100 Subject: [PATCH 2/8] CFG: Implement logic for `rescue-ensure` blocks --- .../controlflow/ControlFlowGraph.qll | 45 +- .../controlflow/internal/Completion.qll | 242 ++++++++-- .../internal/ControlFlowGraphImpl.qll | 436 +++++++++++++++--- .../controlflow/internal/NonReturning.qll | 2 +- .../controlflow/internal/Splitting.qll | 262 ++++++++++- .../controlflow/graph/Cfg.expected | 376 +++++++++++++-- 6 files changed, 1213 insertions(+), 150 deletions(-) diff --git a/ql/src/codeql_ruby/controlflow/ControlFlowGraph.qll b/ql/src/codeql_ruby/controlflow/ControlFlowGraph.qll index d69a65243b8..8df84ffd371 100644 --- a/ql/src/codeql_ruby/controlflow/ControlFlowGraph.qll +++ b/ql/src/codeql_ruby/controlflow/ControlFlowGraph.qll @@ -186,12 +186,17 @@ module SuccessorTypes { /** * A conditional control flow successor. Either a Boolean successor (`BooleanSuccessor`), - * or an emptiness successor (`EmptinessSuccessor`). + * an emptiness successor (`EmptinessSuccessor`), or a matching successor + * (`MatchingSuccessor`) */ class ConditionalSuccessor extends SuccessorType { boolean value; - ConditionalSuccessor() { this = TBooleanSuccessor(value) or this = TEmptinessSuccessor(value) } + ConditionalSuccessor() { + this = TBooleanSuccessor(value) or + this = TEmptinessSuccessor(value) or + this = TMatchingSuccessor(value) + } /** Gets the Boolean value of this successor. */ final boolean getValue() { result = value } @@ -248,12 +253,38 @@ module SuccessorTypes { * ``` */ class EmptinessSuccessor extends ConditionalSuccessor, TEmptinessSuccessor { - /** Holds if this is an empty successor. */ - predicate isEmpty() { value = true } + override string toString() { if value = true then result = "empty" else result = "non-empty" } + } - final override string toString() { - if this.isEmpty() then result = "empty" else result = "non-empty" - } + /** + * A matching control flow successor. + * + * For example, this program fragment: + * + * ```rb + * case x + * when 1 then puts "one" + * else puts "not one" + * end + * ``` + * + * has a control flow graph containing matching successors: + * + * ``` + * x + * | + * 1 + * / \ + * / \ + * / \ + * / \ + * match non-match + * | | + * puts "one" puts "not one" + * ``` + */ + class MatchingSuccessor extends ConditionalSuccessor, TMatchingSuccessor { + override string toString() { if value = true then result = "match" else result = "no-match" } } /** diff --git a/ql/src/codeql_ruby/controlflow/internal/Completion.qll b/ql/src/codeql_ruby/controlflow/internal/Completion.qll index 71d09d6e7db..4245bc7990a 100644 --- a/ql/src/codeql_ruby/controlflow/internal/Completion.qll +++ b/ql/src/codeql_ruby/controlflow/internal/Completion.qll @@ -7,13 +7,15 @@ private import codeql_ruby.ast.internal.TreeSitter::Generated private import codeql_ruby.controlflow.ControlFlowGraph private import AstNodes +private import ControlFlowGraphImpl private import NonReturning private import SuccessorTypes private newtype TCompletion = TSimpleCompletion() or - TBooleanCompletion(boolean b) { b = true or b = false } or - TEmptinessCompletion(boolean isEmpty) { isEmpty = true or isEmpty = false } or + TBooleanCompletion(boolean b) { b in [false, true] } or + TEmptinessCompletion(boolean isEmpty) { isEmpty in [false, true] } or + TMatchingCompletion(boolean isMatch) { isMatch in [false, true] } or TReturnCompletion() or TBreakCompletion() or TNextCompletion() or @@ -21,11 +23,35 @@ private newtype TCompletion = TRetryCompletion() or TRaiseCompletion() or // TODO: Add exception type? TExitCompletion() or - TNestedCompletion(Completion inner, Completion outer) { - outer = TSimpleCompletion() and - inner = TBreakCompletion() + TNestedCompletion(Completion inner, Completion outer, int nestLevel) { + inner = TBreakCompletion() and + outer instanceof NonNestedNormalCompletion and + nestLevel = 0 + or + inner instanceof NormalCompletion and + nestedEnsureCompletion(outer, nestLevel) } +pragma[noinline] +private predicate nestedEnsureCompletion(Completion outer, int nestLevel) { + ( + outer = TReturnCompletion() + or + outer = TBreakCompletion() + or + outer = TNextCompletion() + or + outer = TRedoCompletion() + or + outer = TRetryCompletion() + or + outer = TRaiseCompletion() + or + outer = TExitCompletion() + ) and + nestLevel = any(Trees::RescueEnsureBlockTree t).nestLevel() +} + pragma[noinline] private predicate completionIsValidForStmt(AstNode n, Completion c) { n instanceof Break and @@ -57,11 +83,15 @@ abstract class Completion extends TCompletion { this = TBooleanCompletion(_) ) or + mustHaveMatchingCompletion(n) and + this = TMatchingCompletion(_) + or n = any(RescueModifier parent).getBody() and this = TRaiseCompletion() or not n instanceof NonReturningCall and not completionIsValidForStmt(n, _) and not mustHaveBooleanCompletion(n) and + not mustHaveMatchingCompletion(n) and this = TSimpleCompletion() } @@ -143,14 +173,30 @@ private predicate inBooleanContext(AstNode n) { n instanceof Pattern } +/** + * Holds if a normal completion of `n` must be a matching completion. + */ +private predicate mustHaveMatchingCompletion(AstNode n) { + inMatchingContext(n) and + not n instanceof NonReturningCall +} + +/** + * Holds if `n` is used in a matching context. That is, whether or + * not the value of `n` matches, determines the successor. + */ +private predicate inMatchingContext(AstNode n) { n = any(Rescue r).getExceptions().getChild(_) } + /** * A completion that represents normal evaluation of a statement or an * expression. */ abstract class NormalCompletion extends Completion { } +abstract private class NonNestedNormalCompletion extends NormalCompletion { } + /** A simple (normal) completion. */ -class SimpleCompletion extends NormalCompletion, TSimpleCompletion { +class SimpleCompletion extends NonNestedNormalCompletion, TSimpleCompletion { override NormalSuccessor getAMatchingSuccessorType() { any() } override string toString() { result = "simple" } @@ -158,10 +204,13 @@ class SimpleCompletion extends NormalCompletion, TSimpleCompletion { /** * A completion that represents evaluation of an expression, whose value determines - * the successor. Either a Boolean completion (`BooleanCompletion`) - * or an emptiness completion (`EmptinessCompletion`). + * the successor. Either a Boolean completion (`BooleanCompletion`), an emptiness + * completion (`EmptinessCompletion`), or a matching completion (`MatchingCompletion`). */ -abstract class ConditionalCompletion extends NormalCompletion { } +abstract class ConditionalCompletion extends NonNestedNormalCompletion { + /** Gets the Boolean value of this conditional completion. */ + abstract boolean getValue(); +} /** * A completion that represents evaluation of an expression @@ -172,8 +221,7 @@ class BooleanCompletion extends ConditionalCompletion, TBooleanCompletion { BooleanCompletion() { this = TBooleanCompletion(value) } - /** Gets the Boolean value of this completion. */ - boolean getValue() { result = value } + override boolean getValue() { result = value } /** Gets the dual Boolean completion. */ BooleanCompletion getDual() { result = TBooleanCompletion(value.booleanNot()) } @@ -198,84 +246,157 @@ class FalseCompletion extends BooleanCompletion { * a test in a `for in` statement. */ class EmptinessCompletion extends ConditionalCompletion, TEmptinessCompletion { - /** Holds if the emptiness test evaluates to `true`. */ - predicate isEmpty() { this = TEmptinessCompletion(true) } + private boolean value; - override EmptinessSuccessor getAMatchingSuccessorType() { - if isEmpty() then result.getValue() = true else result.getValue() = false - } + EmptinessCompletion() { this = TEmptinessCompletion(value) } - override string toString() { if this.isEmpty() then result = "empty" else result = "non-empty" } + override boolean getValue() { result = value } + + override EmptinessSuccessor getAMatchingSuccessorType() { result.getValue() = value } + + override string toString() { if value = true then result = "empty" else result = "non-empty" } +} + +/** + * A completion that represents evaluation of a matching test, for example + * a test in a `rescue` statement. + */ +class MatchingCompletion extends ConditionalCompletion, TMatchingCompletion { + private boolean value; + + MatchingCompletion() { this = TMatchingCompletion(value) } + + override boolean getValue() { result = value } + + override MatchingSuccessor getAMatchingSuccessorType() { result.getValue() = value } + + override string toString() { if value = true then result = "match" else result = "no-match" } } /** * A completion that represents evaluation of a statement or an * expression resulting in a return. */ -class ReturnCompletion extends Completion, TReturnCompletion { +class ReturnCompletion extends Completion { + ReturnCompletion() { + this = TReturnCompletion() or + this = TNestedCompletion(_, TReturnCompletion(), _) + } + override ReturnSuccessor getAMatchingSuccessorType() { any() } - override string toString() { result = "return" } + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TReturnCompletion() and result = "return" + } } /** * A completion that represents evaluation of a statement or an * expression resulting in a break from a loop. */ -class BreakCompletion extends Completion, TBreakCompletion { +class BreakCompletion extends Completion { + BreakCompletion() { + this = TBreakCompletion() or + this = TNestedCompletion(_, TBreakCompletion(), _) + } + override BreakSuccessor getAMatchingSuccessorType() { any() } - override string toString() { result = "break" } + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TBreakCompletion() and result = "break" + } } /** * A completion that represents evaluation of a statement or an * expression resulting in a continuation of a loop. */ -class NextCompletion extends Completion, TNextCompletion { +class NextCompletion extends Completion { + NextCompletion() { + this = TNextCompletion() or + this = TNestedCompletion(_, TNextCompletion(), _) + } + override NextSuccessor getAMatchingSuccessorType() { any() } - override string toString() { result = "next" } + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TNextCompletion() and result = "next" + } } /** * A completion that represents evaluation of a statement or an * expression resulting in a redo of a loop iteration. */ -class RedoCompletion extends Completion, TRedoCompletion { +class RedoCompletion extends Completion { + RedoCompletion() { + this = TRedoCompletion() or + this = TNestedCompletion(_, TRedoCompletion(), _) + } + override RedoSuccessor getAMatchingSuccessorType() { any() } - override string toString() { result = "redo" } + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TRedoCompletion() and result = "redo" + } } /** * A completion that represents evaluation of a statement or an * expression resulting in a retry. */ -class RetryCompletion extends Completion, TRetryCompletion { +class RetryCompletion extends Completion { + RetryCompletion() { + this = TRetryCompletion() or + this = TNestedCompletion(_, TRetryCompletion(), _) + } + override RetrySuccessor getAMatchingSuccessorType() { any() } - override string toString() { result = "retry" } + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TRetryCompletion() and result = "retry" + } } /** * A completion that represents evaluation of a statement or an * expression resulting in a thrown exception. */ -class RaiseCompletion extends Completion, TRaiseCompletion { +class RaiseCompletion extends Completion { + RaiseCompletion() { + this = TRaiseCompletion() or + this = TNestedCompletion(_, TRaiseCompletion(), _) + } + override RaiseSuccessor getAMatchingSuccessorType() { any() } - override string toString() { result = "raise" } + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TRaiseCompletion() and result = "raise" + } } /** * A completion that represents evaluation of a statement or an * expression resulting in an abort/exit. */ -class ExitCompletion extends Completion, TExitCompletion { +class ExitCompletion extends Completion { + ExitCompletion() { + this = TExitCompletion() or + this = TNestedCompletion(_, TExitCompletion(), _) + } + override ExitSuccessor getAMatchingSuccessorType() { any() } - override string toString() { result = "exit" } + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TExitCompletion() and result = "exit" + } } /** @@ -296,25 +417,60 @@ class ExitCompletion extends Completion, TExitCompletion { * the `while` loop can have a nested completion where the inner completion * is a `break` and the outer completion is a simple successor. */ -class NestedCompletion extends Completion, TNestedCompletion { +abstract class NestedCompletion extends Completion, TNestedCompletion { Completion inner; Completion outer; + int nestLevel; - NestedCompletion() { this = TNestedCompletion(inner, outer) } + NestedCompletion() { this = TNestedCompletion(inner, outer, nestLevel) } - override Completion getInnerCompletion() { result = inner } + /** Gets a completion that is compatible with the inner completion. */ + abstract Completion getAnInnerCompatibleCompletion(); + + /** Gets the level of this nested completion. */ + final int getNestLevel() { result = nestLevel } + + override string toString() { result = outer + " [" + inner + "] (" + nestLevel + ")" } +} + +class NestedBreakCompletion extends NormalCompletion, NestedCompletion { + NestedBreakCompletion() { + inner = TBreakCompletion() and + outer instanceof NonNestedNormalCompletion + } + + override BreakCompletion getInnerCompletion() { result = inner } + + override NonNestedNormalCompletion getOuterCompletion() { result = outer } + + override Completion getAnInnerCompatibleCompletion() { + result = inner and + outer = TSimpleCompletion() + or + result = TNestedCompletion(outer, inner, _) + } + + override SuccessorType getAMatchingSuccessorType() { + outer instanceof SimpleCompletion and + result instanceof BreakSuccessor + or + result = outer.(ConditionalCompletion).getAMatchingSuccessorType() + } +} + +class NestedEnsureCompletion extends NestedCompletion { + NestedEnsureCompletion() { + inner instanceof NormalCompletion and + nestedEnsureCompletion(outer, nestLevel) + } + + override NormalCompletion getInnerCompletion() { result = inner } override Completion getOuterCompletion() { result = outer } - override SuccessorType getAMatchingSuccessorType() { - inner = TBreakCompletion() and - outer = TSimpleCompletion() and - result instanceof BreakSuccessor + override Completion getAnInnerCompatibleCompletion() { + result.getOuterCompletion() = this.getInnerCompletion() } - override string toString() { result = outer + " [" + inner + "]" } -} - -private class NestedNormalCompletion extends NormalCompletion, NestedCompletion { - NestedNormalCompletion() { outer instanceof NormalCompletion } + override SuccessorType getAMatchingSuccessorType() { none() } } diff --git a/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll b/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll index 6d9d4f7b6a3..d0df3768d98 100644 --- a/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll +++ b/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll @@ -38,6 +38,14 @@ private import Completion private import SuccessorTypes private import Splitting +private AstNode parent(AstNode n) { + result.getAFieldOrChild() = n and + not n instanceof CfgScope +} + +/** Gets the CFG scope of node `n`. */ +CfgScope getScope(AstNode n) { result = parent+(n) } + abstract private class ControlFlowTree extends AstNode { /** * Holds if `first` is the first element executed within this AST node. @@ -227,7 +235,7 @@ abstract private class LeafTree extends PreOrderTree, PostOrderTree { } /** Defines the CFG by dispatch on the various AST types. */ -private module Trees { +module Trees { private class AliasTree extends StandardPreOrderTree, Alias { final override AstNode getChildNode(int i) { i = 0 and result = this.getName() @@ -262,12 +270,9 @@ private module Trees { final override Interpolation getChildNode(int i) { result = this.getChild(i) } } - private class BeginTree extends StandardPreOrderTree, Begin { - final override AstNode getChildNode(int i) { - result = this.getChild(i) and - not result instanceof Rescue and - not result instanceof Else and - not result instanceof Ensure + private class BeginTree extends RescueEnsureBlockTree, Begin { + final override AstNode getChildNode(int i, boolean rescuable) { + result = this.getChild(i) and rescuable = true } override predicate isHidden() { any() } @@ -370,11 +375,14 @@ private module Trees { private class CharacterTree extends LeafTree, Character { } - private class ClassTree extends StandardPreOrderTree, Class { - final override AstNode getChildNode(int i) { - result = this.getName() and i = 0 - or - result = this.getChild(i - 1) + private class ClassTree extends RescueEnsureBlockTree, Class { + final override AstNode getChildNode(int i, boolean rescuable) { + rescuable = true and + ( + result = this.getName() and i = 0 + or + result = this.getChild(i - 1) + ) } } @@ -399,11 +407,11 @@ private module Trees { override predicate isHidden() { any() } } - private class DoBlockTree extends StandardPreOrderTree, DoBlock { - final override AstNode getChildNode(int i) { - result = this.getParameters() and i = 0 + private class DoBlockTree extends RescueEnsureBlockTree, DoBlock { + final override AstNode getChildNode(int i, boolean rescuable) { + result = this.getParameters() and i = 0 and rescuable = false or - result = this.getChild(i - 1) + result = this.getChild(i - 1) and rescuable = true } override predicate isHidden() { any() } @@ -431,8 +439,6 @@ private module Trees { private class EnsureTree extends StandardPreOrderTree, Ensure { final override AstNode getChildNode(int i) { result = this.getChild(i) } - - override predicate isHidden() { any() } } private class EscapeSequenceTree extends LeafTree, EscapeSequence { } @@ -443,8 +449,58 @@ private module Trees { override predicate isHidden() { any() } } - private class ExceptionsTree extends StandardPostOrderTree, Exceptions { - final override AstNode getChildNode(int i) { result = this.getChild(i) } + private class ExceptionsTree extends PreOrderTree, Exceptions { + final override predicate propagatesAbnormal(AstNode child) { none() } + + /** Holds if this exception filter belongs to a final `rescue` block. */ + pragma[noinline] + private predicate inLastRescue() { + exists(RescueEnsureBlockTree block, Rescue rescue, int i | + rescue = block.getRescue(i) and + this = rescue.getExceptions() and + not exists(block.getRescue(i + 1)) + ) + } + + final override predicate last(AstNode last, Completion c) { + last(this.getChild(_), last, c) and + c.(MatchingCompletion).getValue() = true + or + exists(int lst, Completion c0 | + last(this.getChild(lst), last, c0) and + not exists(this.getChild(lst + 1)) + | + // The last exception filter in a last `rescue` block propagates + // the exception when it doesn't match + this.inLastRescue() and + c = + any(NestedEnsureCompletion nec | + nec.getOuterCompletion() instanceof RaiseCompletion and + nec.getInnerCompletion() = c0 and + c0.(MatchingCompletion).getValue() = false and + nec.getNestLevel() = 0 + ) + or + c = c0 and + ( + not this.inLastRescue() + or + not c0.(MatchingCompletion).getValue() = false + ) + ) + } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + pred = this and + first(this.getChild(0), succ) and + c instanceof SimpleCompletion + or + exists(int i | + last(this.getChild(i), pred, c) and + c.(MatchingCompletion).getValue() = false and + first(this.getChild(i + 1), succ) + ) + } override predicate isHidden() { any() } } @@ -493,18 +549,14 @@ private module Trees { final override predicate last(AstNode last, Completion c) { last = this and - c.(EmptinessCompletion).isEmpty() + c.(EmptinessCompletion).getValue() = true or last(this.getBody(), last, c) and not c.continuesLoop() and not c instanceof BreakCompletion and not c instanceof RedoCompletion or - c = - any(NestedCompletion nc | - last(this.getBody(), last, nc.getInnerCompletion().(BreakCompletion)) and - nc.getOuterCompletion() instanceof SimpleCompletion - ) + last(this.getBody(), last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion()) } /** @@ -521,8 +573,7 @@ private module Trees { or pred = this and first(this.getPattern(0), succ) and - c instanceof EmptinessCompletion and - not c.(EmptinessCompletion).isEmpty() + c.(EmptinessCompletion).getValue() = false or exists(int i, ControlFlowTree next | last(this.getPattern(i), pred, c) and @@ -719,11 +770,11 @@ private module Trees { } } - private class MethodTree extends StandardPreOrderTree, Method { - final override AstNode getChildNode(int i) { - result = this.getParameters() and i = 0 + private class MethodTree extends RescueEnsureBlockTree, Method { + final override AstNode getChildNode(int i, boolean rescuable) { + result = this.getParameters() and i = 0 and rescuable = false or - result = this.getChild(i - 1) + result = this.getChild(i - 1) and rescuable = true } override predicate isHidden() { any() } @@ -744,11 +795,14 @@ private module Trees { override predicate isHidden() { any() } } - private class ModuleTree extends StandardPreOrderTree, Module { - final override AstNode getChildNode(int i) { - result = this.getName() and i = 0 - or - result = this.getChild(i - 1) + private class ModuleTree extends RescueEnsureBlockTree, Module { + final override AstNode getChildNode(int i, boolean rescuable) { + rescuable = true and + ( + result = this.getName() and i = 0 + or + result = this.getChild(i - 1) + ) } } @@ -809,13 +863,272 @@ private module Trees { final override Interpolation getChildNode(int i) { result = this.getChild(i) } } - private class RescueTree extends StandardPreOrderTree, Rescue { - final override AstNode getChildNode(int i) { - result = this.getExceptions() and i = 0 + private class RescueTree extends PreOrderTree, Rescue { + final override predicate propagatesAbnormal(AstNode child) { child = this.getExceptions() } + + final override predicate last(AstNode last, Completion c) { + last(this.getExceptions(), last, c) and + c.(MatchingCompletion).getValue() = false or - result = this.getVariable() and i = 1 + last(this.getBody(), last, c) or - result = this.getBody() and i = 2 + not exists(this.getExceptions()) and + not exists(this.getBody()) and + ( + last(this.getVariable(), last, c) + or + not exists(this.getVariable()) and + last = this and + c.isValidFor(this) + ) + } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + exists(AstNode next | + pred = this and + first(next, succ) and + c instanceof SimpleCompletion + | + next = this.getExceptions() + or + not exists(this.getExceptions()) and + ( + next = this.getVariable() + or + not exists(this.getVariable()) and + next = this.getBody() + ) + ) + or + exists(AstNode next | + last(this.getExceptions(), pred, c) and + first(next, succ) and + c.(MatchingCompletion).getValue() = true + | + next = this.getVariable() + or + not exists(this.getVariable()) and + next = this.getBody() + ) + or + last(this.getVariable(), pred, c) and + first(this.getBody(), succ) and + c instanceof NormalCompletion + } + } + + /** Gets a child of `n` that is in CFG scope `scope`. */ + pragma[noinline] + private AstNode getAChildInScope(AstNode n, CfgScope scope) { + result.getParent() = n and + scope = getScope(result) + } + + /** A block that may contain `rescue`/`ensure`. */ + abstract class RescueEnsureBlockTree extends PreOrderTree { + /** + * Gets the `i`th child of this block. `rescuable` indicates whether exceptional + * execution of the child can be caught by `rescue`/`ensure`. + */ + abstract AstNode getChildNode(int i, boolean rescuable); + + /** Gets the `i`th child in the body of this block. */ + final private AstNode getBodyChild(int i, boolean rescuable) { + result = this.getChildNode(_, rescuable) and + result = + rank[i + 1](AstNode child, int j | + child = this.getChildNode(j, _) and + not result instanceof Rescue and + not result instanceof Ensure and + not result instanceof Else and + not child instanceof CfgScope + | + child order by j + ) + } + + /** Gets the `i`th `rescue` block in this block. */ + final Rescue getRescue(int i) { + result = rank[i + 1](Rescue s | s = this.getAFieldOrChild() | s order by s.getParentIndex()) + } + + /** Gets the `else` block in this block, if any. */ + final private Else getElse() { result = unique(Else s | s = this.getAFieldOrChild()) } + + /** Gets the `ensure` block in this block, if any. */ + final Ensure getEnsure() { result = unique(Ensure s | s = this.getAFieldOrChild()) } + + final private predicate hasEnsure() { exists(this.getEnsure()) } + + final override predicate propagatesAbnormal(AstNode child) { child = this.getEnsure() } + + /** + * Gets a descendant that belongs to the `ensure` block of this block, if any. + * Nested `ensure` blocks are not included. + */ + AstNode getAnEnsureDescendant() { + result = this.getEnsure() + or + exists(AstNode mid | + mid = this.getAnEnsureDescendant() and + result = getAChildInScope(mid, getScope(mid)) and + not exists(RescueEnsureBlockTree nestedBlock | + result = nestedBlock.getEnsure() and + nestedBlock != this + ) + ) + } + + /** + * Holds if `innerBlock` has an `ensure` block and is immediately nested inside the + * `ensure` block of this block. + */ + private predicate nestedEnsure(RescueEnsureBlockTree innerBlock) { + exists(Ensure innerEnsure | + innerEnsure = getAChildInScope(this.getAnEnsureDescendant(), getScope(this)) and + innerEnsure = innerBlock.getEnsure() + ) + } + + /** + * Gets the `ensure`-nesting level of this block. That is, the number of `ensure` + * blocks that this block is nested under. + */ + int nestLevel() { result = count(RescueEnsureBlockTree outer | outer.nestedEnsure+(this)) } + + /** + * Holds if `last` is a last element in the body of this block. `ensurable` + * indicates whether `last` may be a predecessor of an `ensure` block. + */ + pragma[nomagic] + private predicate lastBody(AstNode last, Completion c, boolean ensurable) { + exists(boolean rescuable | + if c instanceof RaiseCompletion then ensurable = rescuable else ensurable = true + | + last(this.getBodyChild(_, rescuable), last, c) and + not c instanceof NormalCompletion + or + exists(int lst | + last(this.getBodyChild(lst, rescuable), last, c) and + not exists(this.getBodyChild(lst + 1, _)) + ) + ) + } + + /** + * Gets a last element from this block that may finish with completion `c`, such + * that control may be transferred to the `ensure` block (if it exists), but only + * if `ensurable = true`. + */ + pragma[nomagic] + private AstNode getAnEnsurePredecessor(Completion c, boolean ensurable) { + exists(boolean ensurable0 | + // Exit completions ignore the `ensure` block (TODO: CHECK THIS) + if c instanceof ExitCompletion then ensurable = false else ensurable = ensurable0 + | + this.lastBody(result, c, ensurable0) and + ( + // Any non-throw completion will always continue directly to the `ensure` block, + // unless there is an `else` block + not c instanceof RaiseCompletion and + not exists(this.getElse()) + or + // Any completion will continue to the `ensure` block when there are no `rescue` + // blocks + not exists(this.getRescue(_)) + ) + or + // Last element from any of the `rescue` blocks continues to the `ensure` block + last(this.getRescue(_).getBody(), result, c) and + ensurable0 = true + or + // Last element of last `rescue` block continues to the `ensure` block + exists(int lst | + last(this.getRescue(lst), result, c) and + not exists(this.getRescue(lst + 1)) and + ensurable0 = true + ) + or + // Last element of `else` block continues to the `ensure` block + last(this.getElse(), result, c) and + ensurable0 = true + ) + } + + pragma[nomagic] + private predicate lastEnsure0(AstNode last, Completion c) { last(this.getEnsure(), last, c) } + + pragma[nomagic] + private predicate lastEnsure( + AstNode last, NormalCompletion ensure, Completion outer, int nestLevel + ) { + this.lastEnsure0(last, ensure) and + exists( + this.getAnEnsurePredecessor(any(Completion c0 | outer = c0.getOuterCompletion()), true) + ) and + nestLevel = this.nestLevel() + } + + override predicate last(AstNode last, Completion c) { + exists(boolean ensurable | last = this.getAnEnsurePredecessor(c, ensurable) | + not this.hasEnsure() + or + ensurable = false + ) + or + // If the body completes normally, take the completion from the `ensure` block + this.lastEnsure(last, c, any(NormalCompletion nc), _) + or + // If the `ensure` block completes normally, it inherits any non-normal + // completion from the body + c = + any(NestedEnsureCompletion nec | + this + .lastEnsure(last, nec.getAnInnerCompatibleCompletion(), nec.getOuterCompletion(), + nec.getNestLevel()) + ) + } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + pred = this and + first(this.getBodyChild(0, _), succ) and + c instanceof SimpleCompletion + or + // Normal left-to-right evaluation in the body + exists(int i | + last(this.getBodyChild(i, _), pred, c) and + first(this.getBodyChild(i + 1, _), succ) and + c instanceof NormalCompletion + ) + or + // Exceptional flow from body to first `rescue` + this.lastBody(pred, c, true) and + first(this.getRescue(0), succ) and + c instanceof RaiseCompletion + or + exists(Rescue rescue, int i | rescue = this.getRescue(i) | + pred = rescue and + last(this.getRescue(i), rescue, c) and + ( + // Flow from one `rescue` clause to the next when there is no match + first(this.getRescue(i + 1), succ) and + c.(MatchingCompletion).getValue() = false + or + // Flow from last `rescue` clause to `ensure` block + not exists(this.getRescue(i + 1)) and + first(this.getEnsure(), succ) and + c.getInnerCompletion().(MatchingCompletion).getValue() = false + ) + ) + or + // Flow from body to `else` block when no exception + this.lastBody(pred, c, _) and + first(this.getElse(), succ) and + c instanceof NormalCompletion + or + // Flow into `ensure` block + pred = getAnEnsurePredecessor(c, true) and + first(this.getEnsure(), succ) } } @@ -870,23 +1183,31 @@ private module Trees { private class SetterTree extends LeafTree, Setter { } - private class SingletonClassTree extends StandardPreOrderTree, SingletonClass { - final override AstNode getChildNode(int i) { - result = this.getValue() and i = 0 - or - result = this.getChild(i - 1) + private class SingletonClassTree extends RescueEnsureBlockTree, SingletonClass { + final override AstNode getChildNode(int i, boolean rescuable) { + rescuable = true and + ( + result = this.getValue() and i = 0 + or + result = this.getChild(i - 1) + ) } override predicate isHidden() { any() } } - private class SingletonMethodTree extends StandardPreOrderTree, SingletonMethod { - final override AstNode getChildNode(int i) { - result = this.getObject() and i = 0 + private class SingletonMethodTree extends RescueEnsureBlockTree, SingletonMethod { + final override AstNode getChildNode(int i, boolean rescuable) { + result = this.getObject() and + i = 0 and + rescuable = true // TODO: CHECK THIS or - result = this.getParameters() and i = 1 + result = this.getParameters() and + i = 1 and + rescuable = false or - result = this.getChild(i - 2) + result = this.getChild(i - 2) and + rescuable = true } override predicate isHidden() { any() } @@ -990,11 +1311,7 @@ private module Trees { not c instanceof BreakCompletion and not c instanceof RedoCompletion or - c = - any(NestedCompletion nc | - last(this.getBodyNode(), last, nc.getInnerCompletion().(BreakCompletion)) and - nc.getOuterCompletion() instanceof SimpleCompletion - ) + last(this.getBodyNode(), last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion()) } final override predicate succ(AstNode pred, AstNode succ, Completion c) { @@ -1048,8 +1365,9 @@ private module Cached { cached newtype TSuccessorType = TSuccessorSuccessor() or - TBooleanSuccessor(boolean b) { b = true or b = false } or - TEmptinessSuccessor(boolean isEmpty) { isEmpty = true or isEmpty = false } or + TBooleanSuccessor(boolean b) { b in [false, true] } or + TEmptinessSuccessor(boolean isEmpty) { isEmpty in [false, true] } or + TMatchingSuccessor(boolean isMatch) { isMatch in [false, true] } or TReturnSuccessor() or TBreakSuccessor() or TNextSuccessor() or diff --git a/ql/src/codeql_ruby/controlflow/internal/NonReturning.qll b/ql/src/codeql_ruby/controlflow/internal/NonReturning.qll index 29fcfb7fd6a..dc065750c5f 100644 --- a/ql/src/codeql_ruby/controlflow/internal/NonReturning.qll +++ b/ql/src/codeql_ruby/controlflow/internal/NonReturning.qll @@ -12,7 +12,7 @@ abstract class NonReturningCall extends AstNode { private class RaiseCall extends NonReturningCall, MethodCall { RaiseCall() { this.getMethod().toString() = "raise" } - override RaiseCompletion getACompletion() { any() } + override RaiseCompletion getACompletion() { not result instanceof NestedCompletion } } private class ExitCall extends NonReturningCall, MethodCall { diff --git a/ql/src/codeql_ruby/controlflow/internal/Splitting.qll b/ql/src/codeql_ruby/controlflow/internal/Splitting.qll index 6f8498b3f24..0e525beef9f 100644 --- a/ql/src/codeql_ruby/controlflow/internal/Splitting.qll +++ b/ql/src/codeql_ruby/controlflow/internal/Splitting.qll @@ -15,10 +15,16 @@ private int maxSplits() { result = 5 } cached private module Cached { cached - newtype TSplitKind = TConditionalCompletionSplitKind() + newtype TSplitKind = + TConditionalCompletionSplitKind() or + TEnsureSplitKind(int nestLevel) { nestLevel = any(Trees::RescueEnsureBlockTree t).nestLevel() } cached - newtype TSplit = TConditionalCompletionSplit(BooleanCompletion c) + newtype TSplit = + TConditionalCompletionSplit(ConditionalCompletion c) or + TEnsureSplit(EnsureSplitting::EnsureSplitType type, int nestLevel) { + nestLevel = any(Trees::RescueEnsureBlockTree t).nestLevel() + } cached newtype TSplits = @@ -60,13 +66,6 @@ class Split extends TSplit { string toString() { none() } } -private AstNode parent(AstNode n) { - result.getAFieldOrChild() = n and - not n instanceof CfgScope -} - -private CfgScope getScope(AstNode n) { result = parent+(n) } - /** * Holds if split kinds `sk1` and `sk2` may overlap. That is, they may apply * to at least one common AST node inside `scope`. @@ -195,7 +194,7 @@ private module ConditionalCompletionSplitting { * restrict the edges out of `x < 2 and x > 0` accordingly. */ class ConditionalCompletionSplit extends Split, TConditionalCompletionSplit { - BooleanCompletion completion; + ConditionalCompletion completion; ConditionalCompletionSplit() { this = TConditionalCompletionSplit(completion) } @@ -238,19 +237,258 @@ private module ConditionalCompletionSplitting { override predicate hasExit(AstNode pred, AstNode succ, Completion c) { this.appliesTo(pred) and succ(pred, succ, c) and - if c instanceof BooleanCompletion then completion = c else any() + if c instanceof ConditionalCompletion then completion = c else any() } override predicate hasExitScope(AstNode last, CfgScope scope, Completion c) { this.appliesTo(last) and succExit(last, scope, c) and - if c instanceof BooleanCompletion then completion = c else any() + if c instanceof ConditionalCompletion then completion = c else any() } override predicate hasSuccessor(AstNode pred, AstNode succ, Completion c) { none() } } } +module EnsureSplitting { + /** + * The type of a split `ensure` node. + * + * The type represents one of the possible ways of entering an `ensure` + * block. For example, if a block ends with a `return` statement, then + * the `ensure` block must end with a `return` as well (provided that + * the `ensure` block executes normally). + */ + class EnsureSplitType extends SuccessorType { + EnsureSplitType() { not this instanceof ConditionalSuccessor } + + /** Holds if this split type matches entry into an `ensure` block with completion `c`. */ + predicate isSplitForEntryCompletion(Completion c) { + if c instanceof NormalCompletion + then + // If the entry into the `ensure` block completes with any normal completion, + // it simply means normal execution after the `ensure` block + this instanceof NormalSuccessor + else this = c.getAMatchingSuccessorType() + } + } + + /** A node that belongs to an `ensure` block. */ + private class EnsureNode extends AstNode { + private Trees::RescueEnsureBlockTree block; + + EnsureNode() { this = block.getAnEnsureDescendant() } + + /** Gets the immediate block that this node belongs to. */ + Trees::RescueEnsureBlockTree getBlock() { result = block } + + /** Holds if this node is the entry node in the `ensure` block it belongs to. */ + predicate isEntryNode() { first(block.getEnsure(), this) } + } + + /** A node that does not belong to an `ensure` block. */ + private class NonEnsureNode extends EnsureNode { + NonEnsureNode() { not this = any(Trees::RescueEnsureBlockTree t).getAnEnsureDescendant() } + } + + /** + * A split for nodes belonging to an `ensure` block, which determines how to + * continue execution after leaving the `ensure` block. For example, in + * + * ```rb + * begin + * if x + * raise "Exception" + * end + * ensure + * puts "Ensure" + * end + * ``` + * + * all control flow nodes in the `ensure` block have two splits: one representing + * normal execution of the body (when `x` evaluates to `true`), and one representing + * exceptional execution of the body (when `x` evaluates to `false`). + */ + class EnsureSplit extends Split, TEnsureSplit { + private EnsureSplitType type; + private int nestLevel; + + EnsureSplit() { this = TEnsureSplit(type, nestLevel) } + + /** + * Gets the type of this `ensure` split, that is, how to continue execution after the + * `ensure` block. + */ + EnsureSplitType getType() { result = type } + + /** Gets the nesting level. */ + int getNestLevel() { result = nestLevel } + + override string toString() { + if type instanceof NormalSuccessor + then result = "" + else + if nestLevel > 0 + then result = "ensure(" + nestLevel + "): " + type.toString() + else result = "ensure: " + type.toString() + } + } + + private int getListOrder(EnsureSplitKind kind) { + result = ConditionalCompletionSplitting::getNextListOrder() + kind.getNestLevel() + } + + int getNextListOrder() { + result = max([getListOrder(_) + 1, ConditionalCompletionSplitting::getNextListOrder()]) + } + + private class EnsureSplitKind extends SplitKind, TEnsureSplitKind { + private int nestLevel; + + EnsureSplitKind() { this = TEnsureSplitKind(nestLevel) } + + /** Gets the nesting level. */ + int getNestLevel() { result = nestLevel } + + override int getListOrder() { result = getListOrder(this) } + + override string toString() { result = "ensure (" + nestLevel + ")" } + } + + pragma[noinline] + private predicate hasEntry0(AstNode pred, EnsureNode succ, int nestLevel, Completion c) { + succ.isEntryNode() and + nestLevel = succ.getBlock().nestLevel() and + succ(pred, succ, c) + } + + private class EnsureSplitImpl extends SplitImpl, EnsureSplit { + override EnsureSplitKind getKind() { result.getNestLevel() = this.getNestLevel() } + + override predicate hasEntry(AstNode pred, AstNode succ, Completion c) { + hasEntry0(pred, succ, this.getNestLevel(), c) and + this.getType().isSplitForEntryCompletion(c) + } + + override predicate hasEntryScope(CfgScope scope, AstNode first) { none() } + + /** + * Holds if this split applies to `pred`, where `pred` is a valid predecessor. + */ + private predicate appliesToPredecessor(AstNode pred) { + this.appliesTo(pred) and + (succ(pred, _, _) or succExit(pred, _, _)) + } + + pragma[noinline] + private predicate exit0( + AstNode pred, Trees::RescueEnsureBlockTree block, int nestLevel, Completion c + ) { + this.appliesToPredecessor(pred) and + nestLevel = block.nestLevel() and + last(block, pred, c) + } + + /** + * Holds if `pred` may exit this split with completion `c`. The Boolean + * `inherited` indicates whether `c` is an inherited completion from the + * body. + */ + private predicate exit( + Trees::RescueEnsureBlockTree block, AstNode pred, Completion c, boolean inherited + ) { + exists(EnsureSplitType type | + exit0(pred, block, this.getNestLevel(), c) and + type = this.getType() + | + if last(block.getEnsure(), pred, c) + then + // `ensure` block can itself exit with completion `c`: either `c` must + // match this split, `c` must be an abnormal completion, or this split + // does not require another completion to be recovered + inherited = false and + ( + type = c.getAMatchingSuccessorType() + or + not c instanceof NormalCompletion + or + type instanceof NormalSuccessor + ) + else ( + // `ensure` block can exit with inherited completion `c`, which must + // match this split + inherited = true and + type = c.getAMatchingSuccessorType() and + not type instanceof NormalSuccessor + ) + ) + or + // If this split is normal, and an outer split can exit based on an inherited + // completion, we need to exit this split as well. For example, in + // + // ```rb + // def m(b1, b2) + // if b1 + // return + // end + // ensure + // begin + // if b2 + // raise "Exception" + // end + // ensure + // puts "inner ensure" + // end + // end + // ``` + // + // if the outer split for `puts "inner ensure"` is `return` and the inner split + // is "normal" (corresponding to `b1 = true` and `b2 = false`), then the inner + // split must be able to exit with a `return` completion. + this.appliesToPredecessor(pred) and + exists(EnsureSplitImpl outer | + outer.getNestLevel() = this.getNestLevel() - 1 and + outer.exit(_, pred, c, inherited) and + this.getType() instanceof NormalSuccessor and + inherited = true + ) + } + + override predicate hasExit(AstNode pred, AstNode succ, Completion c) { + succ(pred, succ, c) and + ( + exit(_, pred, c, _) + or + exit(_, pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _) + ) + } + + override predicate hasExitScope(AstNode last, CfgScope scope, Completion c) { + succExit(last, scope, c) and + ( + exit(_, last, c, _) + or + exit(_, last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _) + ) + } + + override predicate hasSuccessor(AstNode pred, AstNode succ, Completion c) { + this.appliesToPredecessor(pred) and + succ(pred, succ, c) and + succ = + any(EnsureNode en | + if en.isEntryNode() + then + // entering a nested `ensure` block + en.getBlock().nestLevel() > this.getNestLevel() + else + // staying in the same (possibly nested) `ensure` block as `pred` + en.getBlock().nestLevel() >= this.getNestLevel() + ) + } + } +} + /** * A set of control flow node splits. The set is represented by a list of splits, * ordered by ascending rank. diff --git a/ql/test/library-tests/controlflow/graph/Cfg.expected b/ql/test/library-tests/controlflow/graph/Cfg.expected index 1ff670286ec..2d1ad8f449c 100644 --- a/ql/test/library-tests/controlflow/graph/Cfg.expected +++ b/ql/test/library-tests/controlflow/graph/Cfg.expected @@ -11,6 +11,7 @@ nodes | break_ensure.rb:3:8:3:12 | Binary | | break_ensure.rb:3:12:3:12 | 0 | | break_ensure.rb:4:7:4:11 | Break | +| break_ensure.rb:7:1:10:5 | Ensure | | break_ensure.rb:8:3:10:5 | If | | break_ensure.rb:8:6:8:13 | elements | | break_ensure.rb:8:6:8:18 | Call | @@ -30,6 +31,22 @@ nodes | break_ensure.rb:16:10:16:14 | Binary | | break_ensure.rb:16:14:16:14 | 0 | | break_ensure.rb:17:9:17:13 | Break | +| break_ensure.rb:19:5:22:9 | Ensure | +| break_ensure.rb:19:5:22:9 | [ensure: break] Ensure | +| break_ensure.rb:20:7:22:9 | If | +| break_ensure.rb:20:7:22:9 | [ensure: break] If | +| break_ensure.rb:20:10:20:17 | [ensure: break] elements | +| break_ensure.rb:20:10:20:17 | elements | +| break_ensure.rb:20:10:20:22 | Call | +| break_ensure.rb:20:10:20:22 | [ensure: break] Call | +| break_ensure.rb:20:19:20:22 | [ensure: break] nil? | +| break_ensure.rb:20:19:20:22 | nil? | +| break_ensure.rb:21:9:21:12 | [ensure: break] puts | +| break_ensure.rb:21:9:21:12 | puts | +| break_ensure.rb:21:9:21:27 | MethodCall | +| break_ensure.rb:21:9:21:27 | [ensure: break] MethodCall | +| break_ensure.rb:21:14:21:27 | String | +| break_ensure.rb:21:14:21:27 | [ensure: break] String | | break_ensure.rb:27:1:42:3 | enter m3 | | break_ensure.rb:27:1:42:3 | exit m3 | | break_ensure.rb:27:1:42:3 | exit m3 (normal) | @@ -39,6 +56,24 @@ nodes | break_ensure.rb:29:8:29:20 | Call | | break_ensure.rb:29:17:29:20 | nil? | | break_ensure.rb:30:7:30:12 | Return | +| break_ensure.rb:32:3:39:7 | Ensure | +| break_ensure.rb:32:3:39:7 | [ensure: return] Ensure | +| break_ensure.rb:33:5:39:7 | For | +| break_ensure.rb:33:5:39:7 | [ensure: return] For | +| break_ensure.rb:33:9:33:15 | [ensure: return] element | +| break_ensure.rb:33:9:33:15 | element | +| break_ensure.rb:33:20:33:27 | [ensure: return] elements | +| break_ensure.rb:33:20:33:27 | elements | +| break_ensure.rb:35:9:37:11 | If | +| break_ensure.rb:35:9:37:11 | [ensure: return] If | +| break_ensure.rb:35:12:35:12 | [ensure: return] x | +| break_ensure.rb:35:12:35:12 | x | +| break_ensure.rb:35:12:35:16 | Binary | +| break_ensure.rb:35:12:35:16 | [ensure: return] Binary | +| break_ensure.rb:35:16:35:16 | 0 | +| break_ensure.rb:35:16:35:16 | [ensure: return] 0 | +| break_ensure.rb:36:11:36:15 | Break | +| break_ensure.rb:36:11:36:15 | [ensure: return] Break | | break_ensure.rb:41:3:41:6 | puts | | break_ensure.rb:41:3:41:13 | MethodCall | | break_ensure.rb:41:8:41:13 | String | @@ -263,6 +298,13 @@ nodes | cfg.rb:75:35:75:36 | 10 | | cfg.rb:75:43:75:43 | x | | cfg.rb:78:3:78:3 | ; | +| cfg.rb:83:3:83:6 | puts | +| cfg.rb:83:3:83:11 | MethodCall | +| cfg.rb:83:8:83:11 | String | +| cfg.rb:84:1:85:12 | Ensure | +| cfg.rb:85:3:85:6 | puts | +| cfg.rb:85:3:85:12 | MethodCall | +| cfg.rb:85:8:85:12 | String | | cfg.rb:88:1:88:6 | escape | | cfg.rb:88:1:88:23 | Assignment | | cfg.rb:88:10:88:23 | String | @@ -782,12 +824,16 @@ nodes | raise.rb:17:7:17:11 | raise | | raise.rb:17:7:17:22 | MethodCall | | raise.rb:17:13:17:22 | ExceptionA | +| raise.rb:19:3:20:18 | Rescue | +| raise.rb:19:10:19:19 | ExceptionA | +| raise.rb:20:5:20:8 | puts | +| raise.rb:20:5:20:18 | MethodCall | +| raise.rb:20:10:20:18 | String | | raise.rb:22:3:22:6 | puts | | raise.rb:22:3:22:15 | MethodCall | | raise.rb:22:8:22:15 | String | | raise.rb:25:1:34:3 | enter m3 | | raise.rb:25:1:34:3 | exit m3 | -| raise.rb:25:1:34:3 | exit m3 (abnormal) | | raise.rb:25:1:34:3 | exit m3 (normal) | | raise.rb:25:8:25:8 | b | | raise.rb:27:5:29:7 | If | @@ -795,12 +841,15 @@ nodes | raise.rb:28:7:28:11 | raise | | raise.rb:28:7:28:22 | MethodCall | | raise.rb:28:13:28:22 | ExceptionA | +| raise.rb:30:3:31:18 | Rescue | +| raise.rb:31:5:31:8 | puts | +| raise.rb:31:5:31:18 | MethodCall | +| raise.rb:31:10:31:18 | String | | raise.rb:33:3:33:6 | puts | | raise.rb:33:3:33:15 | MethodCall | | raise.rb:33:8:33:15 | String | | raise.rb:36:1:45:3 | enter m4 | | raise.rb:36:1:45:3 | exit m4 | -| raise.rb:36:1:45:3 | exit m4 (abnormal) | | raise.rb:36:1:45:3 | exit m4 (normal) | | raise.rb:36:8:36:8 | b | | raise.rb:38:5:40:7 | If | @@ -808,12 +857,16 @@ nodes | raise.rb:39:7:39:11 | raise | | raise.rb:39:7:39:22 | MethodCall | | raise.rb:39:13:39:22 | ExceptionA | +| raise.rb:41:3:42:22 | Rescue | +| raise.rb:41:13:41:13 | e | +| raise.rb:42:5:42:8 | puts | +| raise.rb:42:5:42:22 | MethodCall | +| raise.rb:42:10:42:22 | String | | raise.rb:44:3:44:6 | puts | | raise.rb:44:3:44:15 | MethodCall | | raise.rb:44:8:44:15 | String | | raise.rb:47:1:55:3 | enter m5 | | raise.rb:47:1:55:3 | exit m5 | -| raise.rb:47:1:55:3 | exit m5 (abnormal) | | raise.rb:47:1:55:3 | exit m5 (normal) | | raise.rb:47:8:47:8 | b | | raise.rb:49:5:51:7 | If | @@ -821,6 +874,8 @@ nodes | raise.rb:50:7:50:11 | raise | | raise.rb:50:7:50:22 | MethodCall | | raise.rb:50:13:50:22 | ExceptionA | +| raise.rb:52:3:52:13 | Rescue | +| raise.rb:52:13:52:13 | e | | raise.rb:54:3:54:6 | puts | | raise.rb:54:3:54:15 | MethodCall | | raise.rb:54:8:54:15 | String | @@ -834,6 +889,13 @@ nodes | raise.rb:60:7:60:11 | raise | | raise.rb:60:7:60:22 | MethodCall | | raise.rb:60:13:60:22 | ExceptionA | +| raise.rb:62:3:63:22 | Rescue | +| raise.rb:62:10:62:19 | ExceptionA | +| raise.rb:62:22:62:31 | ExceptionB | +| raise.rb:62:36:62:36 | e | +| raise.rb:63:5:63:8 | puts | +| raise.rb:63:5:63:22 | MethodCall | +| raise.rb:63:10:63:22 | String | | raise.rb:65:3:65:6 | puts | | raise.rb:65:3:65:15 | MethodCall | | raise.rb:65:8:65:15 | String | @@ -858,9 +920,18 @@ nodes | raise.rb:74:3:74:6 | puts | | raise.rb:74:3:74:20 | MethodCall | | raise.rb:74:8:74:20 | String | +| raise.rb:75:1:76:15 | Ensure | +| raise.rb:75:1:76:15 | [ensure: raise] Ensure | +| raise.rb:75:1:76:15 | [ensure: return] Ensure | +| raise.rb:76:3:76:6 | [ensure: raise] puts | +| raise.rb:76:3:76:6 | [ensure: return] puts | | raise.rb:76:3:76:6 | puts | | raise.rb:76:3:76:15 | MethodCall | +| raise.rb:76:3:76:15 | [ensure: raise] MethodCall | +| raise.rb:76:3:76:15 | [ensure: return] MethodCall | | raise.rb:76:8:76:15 | String | +| raise.rb:76:8:76:15 | [ensure: raise] String | +| raise.rb:76:8:76:15 | [ensure: return] String | | raise.rb:79:1:92:3 | enter m8 | | raise.rb:79:1:92:3 | exit m8 | | raise.rb:79:1:92:3 | exit m8 (abnormal) | @@ -885,6 +956,18 @@ nodes | raise.rb:87:5:87:8 | puts | | raise.rb:87:5:87:22 | MethodCall | | raise.rb:87:10:87:22 | String | +| raise.rb:88:3:89:17 | Ensure | +| raise.rb:88:3:89:17 | [ensure: raise] Ensure | +| raise.rb:88:3:89:17 | [ensure: return] Ensure | +| raise.rb:89:5:89:8 | [ensure: raise] puts | +| raise.rb:89:5:89:8 | [ensure: return] puts | +| raise.rb:89:5:89:8 | puts | +| raise.rb:89:5:89:17 | MethodCall | +| raise.rb:89:5:89:17 | [ensure: raise] MethodCall | +| raise.rb:89:5:89:17 | [ensure: return] MethodCall | +| raise.rb:89:10:89:17 | String | +| raise.rb:89:10:89:17 | [ensure: raise] String | +| raise.rb:89:10:89:17 | [ensure: return] String | | raise.rb:91:3:91:6 | puts | | raise.rb:91:3:91:15 | MethodCall | | raise.rb:91:8:91:15 | String | @@ -914,17 +997,87 @@ nodes | raise.rb:102:5:102:8 | puts | | raise.rb:102:5:102:22 | MethodCall | | raise.rb:102:10:102:22 | String | +| raise.rb:103:3:111:7 | Ensure | +| raise.rb:103:3:111:7 | [ensure: raise] Ensure | +| raise.rb:103:3:111:7 | [ensure: return] Ensure | +| raise.rb:104:5:104:8 | [ensure: raise] puts | +| raise.rb:104:5:104:8 | [ensure: return] puts | +| raise.rb:104:5:104:8 | puts | +| raise.rb:104:5:104:23 | MethodCall | +| raise.rb:104:5:104:23 | [ensure: raise] MethodCall | +| raise.rb:104:5:104:23 | [ensure: return] MethodCall | +| raise.rb:104:10:104:23 | String | +| raise.rb:104:10:104:23 | [ensure: raise] String | +| raise.rb:104:10:104:23 | [ensure: return] String | +| raise.rb:106:7:108:9 | If | +| raise.rb:106:7:108:9 | [ensure: raise] If | +| raise.rb:106:7:108:9 | [ensure: return] If | +| raise.rb:106:10:106:11 | [ensure: raise] b1 | +| raise.rb:106:10:106:11 | [ensure: return] b1 | +| raise.rb:106:10:106:11 | b1 | +| raise.rb:107:9:107:13 | [ensure: raise] raise | +| raise.rb:107:9:107:13 | [ensure: return] raise | +| raise.rb:107:9:107:13 | raise | +| raise.rb:107:9:107:26 | MethodCall | +| raise.rb:107:9:107:26 | [ensure: raise] MethodCall | +| raise.rb:107:9:107:26 | [ensure: return] MethodCall | +| raise.rb:107:15:107:26 | String | +| raise.rb:107:15:107:26 | [ensure: raise] String | +| raise.rb:107:15:107:26 | [ensure: return] String | +| raise.rb:109:5:110:25 | Ensure | +| raise.rb:109:5:110:25 | [ensure(1): raise] Ensure | +| raise.rb:109:5:110:25 | [ensure: raise, ensure(1): raise] Ensure | +| raise.rb:109:5:110:25 | [ensure: raise] Ensure | +| raise.rb:109:5:110:25 | [ensure: return, ensure(1): raise] Ensure | +| raise.rb:109:5:110:25 | [ensure: return] Ensure | +| raise.rb:110:7:110:10 | [ensure(1): raise] puts | +| raise.rb:110:7:110:10 | [ensure: raise, ensure(1): raise] puts | +| raise.rb:110:7:110:10 | [ensure: raise] puts | +| raise.rb:110:7:110:10 | [ensure: return, ensure(1): raise] puts | +| raise.rb:110:7:110:10 | [ensure: return] puts | +| raise.rb:110:7:110:10 | puts | +| raise.rb:110:7:110:25 | MethodCall | +| raise.rb:110:7:110:25 | [ensure(1): raise] MethodCall | +| raise.rb:110:7:110:25 | [ensure: raise, ensure(1): raise] MethodCall | +| raise.rb:110:7:110:25 | [ensure: raise] MethodCall | +| raise.rb:110:7:110:25 | [ensure: return, ensure(1): raise] MethodCall | +| raise.rb:110:7:110:25 | [ensure: return] MethodCall | +| raise.rb:110:12:110:25 | String | +| raise.rb:110:12:110:25 | [ensure(1): raise] String | +| raise.rb:110:12:110:25 | [ensure: raise, ensure(1): raise] String | +| raise.rb:110:12:110:25 | [ensure: raise] String | +| raise.rb:110:12:110:25 | [ensure: return, ensure(1): raise] String | +| raise.rb:110:12:110:25 | [ensure: return] String | | raise.rb:113:3:113:6 | puts | | raise.rb:113:3:113:15 | MethodCall | | raise.rb:113:8:113:15 | String | +| raise.rb:114:1:118:5 | Ensure | +| raise.rb:114:1:118:5 | [ensure: raise] Ensure | +| raise.rb:114:1:118:5 | [ensure: return] Ensure | +| raise.rb:115:3:115:6 | [ensure: raise] puts | +| raise.rb:115:3:115:6 | [ensure: return] puts | | raise.rb:115:3:115:6 | puts | | raise.rb:115:3:115:22 | MethodCall | +| raise.rb:115:3:115:22 | [ensure: raise] MethodCall | +| raise.rb:115:3:115:22 | [ensure: return] MethodCall | | raise.rb:115:8:115:22 | String | +| raise.rb:115:8:115:22 | [ensure: raise] String | +| raise.rb:115:8:115:22 | [ensure: return] String | | raise.rb:116:3:118:5 | If | +| raise.rb:116:3:118:5 | [ensure: raise] If | +| raise.rb:116:3:118:5 | [ensure: return] If | +| raise.rb:116:6:116:7 | [ensure: raise] b2 | +| raise.rb:116:6:116:7 | [ensure: return] b2 | | raise.rb:116:6:116:7 | b2 | +| raise.rb:117:5:117:9 | [ensure: raise] raise | +| raise.rb:117:5:117:9 | [ensure: return] raise | | raise.rb:117:5:117:9 | raise | | raise.rb:117:5:117:22 | MethodCall | +| raise.rb:117:5:117:22 | [ensure: raise] MethodCall | +| raise.rb:117:5:117:22 | [ensure: return] MethodCall | | raise.rb:117:11:117:22 | String | +| raise.rb:117:11:117:22 | [ensure: raise] String | +| raise.rb:117:11:117:22 | [ensure: return] String | | raise.rb:121:1:124:3 | enter m10 | | raise.rb:121:1:124:3 | exit m10 | | raise.rb:121:1:124:3 | exit m10 (abnormal) | @@ -936,7 +1089,7 @@ edges | break_ensure.rb:1:1:11:3 | exit m1 (normal) | break_ensure.rb:1:1:11:3 | exit m1 | semmle.label | successor | | break_ensure.rb:1:8:1:15 | elements | break_ensure.rb:2:18:2:25 | elements | semmle.label | successor | | break_ensure.rb:2:3:6:5 | For | break_ensure.rb:2:7:2:13 | element | semmle.label | non-empty | -| break_ensure.rb:2:3:6:5 | For | break_ensure.rb:8:3:10:5 | If | semmle.label | empty | +| break_ensure.rb:2:3:6:5 | For | break_ensure.rb:7:1:10:5 | Ensure | semmle.label | empty | | break_ensure.rb:2:7:2:13 | element | break_ensure.rb:3:5:5:7 | If | semmle.label | successor | | break_ensure.rb:2:18:2:25 | elements | break_ensure.rb:2:3:6:5 | For | semmle.label | successor | | break_ensure.rb:3:5:5:7 | If | break_ensure.rb:3:8:3:8 | x | semmle.label | successor | @@ -944,7 +1097,8 @@ edges | break_ensure.rb:3:8:3:12 | Binary | break_ensure.rb:2:3:6:5 | For | semmle.label | false | | break_ensure.rb:3:8:3:12 | Binary | break_ensure.rb:4:7:4:11 | Break | semmle.label | true | | break_ensure.rb:3:12:3:12 | 0 | break_ensure.rb:3:8:3:12 | Binary | semmle.label | successor | -| break_ensure.rb:4:7:4:11 | Break | break_ensure.rb:8:3:10:5 | If | semmle.label | break | +| break_ensure.rb:4:7:4:11 | Break | break_ensure.rb:7:1:10:5 | Ensure | semmle.label | break | +| break_ensure.rb:7:1:10:5 | Ensure | break_ensure.rb:8:3:10:5 | If | semmle.label | successor | | break_ensure.rb:8:3:10:5 | If | break_ensure.rb:8:6:8:13 | elements | semmle.label | successor | | break_ensure.rb:8:6:8:13 | elements | break_ensure.rb:8:15:8:18 | nil? | semmle.label | successor | | break_ensure.rb:8:6:8:18 | Call | break_ensure.rb:1:1:11:3 | exit m1 (normal) | semmle.label | false | @@ -962,19 +1116,59 @@ edges | break_ensure.rb:14:18:14:25 | elements | break_ensure.rb:14:3:24:5 | For | semmle.label | successor | | break_ensure.rb:16:7:18:9 | If | break_ensure.rb:16:10:16:10 | x | semmle.label | successor | | break_ensure.rb:16:10:16:10 | x | break_ensure.rb:16:14:16:14 | 0 | semmle.label | successor | -| break_ensure.rb:16:10:16:14 | Binary | break_ensure.rb:14:3:24:5 | For | semmle.label | false | | break_ensure.rb:16:10:16:14 | Binary | break_ensure.rb:17:9:17:13 | Break | semmle.label | true | +| break_ensure.rb:16:10:16:14 | Binary | break_ensure.rb:19:5:22:9 | Ensure | semmle.label | false | | break_ensure.rb:16:14:16:14 | 0 | break_ensure.rb:16:10:16:14 | Binary | semmle.label | successor | -| break_ensure.rb:17:9:17:13 | Break | break_ensure.rb:13:1:25:3 | exit m2 (normal) | semmle.label | break | +| break_ensure.rb:17:9:17:13 | Break | break_ensure.rb:19:5:22:9 | [ensure: break] Ensure | semmle.label | break | +| break_ensure.rb:19:5:22:9 | Ensure | break_ensure.rb:20:7:22:9 | If | semmle.label | successor | +| break_ensure.rb:19:5:22:9 | [ensure: break] Ensure | break_ensure.rb:20:7:22:9 | [ensure: break] If | semmle.label | successor | +| break_ensure.rb:20:7:22:9 | If | break_ensure.rb:20:10:20:17 | elements | semmle.label | successor | +| break_ensure.rb:20:7:22:9 | [ensure: break] If | break_ensure.rb:20:10:20:17 | [ensure: break] elements | semmle.label | successor | +| break_ensure.rb:20:10:20:17 | [ensure: break] elements | break_ensure.rb:20:19:20:22 | [ensure: break] nil? | semmle.label | successor | +| break_ensure.rb:20:10:20:17 | elements | break_ensure.rb:20:19:20:22 | nil? | semmle.label | successor | +| break_ensure.rb:20:10:20:22 | Call | break_ensure.rb:14:3:24:5 | For | semmle.label | false | +| break_ensure.rb:20:10:20:22 | Call | break_ensure.rb:21:14:21:27 | String | semmle.label | true | +| break_ensure.rb:20:10:20:22 | [ensure: break] Call | break_ensure.rb:13:1:25:3 | exit m2 (normal) | semmle.label | false | +| break_ensure.rb:20:10:20:22 | [ensure: break] Call | break_ensure.rb:21:14:21:27 | [ensure: break] String | semmle.label | true | +| break_ensure.rb:20:19:20:22 | [ensure: break] nil? | break_ensure.rb:20:10:20:22 | [ensure: break] Call | semmle.label | successor | +| break_ensure.rb:20:19:20:22 | nil? | break_ensure.rb:20:10:20:22 | Call | semmle.label | successor | +| break_ensure.rb:21:9:21:12 | [ensure: break] puts | break_ensure.rb:21:9:21:27 | [ensure: break] MethodCall | semmle.label | successor | +| break_ensure.rb:21:9:21:12 | puts | break_ensure.rb:21:9:21:27 | MethodCall | semmle.label | successor | +| break_ensure.rb:21:9:21:27 | MethodCall | break_ensure.rb:14:3:24:5 | For | semmle.label | successor | +| break_ensure.rb:21:9:21:27 | [ensure: break] MethodCall | break_ensure.rb:13:1:25:3 | exit m2 (normal) | semmle.label | break | +| break_ensure.rb:21:14:21:27 | String | break_ensure.rb:21:9:21:12 | puts | semmle.label | successor | +| break_ensure.rb:21:14:21:27 | [ensure: break] String | break_ensure.rb:21:9:21:12 | [ensure: break] puts | semmle.label | successor | | break_ensure.rb:27:1:42:3 | enter m3 | break_ensure.rb:27:8:27:15 | elements | semmle.label | successor | | break_ensure.rb:27:1:42:3 | exit m3 (normal) | break_ensure.rb:27:1:42:3 | exit m3 | semmle.label | successor | | break_ensure.rb:27:8:27:15 | elements | break_ensure.rb:29:5:31:7 | If | semmle.label | successor | | break_ensure.rb:29:5:31:7 | If | break_ensure.rb:29:8:29:15 | elements | semmle.label | successor | | break_ensure.rb:29:8:29:15 | elements | break_ensure.rb:29:17:29:20 | nil? | semmle.label | successor | | break_ensure.rb:29:8:29:20 | Call | break_ensure.rb:30:7:30:12 | Return | semmle.label | true | -| break_ensure.rb:29:8:29:20 | Call | break_ensure.rb:41:8:41:13 | String | semmle.label | false | +| break_ensure.rb:29:8:29:20 | Call | break_ensure.rb:32:3:39:7 | Ensure | semmle.label | false | | break_ensure.rb:29:17:29:20 | nil? | break_ensure.rb:29:8:29:20 | Call | semmle.label | successor | -| break_ensure.rb:30:7:30:12 | Return | break_ensure.rb:27:1:42:3 | exit m3 (normal) | semmle.label | return | +| break_ensure.rb:30:7:30:12 | Return | break_ensure.rb:32:3:39:7 | [ensure: return] Ensure | semmle.label | return | +| break_ensure.rb:32:3:39:7 | Ensure | break_ensure.rb:33:20:33:27 | elements | semmle.label | successor | +| break_ensure.rb:32:3:39:7 | [ensure: return] Ensure | break_ensure.rb:33:20:33:27 | [ensure: return] elements | semmle.label | successor | +| break_ensure.rb:33:5:39:7 | For | break_ensure.rb:33:9:33:15 | element | semmle.label | non-empty | +| break_ensure.rb:33:5:39:7 | For | break_ensure.rb:41:8:41:13 | String | semmle.label | empty | +| break_ensure.rb:33:5:39:7 | [ensure: return] For | break_ensure.rb:27:1:42:3 | exit m3 (normal) | semmle.label | return | +| break_ensure.rb:33:5:39:7 | [ensure: return] For | break_ensure.rb:33:9:33:15 | [ensure: return] element | semmle.label | non-empty | +| break_ensure.rb:33:9:33:15 | [ensure: return] element | break_ensure.rb:35:9:37:11 | [ensure: return] If | semmle.label | successor | +| break_ensure.rb:33:9:33:15 | element | break_ensure.rb:35:9:37:11 | If | semmle.label | successor | +| break_ensure.rb:33:20:33:27 | [ensure: return] elements | break_ensure.rb:33:5:39:7 | [ensure: return] For | semmle.label | successor | +| break_ensure.rb:33:20:33:27 | elements | break_ensure.rb:33:5:39:7 | For | semmle.label | successor | +| break_ensure.rb:35:9:37:11 | If | break_ensure.rb:35:12:35:12 | x | semmle.label | successor | +| break_ensure.rb:35:9:37:11 | [ensure: return] If | break_ensure.rb:35:12:35:12 | [ensure: return] x | semmle.label | successor | +| break_ensure.rb:35:12:35:12 | [ensure: return] x | break_ensure.rb:35:16:35:16 | [ensure: return] 0 | semmle.label | successor | +| break_ensure.rb:35:12:35:12 | x | break_ensure.rb:35:16:35:16 | 0 | semmle.label | successor | +| break_ensure.rb:35:12:35:16 | Binary | break_ensure.rb:33:5:39:7 | For | semmle.label | false | +| break_ensure.rb:35:12:35:16 | Binary | break_ensure.rb:36:11:36:15 | Break | semmle.label | true | +| break_ensure.rb:35:12:35:16 | [ensure: return] Binary | break_ensure.rb:33:5:39:7 | [ensure: return] For | semmle.label | false | +| break_ensure.rb:35:12:35:16 | [ensure: return] Binary | break_ensure.rb:36:11:36:15 | [ensure: return] Break | semmle.label | true | +| break_ensure.rb:35:16:35:16 | 0 | break_ensure.rb:35:12:35:16 | Binary | semmle.label | successor | +| break_ensure.rb:35:16:35:16 | [ensure: return] 0 | break_ensure.rb:35:12:35:16 | [ensure: return] Binary | semmle.label | successor | +| break_ensure.rb:36:11:36:15 | Break | break_ensure.rb:41:8:41:13 | String | semmle.label | break | +| break_ensure.rb:36:11:36:15 | [ensure: return] Break | break_ensure.rb:27:1:42:3 | exit m3 (normal) | semmle.label | return | | break_ensure.rb:41:3:41:6 | puts | break_ensure.rb:41:3:41:13 | MethodCall | semmle.label | successor | | break_ensure.rb:41:3:41:13 | MethodCall | break_ensure.rb:27:1:42:3 | exit m3 (normal) | semmle.label | successor | | break_ensure.rb:41:8:41:13 | String | break_ensure.rb:41:3:41:6 | puts | semmle.label | successor | @@ -1203,7 +1397,14 @@ edges | cfg.rb:75:27:75:28 | 10 | cfg.rb:75:23:75:28 | Binary | semmle.label | successor | | cfg.rb:75:35:75:36 | 10 | cfg.rb:78:3:78:3 | ; | semmle.label | successor | | cfg.rb:75:43:75:43 | x | cfg.rb:78:3:78:3 | ; | semmle.label | successor | -| cfg.rb:78:3:78:3 | ; | cfg.rb:88:19:88:19 | x | semmle.label | successor | +| cfg.rb:78:3:78:3 | ; | cfg.rb:83:8:83:11 | String | semmle.label | successor | +| cfg.rb:83:3:83:6 | puts | cfg.rb:83:3:83:11 | MethodCall | semmle.label | successor | +| cfg.rb:83:3:83:11 | MethodCall | cfg.rb:84:1:85:12 | Ensure | semmle.label | successor | +| cfg.rb:83:8:83:11 | String | cfg.rb:83:3:83:6 | puts | semmle.label | successor | +| cfg.rb:84:1:85:12 | Ensure | cfg.rb:85:8:85:12 | String | semmle.label | successor | +| cfg.rb:85:3:85:6 | puts | cfg.rb:85:3:85:12 | MethodCall | semmle.label | successor | +| cfg.rb:85:3:85:12 | MethodCall | cfg.rb:88:19:88:19 | x | semmle.label | successor | +| cfg.rb:85:8:85:12 | String | cfg.rb:85:3:85:6 | puts | semmle.label | successor | | cfg.rb:88:1:88:6 | escape | cfg.rb:88:1:88:23 | Assignment | semmle.label | successor | | cfg.rb:88:1:88:23 | Assignment | cfg.rb:90:11:90:13 | 1.4 | semmle.label | successor | | cfg.rb:88:10:88:23 | String | cfg.rb:88:1:88:6 | escape | semmle.label | successor | @@ -1729,47 +1930,61 @@ edges | raise.rb:16:8:16:8 | b | raise.rb:17:13:17:22 | ExceptionA | semmle.label | true | | raise.rb:16:8:16:8 | b | raise.rb:22:8:22:15 | String | semmle.label | false | | raise.rb:17:7:17:11 | raise | raise.rb:17:7:17:22 | MethodCall | semmle.label | successor | -| raise.rb:17:7:17:22 | MethodCall | raise.rb:14:1:23:3 | exit m2 (abnormal) | semmle.label | raise | +| raise.rb:17:7:17:22 | MethodCall | raise.rb:19:3:20:18 | Rescue | semmle.label | raise | | raise.rb:17:13:17:22 | ExceptionA | raise.rb:17:7:17:11 | raise | semmle.label | successor | +| raise.rb:19:3:20:18 | Rescue | raise.rb:19:10:19:19 | ExceptionA | semmle.label | successor | +| raise.rb:19:10:19:19 | ExceptionA | raise.rb:14:1:23:3 | exit m2 (abnormal) | semmle.label | raise | +| raise.rb:19:10:19:19 | ExceptionA | raise.rb:20:10:20:18 | String | semmle.label | match | +| raise.rb:20:5:20:8 | puts | raise.rb:20:5:20:18 | MethodCall | semmle.label | successor | +| raise.rb:20:5:20:18 | MethodCall | raise.rb:22:8:22:15 | String | semmle.label | successor | +| raise.rb:20:10:20:18 | String | raise.rb:20:5:20:8 | puts | semmle.label | successor | | raise.rb:22:3:22:6 | puts | raise.rb:22:3:22:15 | MethodCall | semmle.label | successor | | raise.rb:22:3:22:15 | MethodCall | raise.rb:14:1:23:3 | exit m2 (normal) | semmle.label | successor | | raise.rb:22:8:22:15 | String | raise.rb:22:3:22:6 | puts | semmle.label | successor | | raise.rb:25:1:34:3 | enter m3 | raise.rb:25:8:25:8 | b | semmle.label | successor | -| raise.rb:25:1:34:3 | exit m3 (abnormal) | raise.rb:25:1:34:3 | exit m3 | semmle.label | successor | | raise.rb:25:1:34:3 | exit m3 (normal) | raise.rb:25:1:34:3 | exit m3 | semmle.label | successor | | raise.rb:25:8:25:8 | b | raise.rb:27:5:29:7 | If | semmle.label | successor | | raise.rb:27:5:29:7 | If | raise.rb:27:8:27:8 | b | semmle.label | successor | | raise.rb:27:8:27:8 | b | raise.rb:28:13:28:22 | ExceptionA | semmle.label | true | | raise.rb:27:8:27:8 | b | raise.rb:33:8:33:15 | String | semmle.label | false | | raise.rb:28:7:28:11 | raise | raise.rb:28:7:28:22 | MethodCall | semmle.label | successor | -| raise.rb:28:7:28:22 | MethodCall | raise.rb:25:1:34:3 | exit m3 (abnormal) | semmle.label | raise | +| raise.rb:28:7:28:22 | MethodCall | raise.rb:30:3:31:18 | Rescue | semmle.label | raise | | raise.rb:28:13:28:22 | ExceptionA | raise.rb:28:7:28:11 | raise | semmle.label | successor | +| raise.rb:30:3:31:18 | Rescue | raise.rb:31:10:31:18 | String | semmle.label | successor | +| raise.rb:31:5:31:8 | puts | raise.rb:31:5:31:18 | MethodCall | semmle.label | successor | +| raise.rb:31:5:31:18 | MethodCall | raise.rb:33:8:33:15 | String | semmle.label | successor | +| raise.rb:31:10:31:18 | String | raise.rb:31:5:31:8 | puts | semmle.label | successor | | raise.rb:33:3:33:6 | puts | raise.rb:33:3:33:15 | MethodCall | semmle.label | successor | | raise.rb:33:3:33:15 | MethodCall | raise.rb:25:1:34:3 | exit m3 (normal) | semmle.label | successor | | raise.rb:33:8:33:15 | String | raise.rb:33:3:33:6 | puts | semmle.label | successor | | raise.rb:36:1:45:3 | enter m4 | raise.rb:36:8:36:8 | b | semmle.label | successor | -| raise.rb:36:1:45:3 | exit m4 (abnormal) | raise.rb:36:1:45:3 | exit m4 | semmle.label | successor | | raise.rb:36:1:45:3 | exit m4 (normal) | raise.rb:36:1:45:3 | exit m4 | semmle.label | successor | | raise.rb:36:8:36:8 | b | raise.rb:38:5:40:7 | If | semmle.label | successor | | raise.rb:38:5:40:7 | If | raise.rb:38:8:38:8 | b | semmle.label | successor | | raise.rb:38:8:38:8 | b | raise.rb:39:13:39:22 | ExceptionA | semmle.label | true | | raise.rb:38:8:38:8 | b | raise.rb:44:8:44:15 | String | semmle.label | false | | raise.rb:39:7:39:11 | raise | raise.rb:39:7:39:22 | MethodCall | semmle.label | successor | -| raise.rb:39:7:39:22 | MethodCall | raise.rb:36:1:45:3 | exit m4 (abnormal) | semmle.label | raise | +| raise.rb:39:7:39:22 | MethodCall | raise.rb:41:3:42:22 | Rescue | semmle.label | raise | | raise.rb:39:13:39:22 | ExceptionA | raise.rb:39:7:39:11 | raise | semmle.label | successor | +| raise.rb:41:3:42:22 | Rescue | raise.rb:41:13:41:13 | e | semmle.label | successor | +| raise.rb:41:13:41:13 | e | raise.rb:42:10:42:22 | String | semmle.label | successor | +| raise.rb:42:5:42:8 | puts | raise.rb:42:5:42:22 | MethodCall | semmle.label | successor | +| raise.rb:42:5:42:22 | MethodCall | raise.rb:44:8:44:15 | String | semmle.label | successor | +| raise.rb:42:10:42:22 | String | raise.rb:42:5:42:8 | puts | semmle.label | successor | | raise.rb:44:3:44:6 | puts | raise.rb:44:3:44:15 | MethodCall | semmle.label | successor | | raise.rb:44:3:44:15 | MethodCall | raise.rb:36:1:45:3 | exit m4 (normal) | semmle.label | successor | | raise.rb:44:8:44:15 | String | raise.rb:44:3:44:6 | puts | semmle.label | successor | | raise.rb:47:1:55:3 | enter m5 | raise.rb:47:8:47:8 | b | semmle.label | successor | -| raise.rb:47:1:55:3 | exit m5 (abnormal) | raise.rb:47:1:55:3 | exit m5 | semmle.label | successor | | raise.rb:47:1:55:3 | exit m5 (normal) | raise.rb:47:1:55:3 | exit m5 | semmle.label | successor | | raise.rb:47:8:47:8 | b | raise.rb:49:5:51:7 | If | semmle.label | successor | | raise.rb:49:5:51:7 | If | raise.rb:49:8:49:8 | b | semmle.label | successor | | raise.rb:49:8:49:8 | b | raise.rb:50:13:50:22 | ExceptionA | semmle.label | true | | raise.rb:49:8:49:8 | b | raise.rb:54:8:54:15 | String | semmle.label | false | | raise.rb:50:7:50:11 | raise | raise.rb:50:7:50:22 | MethodCall | semmle.label | successor | -| raise.rb:50:7:50:22 | MethodCall | raise.rb:47:1:55:3 | exit m5 (abnormal) | semmle.label | raise | +| raise.rb:50:7:50:22 | MethodCall | raise.rb:52:3:52:13 | Rescue | semmle.label | raise | | raise.rb:50:13:50:22 | ExceptionA | raise.rb:50:7:50:11 | raise | semmle.label | successor | +| raise.rb:52:3:52:13 | Rescue | raise.rb:52:13:52:13 | e | semmle.label | successor | +| raise.rb:52:13:52:13 | e | raise.rb:54:8:54:15 | String | semmle.label | successor | | raise.rb:54:3:54:6 | puts | raise.rb:54:3:54:15 | MethodCall | semmle.label | successor | | raise.rb:54:3:54:15 | MethodCall | raise.rb:47:1:55:3 | exit m5 (normal) | semmle.label | successor | | raise.rb:54:8:54:15 | String | raise.rb:54:3:54:6 | puts | semmle.label | successor | @@ -1781,8 +1996,17 @@ edges | raise.rb:59:8:59:8 | b | raise.rb:60:13:60:22 | ExceptionA | semmle.label | true | | raise.rb:59:8:59:8 | b | raise.rb:65:8:65:15 | String | semmle.label | false | | raise.rb:60:7:60:11 | raise | raise.rb:60:7:60:22 | MethodCall | semmle.label | successor | -| raise.rb:60:7:60:22 | MethodCall | raise.rb:57:1:66:3 | exit m6 (abnormal) | semmle.label | raise | +| raise.rb:60:7:60:22 | MethodCall | raise.rb:62:3:63:22 | Rescue | semmle.label | raise | | raise.rb:60:13:60:22 | ExceptionA | raise.rb:60:7:60:11 | raise | semmle.label | successor | +| raise.rb:62:3:63:22 | Rescue | raise.rb:62:10:62:19 | ExceptionA | semmle.label | successor | +| raise.rb:62:10:62:19 | ExceptionA | raise.rb:62:22:62:31 | ExceptionB | semmle.label | no-match | +| raise.rb:62:10:62:19 | ExceptionA | raise.rb:62:36:62:36 | e | semmle.label | match | +| raise.rb:62:22:62:31 | ExceptionB | raise.rb:57:1:66:3 | exit m6 (abnormal) | semmle.label | raise | +| raise.rb:62:22:62:31 | ExceptionB | raise.rb:62:36:62:36 | e | semmle.label | match | +| raise.rb:62:36:62:36 | e | raise.rb:63:10:63:22 | String | semmle.label | successor | +| raise.rb:63:5:63:8 | puts | raise.rb:63:5:63:22 | MethodCall | semmle.label | successor | +| raise.rb:63:5:63:22 | MethodCall | raise.rb:65:8:65:15 | String | semmle.label | successor | +| raise.rb:63:10:63:22 | String | raise.rb:63:5:63:8 | puts | semmle.label | successor | | raise.rb:65:3:65:6 | puts | raise.rb:65:3:65:15 | MethodCall | semmle.label | successor | | raise.rb:65:3:65:15 | MethodCall | raise.rb:57:1:66:3 | exit m6 (normal) | semmle.label | successor | | raise.rb:65:8:65:15 | String | raise.rb:65:3:65:6 | puts | semmle.label | successor | @@ -1796,21 +2020,30 @@ edges | raise.rb:69:6:69:10 | Binary | raise.rb:71:3:72:18 | Elsif | semmle.label | false | | raise.rb:69:10:69:10 | 2 | raise.rb:69:6:69:10 | Binary | semmle.label | successor | | raise.rb:70:5:70:9 | raise | raise.rb:70:5:70:17 | MethodCall | semmle.label | successor | -| raise.rb:70:5:70:17 | MethodCall | raise.rb:68:1:77:3 | exit m7 (abnormal) | semmle.label | raise | +| raise.rb:70:5:70:17 | MethodCall | raise.rb:75:1:76:15 | [ensure: raise] Ensure | semmle.label | raise | | raise.rb:70:11:70:17 | String | raise.rb:70:5:70:9 | raise | semmle.label | successor | | raise.rb:71:3:72:18 | Elsif | raise.rb:71:9:71:9 | x | semmle.label | successor | | raise.rb:71:9:71:9 | x | raise.rb:71:13:71:13 | 0 | semmle.label | successor | | raise.rb:71:9:71:13 | Binary | raise.rb:72:12:72:18 | String | semmle.label | true | | raise.rb:71:9:71:13 | Binary | raise.rb:74:8:74:20 | String | semmle.label | false | | raise.rb:71:13:71:13 | 0 | raise.rb:71:9:71:13 | Binary | semmle.label | successor | -| raise.rb:72:5:72:18 | Return | raise.rb:68:1:77:3 | exit m7 (normal) | semmle.label | return | +| raise.rb:72:5:72:18 | Return | raise.rb:75:1:76:15 | [ensure: return] Ensure | semmle.label | return | | raise.rb:72:12:72:18 | String | raise.rb:72:5:72:18 | Return | semmle.label | successor | | raise.rb:74:3:74:6 | puts | raise.rb:74:3:74:20 | MethodCall | semmle.label | successor | -| raise.rb:74:3:74:20 | MethodCall | raise.rb:76:8:76:15 | String | semmle.label | successor | +| raise.rb:74:3:74:20 | MethodCall | raise.rb:75:1:76:15 | Ensure | semmle.label | successor | | raise.rb:74:8:74:20 | String | raise.rb:74:3:74:6 | puts | semmle.label | successor | +| raise.rb:75:1:76:15 | Ensure | raise.rb:76:8:76:15 | String | semmle.label | successor | +| raise.rb:75:1:76:15 | [ensure: raise] Ensure | raise.rb:76:8:76:15 | [ensure: raise] String | semmle.label | successor | +| raise.rb:75:1:76:15 | [ensure: return] Ensure | raise.rb:76:8:76:15 | [ensure: return] String | semmle.label | successor | +| raise.rb:76:3:76:6 | [ensure: raise] puts | raise.rb:76:3:76:15 | [ensure: raise] MethodCall | semmle.label | successor | +| raise.rb:76:3:76:6 | [ensure: return] puts | raise.rb:76:3:76:15 | [ensure: return] MethodCall | semmle.label | successor | | raise.rb:76:3:76:6 | puts | raise.rb:76:3:76:15 | MethodCall | semmle.label | successor | | raise.rb:76:3:76:15 | MethodCall | raise.rb:68:1:77:3 | exit m7 (normal) | semmle.label | successor | +| raise.rb:76:3:76:15 | [ensure: raise] MethodCall | raise.rb:68:1:77:3 | exit m7 (abnormal) | semmle.label | raise | +| raise.rb:76:3:76:15 | [ensure: return] MethodCall | raise.rb:68:1:77:3 | exit m7 (normal) | semmle.label | return | | raise.rb:76:8:76:15 | String | raise.rb:76:3:76:6 | puts | semmle.label | successor | +| raise.rb:76:8:76:15 | [ensure: raise] String | raise.rb:76:3:76:6 | [ensure: raise] puts | semmle.label | successor | +| raise.rb:76:8:76:15 | [ensure: return] String | raise.rb:76:3:76:6 | [ensure: return] puts | semmle.label | successor | | raise.rb:79:1:92:3 | enter m8 | raise.rb:79:8:79:8 | x | semmle.label | successor | | raise.rb:79:1:92:3 | exit m8 (abnormal) | raise.rb:79:1:92:3 | exit m8 | semmle.label | successor | | raise.rb:79:1:92:3 | exit m8 (normal) | raise.rb:79:1:92:3 | exit m8 | semmle.label | successor | @@ -1824,18 +2057,30 @@ edges | raise.rb:82:8:82:12 | Binary | raise.rb:84:5:85:20 | Elsif | semmle.label | false | | raise.rb:82:12:82:12 | 2 | raise.rb:82:8:82:12 | Binary | semmle.label | successor | | raise.rb:83:7:83:11 | raise | raise.rb:83:7:83:19 | MethodCall | semmle.label | successor | -| raise.rb:83:7:83:19 | MethodCall | raise.rb:79:1:92:3 | exit m8 (abnormal) | semmle.label | raise | +| raise.rb:83:7:83:19 | MethodCall | raise.rb:88:3:89:17 | [ensure: raise] Ensure | semmle.label | raise | | raise.rb:83:13:83:19 | String | raise.rb:83:7:83:11 | raise | semmle.label | successor | | raise.rb:84:5:85:20 | Elsif | raise.rb:84:11:84:11 | x | semmle.label | successor | | raise.rb:84:11:84:11 | x | raise.rb:84:15:84:15 | 0 | semmle.label | successor | | raise.rb:84:11:84:15 | Binary | raise.rb:85:14:85:20 | String | semmle.label | true | | raise.rb:84:11:84:15 | Binary | raise.rb:87:10:87:22 | String | semmle.label | false | | raise.rb:84:15:84:15 | 0 | raise.rb:84:11:84:15 | Binary | semmle.label | successor | -| raise.rb:85:7:85:20 | Return | raise.rb:79:1:92:3 | exit m8 (normal) | semmle.label | return | +| raise.rb:85:7:85:20 | Return | raise.rb:88:3:89:17 | [ensure: return] Ensure | semmle.label | return | | raise.rb:85:14:85:20 | String | raise.rb:85:7:85:20 | Return | semmle.label | successor | | raise.rb:87:5:87:8 | puts | raise.rb:87:5:87:22 | MethodCall | semmle.label | successor | -| raise.rb:87:5:87:22 | MethodCall | raise.rb:91:8:91:15 | String | semmle.label | successor | +| raise.rb:87:5:87:22 | MethodCall | raise.rb:88:3:89:17 | Ensure | semmle.label | successor | | raise.rb:87:10:87:22 | String | raise.rb:87:5:87:8 | puts | semmle.label | successor | +| raise.rb:88:3:89:17 | Ensure | raise.rb:89:10:89:17 | String | semmle.label | successor | +| raise.rb:88:3:89:17 | [ensure: raise] Ensure | raise.rb:89:10:89:17 | [ensure: raise] String | semmle.label | successor | +| raise.rb:88:3:89:17 | [ensure: return] Ensure | raise.rb:89:10:89:17 | [ensure: return] String | semmle.label | successor | +| raise.rb:89:5:89:8 | [ensure: raise] puts | raise.rb:89:5:89:17 | [ensure: raise] MethodCall | semmle.label | successor | +| raise.rb:89:5:89:8 | [ensure: return] puts | raise.rb:89:5:89:17 | [ensure: return] MethodCall | semmle.label | successor | +| raise.rb:89:5:89:8 | puts | raise.rb:89:5:89:17 | MethodCall | semmle.label | successor | +| raise.rb:89:5:89:17 | MethodCall | raise.rb:91:8:91:15 | String | semmle.label | successor | +| raise.rb:89:5:89:17 | [ensure: raise] MethodCall | raise.rb:79:1:92:3 | exit m8 (abnormal) | semmle.label | raise | +| raise.rb:89:5:89:17 | [ensure: return] MethodCall | raise.rb:79:1:92:3 | exit m8 (normal) | semmle.label | return | +| raise.rb:89:10:89:17 | String | raise.rb:89:5:89:8 | puts | semmle.label | successor | +| raise.rb:89:10:89:17 | [ensure: raise] String | raise.rb:89:5:89:8 | [ensure: raise] puts | semmle.label | successor | +| raise.rb:89:10:89:17 | [ensure: return] String | raise.rb:89:5:89:8 | [ensure: return] puts | semmle.label | successor | | raise.rb:91:3:91:6 | puts | raise.rb:91:3:91:15 | MethodCall | semmle.label | successor | | raise.rb:91:3:91:15 | MethodCall | raise.rb:79:1:92:3 | exit m8 (normal) | semmle.label | successor | | raise.rb:91:8:91:15 | String | raise.rb:91:3:91:6 | puts | semmle.label | successor | @@ -1854,30 +2099,105 @@ edges | raise.rb:97:8:97:12 | Binary | raise.rb:99:5:100:20 | Elsif | semmle.label | false | | raise.rb:97:12:97:12 | 2 | raise.rb:97:8:97:12 | Binary | semmle.label | successor | | raise.rb:98:7:98:11 | raise | raise.rb:98:7:98:19 | MethodCall | semmle.label | successor | -| raise.rb:98:7:98:19 | MethodCall | raise.rb:94:1:119:3 | exit m9 (abnormal) | semmle.label | raise | +| raise.rb:98:7:98:19 | MethodCall | raise.rb:103:3:111:7 | [ensure: raise] Ensure | semmle.label | raise | | raise.rb:98:13:98:19 | String | raise.rb:98:7:98:11 | raise | semmle.label | successor | | raise.rb:99:5:100:20 | Elsif | raise.rb:99:11:99:11 | x | semmle.label | successor | | raise.rb:99:11:99:11 | x | raise.rb:99:15:99:15 | 0 | semmle.label | successor | | raise.rb:99:11:99:15 | Binary | raise.rb:100:14:100:20 | String | semmle.label | true | | raise.rb:99:11:99:15 | Binary | raise.rb:102:10:102:22 | String | semmle.label | false | | raise.rb:99:15:99:15 | 0 | raise.rb:99:11:99:15 | Binary | semmle.label | successor | -| raise.rb:100:7:100:20 | Return | raise.rb:94:1:119:3 | exit m9 (normal) | semmle.label | return | +| raise.rb:100:7:100:20 | Return | raise.rb:103:3:111:7 | [ensure: return] Ensure | semmle.label | return | | raise.rb:100:14:100:20 | String | raise.rb:100:7:100:20 | Return | semmle.label | successor | | raise.rb:102:5:102:8 | puts | raise.rb:102:5:102:22 | MethodCall | semmle.label | successor | -| raise.rb:102:5:102:22 | MethodCall | raise.rb:113:8:113:15 | String | semmle.label | successor | +| raise.rb:102:5:102:22 | MethodCall | raise.rb:103:3:111:7 | Ensure | semmle.label | successor | | raise.rb:102:10:102:22 | String | raise.rb:102:5:102:8 | puts | semmle.label | successor | +| raise.rb:103:3:111:7 | Ensure | raise.rb:104:10:104:23 | String | semmle.label | successor | +| raise.rb:103:3:111:7 | [ensure: raise] Ensure | raise.rb:104:10:104:23 | [ensure: raise] String | semmle.label | successor | +| raise.rb:103:3:111:7 | [ensure: return] Ensure | raise.rb:104:10:104:23 | [ensure: return] String | semmle.label | successor | +| raise.rb:104:5:104:8 | [ensure: raise] puts | raise.rb:104:5:104:23 | [ensure: raise] MethodCall | semmle.label | successor | +| raise.rb:104:5:104:8 | [ensure: return] puts | raise.rb:104:5:104:23 | [ensure: return] MethodCall | semmle.label | successor | +| raise.rb:104:5:104:8 | puts | raise.rb:104:5:104:23 | MethodCall | semmle.label | successor | +| raise.rb:104:5:104:23 | MethodCall | raise.rb:106:7:108:9 | If | semmle.label | successor | +| raise.rb:104:5:104:23 | [ensure: raise] MethodCall | raise.rb:106:7:108:9 | [ensure: raise] If | semmle.label | successor | +| raise.rb:104:5:104:23 | [ensure: return] MethodCall | raise.rb:106:7:108:9 | [ensure: return] If | semmle.label | successor | +| raise.rb:104:10:104:23 | String | raise.rb:104:5:104:8 | puts | semmle.label | successor | +| raise.rb:104:10:104:23 | [ensure: raise] String | raise.rb:104:5:104:8 | [ensure: raise] puts | semmle.label | successor | +| raise.rb:104:10:104:23 | [ensure: return] String | raise.rb:104:5:104:8 | [ensure: return] puts | semmle.label | successor | +| raise.rb:106:7:108:9 | If | raise.rb:106:10:106:11 | b1 | semmle.label | successor | +| raise.rb:106:7:108:9 | [ensure: raise] If | raise.rb:106:10:106:11 | [ensure: raise] b1 | semmle.label | successor | +| raise.rb:106:7:108:9 | [ensure: return] If | raise.rb:106:10:106:11 | [ensure: return] b1 | semmle.label | successor | +| raise.rb:106:10:106:11 | [ensure: raise] b1 | raise.rb:107:15:107:26 | [ensure: raise] String | semmle.label | true | +| raise.rb:106:10:106:11 | [ensure: raise] b1 | raise.rb:109:5:110:25 | [ensure: raise] Ensure | semmle.label | false | +| raise.rb:106:10:106:11 | [ensure: return] b1 | raise.rb:107:15:107:26 | [ensure: return] String | semmle.label | true | +| raise.rb:106:10:106:11 | [ensure: return] b1 | raise.rb:109:5:110:25 | [ensure: return] Ensure | semmle.label | false | +| raise.rb:106:10:106:11 | b1 | raise.rb:107:15:107:26 | String | semmle.label | true | +| raise.rb:106:10:106:11 | b1 | raise.rb:109:5:110:25 | Ensure | semmle.label | false | +| raise.rb:107:9:107:13 | [ensure: raise] raise | raise.rb:107:9:107:26 | [ensure: raise] MethodCall | semmle.label | successor | +| raise.rb:107:9:107:13 | [ensure: return] raise | raise.rb:107:9:107:26 | [ensure: return] MethodCall | semmle.label | successor | +| raise.rb:107:9:107:13 | raise | raise.rb:107:9:107:26 | MethodCall | semmle.label | successor | +| raise.rb:107:9:107:26 | MethodCall | raise.rb:109:5:110:25 | [ensure(1): raise] Ensure | semmle.label | raise | +| raise.rb:107:9:107:26 | [ensure: raise] MethodCall | raise.rb:109:5:110:25 | [ensure: raise, ensure(1): raise] Ensure | semmle.label | raise | +| raise.rb:107:9:107:26 | [ensure: return] MethodCall | raise.rb:109:5:110:25 | [ensure: return, ensure(1): raise] Ensure | semmle.label | raise | +| raise.rb:107:15:107:26 | String | raise.rb:107:9:107:13 | raise | semmle.label | successor | +| raise.rb:107:15:107:26 | [ensure: raise] String | raise.rb:107:9:107:13 | [ensure: raise] raise | semmle.label | successor | +| raise.rb:107:15:107:26 | [ensure: return] String | raise.rb:107:9:107:13 | [ensure: return] raise | semmle.label | successor | +| raise.rb:109:5:110:25 | Ensure | raise.rb:110:12:110:25 | String | semmle.label | successor | +| raise.rb:109:5:110:25 | [ensure(1): raise] Ensure | raise.rb:110:12:110:25 | [ensure(1): raise] String | semmle.label | successor | +| raise.rb:109:5:110:25 | [ensure: raise, ensure(1): raise] Ensure | raise.rb:110:12:110:25 | [ensure: raise, ensure(1): raise] String | semmle.label | successor | +| raise.rb:109:5:110:25 | [ensure: raise] Ensure | raise.rb:110:12:110:25 | [ensure: raise] String | semmle.label | successor | +| raise.rb:109:5:110:25 | [ensure: return, ensure(1): raise] Ensure | raise.rb:110:12:110:25 | [ensure: return, ensure(1): raise] String | semmle.label | successor | +| raise.rb:109:5:110:25 | [ensure: return] Ensure | raise.rb:110:12:110:25 | [ensure: return] String | semmle.label | successor | +| raise.rb:110:7:110:10 | [ensure(1): raise] puts | raise.rb:110:7:110:25 | [ensure(1): raise] MethodCall | semmle.label | successor | +| raise.rb:110:7:110:10 | [ensure: raise, ensure(1): raise] puts | raise.rb:110:7:110:25 | [ensure: raise, ensure(1): raise] MethodCall | semmle.label | successor | +| raise.rb:110:7:110:10 | [ensure: raise] puts | raise.rb:110:7:110:25 | [ensure: raise] MethodCall | semmle.label | successor | +| raise.rb:110:7:110:10 | [ensure: return, ensure(1): raise] puts | raise.rb:110:7:110:25 | [ensure: return, ensure(1): raise] MethodCall | semmle.label | successor | +| raise.rb:110:7:110:10 | [ensure: return] puts | raise.rb:110:7:110:25 | [ensure: return] MethodCall | semmle.label | successor | +| raise.rb:110:7:110:10 | puts | raise.rb:110:7:110:25 | MethodCall | semmle.label | successor | +| raise.rb:110:7:110:25 | MethodCall | raise.rb:113:8:113:15 | String | semmle.label | successor | +| raise.rb:110:7:110:25 | [ensure(1): raise] MethodCall | raise.rb:114:1:118:5 | [ensure: raise] Ensure | semmle.label | raise | +| raise.rb:110:7:110:25 | [ensure: raise, ensure(1): raise] MethodCall | raise.rb:114:1:118:5 | [ensure: raise] Ensure | semmle.label | raise | +| raise.rb:110:7:110:25 | [ensure: raise] MethodCall | raise.rb:114:1:118:5 | [ensure: raise] Ensure | semmle.label | raise | +| raise.rb:110:7:110:25 | [ensure: return, ensure(1): raise] MethodCall | raise.rb:114:1:118:5 | [ensure: raise] Ensure | semmle.label | raise | +| raise.rb:110:7:110:25 | [ensure: return] MethodCall | raise.rb:114:1:118:5 | [ensure: return] Ensure | semmle.label | return | +| raise.rb:110:12:110:25 | String | raise.rb:110:7:110:10 | puts | semmle.label | successor | +| raise.rb:110:12:110:25 | [ensure(1): raise] String | raise.rb:110:7:110:10 | [ensure(1): raise] puts | semmle.label | successor | +| raise.rb:110:12:110:25 | [ensure: raise, ensure(1): raise] String | raise.rb:110:7:110:10 | [ensure: raise, ensure(1): raise] puts | semmle.label | successor | +| raise.rb:110:12:110:25 | [ensure: raise] String | raise.rb:110:7:110:10 | [ensure: raise] puts | semmle.label | successor | +| raise.rb:110:12:110:25 | [ensure: return, ensure(1): raise] String | raise.rb:110:7:110:10 | [ensure: return, ensure(1): raise] puts | semmle.label | successor | +| raise.rb:110:12:110:25 | [ensure: return] String | raise.rb:110:7:110:10 | [ensure: return] puts | semmle.label | successor | | raise.rb:113:3:113:6 | puts | raise.rb:113:3:113:15 | MethodCall | semmle.label | successor | -| raise.rb:113:3:113:15 | MethodCall | raise.rb:115:8:115:22 | String | semmle.label | successor | +| raise.rb:113:3:113:15 | MethodCall | raise.rb:114:1:118:5 | Ensure | semmle.label | successor | | raise.rb:113:8:113:15 | String | raise.rb:113:3:113:6 | puts | semmle.label | successor | +| raise.rb:114:1:118:5 | Ensure | raise.rb:115:8:115:22 | String | semmle.label | successor | +| raise.rb:114:1:118:5 | [ensure: raise] Ensure | raise.rb:115:8:115:22 | [ensure: raise] String | semmle.label | successor | +| raise.rb:114:1:118:5 | [ensure: return] Ensure | raise.rb:115:8:115:22 | [ensure: return] String | semmle.label | successor | +| raise.rb:115:3:115:6 | [ensure: raise] puts | raise.rb:115:3:115:22 | [ensure: raise] MethodCall | semmle.label | successor | +| raise.rb:115:3:115:6 | [ensure: return] puts | raise.rb:115:3:115:22 | [ensure: return] MethodCall | semmle.label | successor | | raise.rb:115:3:115:6 | puts | raise.rb:115:3:115:22 | MethodCall | semmle.label | successor | | raise.rb:115:3:115:22 | MethodCall | raise.rb:116:3:118:5 | If | semmle.label | successor | +| raise.rb:115:3:115:22 | [ensure: raise] MethodCall | raise.rb:116:3:118:5 | [ensure: raise] If | semmle.label | successor | +| raise.rb:115:3:115:22 | [ensure: return] MethodCall | raise.rb:116:3:118:5 | [ensure: return] If | semmle.label | successor | | raise.rb:115:8:115:22 | String | raise.rb:115:3:115:6 | puts | semmle.label | successor | +| raise.rb:115:8:115:22 | [ensure: raise] String | raise.rb:115:3:115:6 | [ensure: raise] puts | semmle.label | successor | +| raise.rb:115:8:115:22 | [ensure: return] String | raise.rb:115:3:115:6 | [ensure: return] puts | semmle.label | successor | | raise.rb:116:3:118:5 | If | raise.rb:116:6:116:7 | b2 | semmle.label | successor | +| raise.rb:116:3:118:5 | [ensure: raise] If | raise.rb:116:6:116:7 | [ensure: raise] b2 | semmle.label | successor | +| raise.rb:116:3:118:5 | [ensure: return] If | raise.rb:116:6:116:7 | [ensure: return] b2 | semmle.label | successor | +| raise.rb:116:6:116:7 | [ensure: raise] b2 | raise.rb:94:1:119:3 | exit m9 (abnormal) | semmle.label | raise | +| raise.rb:116:6:116:7 | [ensure: raise] b2 | raise.rb:117:11:117:22 | [ensure: raise] String | semmle.label | true | +| raise.rb:116:6:116:7 | [ensure: return] b2 | raise.rb:94:1:119:3 | exit m9 (normal) | semmle.label | return | +| raise.rb:116:6:116:7 | [ensure: return] b2 | raise.rb:117:11:117:22 | [ensure: return] String | semmle.label | true | | raise.rb:116:6:116:7 | b2 | raise.rb:94:1:119:3 | exit m9 (normal) | semmle.label | false | | raise.rb:116:6:116:7 | b2 | raise.rb:117:11:117:22 | String | semmle.label | true | +| raise.rb:117:5:117:9 | [ensure: raise] raise | raise.rb:117:5:117:22 | [ensure: raise] MethodCall | semmle.label | successor | +| raise.rb:117:5:117:9 | [ensure: return] raise | raise.rb:117:5:117:22 | [ensure: return] MethodCall | semmle.label | successor | | raise.rb:117:5:117:9 | raise | raise.rb:117:5:117:22 | MethodCall | semmle.label | successor | | raise.rb:117:5:117:22 | MethodCall | raise.rb:94:1:119:3 | exit m9 (abnormal) | semmle.label | raise | +| raise.rb:117:5:117:22 | [ensure: raise] MethodCall | raise.rb:94:1:119:3 | exit m9 (abnormal) | semmle.label | raise | +| raise.rb:117:5:117:22 | [ensure: return] MethodCall | raise.rb:94:1:119:3 | exit m9 (abnormal) | semmle.label | raise | | raise.rb:117:11:117:22 | String | raise.rb:117:5:117:9 | raise | semmle.label | successor | +| raise.rb:117:11:117:22 | [ensure: raise] String | raise.rb:117:5:117:9 | [ensure: raise] raise | semmle.label | successor | +| raise.rb:117:11:117:22 | [ensure: return] String | raise.rb:117:5:117:9 | [ensure: return] raise | semmle.label | successor | | raise.rb:121:1:124:3 | enter m10 | raise.rb:121:20:121:30 | String | semmle.label | successor | | raise.rb:121:1:124:3 | exit m10 (abnormal) | raise.rb:121:1:124:3 | exit m10 | semmle.label | successor | | raise.rb:121:14:121:18 | raise | raise.rb:121:14:121:30 | MethodCall | semmle.label | successor | From 31b8d33a7cf6b8edcb7bb3b2433989f55d2bd280 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 7 Dec 2020 17:18:18 +0100 Subject: [PATCH 3/8] CFG: Mark `redo` edges out of `for` loops --- .../codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll | 4 ++-- ql/test/library-tests/controlflow/graph/Cfg.expected | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll b/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll index d0df3768d98..d936a68b361 100644 --- a/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll +++ b/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll @@ -1327,9 +1327,9 @@ module Trees { first(this.getConditionNode(), succ) and c.continuesLoop() or - last(this.getBodyNode(), pred, any(RedoCompletion rc)) and + last(this.getBodyNode(), pred, c) and first(this.getBodyNode(), succ) and - c instanceof SimpleCompletion + c instanceof RedoCompletion } } } diff --git a/ql/test/library-tests/controlflow/graph/Cfg.expected b/ql/test/library-tests/controlflow/graph/Cfg.expected index 2d1ad8f449c..e4b7b36ccdb 100644 --- a/ql/test/library-tests/controlflow/graph/Cfg.expected +++ b/ql/test/library-tests/controlflow/graph/Cfg.expected @@ -1685,7 +1685,7 @@ edges | cfg.rb:179:6:179:11 | Binary | cfg.rb:179:18:179:21 | Redo | semmle.label | true | | cfg.rb:179:6:179:11 | Binary | cfg.rb:180:8:180:8 | x | semmle.label | false | | cfg.rb:179:11:179:11 | 5 | cfg.rb:179:6:179:11 | Binary | semmle.label | successor | -| cfg.rb:179:18:179:21 | Redo | cfg.rb:178:3:178:3 | x | semmle.label | successor | +| cfg.rb:179:18:179:21 | Redo | cfg.rb:178:3:178:3 | x | semmle.label | redo | | cfg.rb:180:3:180:6 | puts | cfg.rb:180:3:180:8 | MethodCall | semmle.label | successor | | cfg.rb:180:3:180:8 | MethodCall | cfg.rb:177:7:177:7 | x | semmle.label | successor | | cfg.rb:180:8:180:8 | x | cfg.rb:180:3:180:6 | puts | semmle.label | successor | @@ -1875,7 +1875,7 @@ edges | loops.rb:16:11:16:16 | Binary | loops.rb:17:7:17:10 | Redo | semmle.label | true | | loops.rb:16:11:16:16 | Binary | loops.rb:19:10:19:15 | String | semmle.label | false | | loops.rb:16:15:16:16 | 10 | loops.rb:16:11:16:16 | Binary | semmle.label | successor | -| loops.rb:17:7:17:10 | Redo | loops.rb:10:10:10:10 | x | semmle.label | successor | +| loops.rb:17:7:17:10 | Redo | loops.rb:10:10:10:10 | x | semmle.label | redo | | loops.rb:19:5:19:8 | puts | loops.rb:19:5:19:15 | MethodCall | semmle.label | successor | | loops.rb:19:5:19:15 | MethodCall | loops.rb:9:9:9:9 | x | semmle.label | successor | | loops.rb:19:10:19:15 | String | loops.rb:19:5:19:8 | puts | semmle.label | successor | From 80a59a81edf07e4f651c924ca7290b0bf0030a16 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 7 Dec 2020 17:19:34 +0100 Subject: [PATCH 4/8] CFG: Use `MatchingCompletion` for patterns --- .../controlflow/internal/Completion.qll | 16 +++++- .../internal/ControlFlowGraphImpl.qll | 18 ++++--- .../controlflow/graph/Cfg.expected | 54 +++++++------------ 3 files changed, 43 insertions(+), 45 deletions(-) diff --git a/ql/src/codeql_ruby/controlflow/internal/Completion.qll b/ql/src/codeql_ruby/controlflow/internal/Completion.qll index 4245bc7990a..0bb18ac5bbb 100644 --- a/ql/src/codeql_ruby/controlflow/internal/Completion.qll +++ b/ql/src/codeql_ruby/controlflow/internal/Completion.qll @@ -170,7 +170,11 @@ private predicate inBooleanContext(AstNode n) { or n = any(ParenthesizedStatement parent | inBooleanContext(parent)).getChild() or - n instanceof Pattern + exists(Case c, When w | + not exists(c.getValue()) and + c.getChild(_) = w and + w.getPattern(_).getChild() = n + ) } /** @@ -185,7 +189,15 @@ private predicate mustHaveMatchingCompletion(AstNode n) { * Holds if `n` is used in a matching context. That is, whether or * not the value of `n` matches, determines the successor. */ -private predicate inMatchingContext(AstNode n) { n = any(Rescue r).getExceptions().getChild(_) } +private predicate inMatchingContext(AstNode n) { + n = any(Rescue r).getExceptions().getChild(_) + or + exists(Case c, When w | + exists(c.getValue()) and + c.getChild(_) = w and + w.getPattern(_).getChild() = n + ) +} /** * A completion that represents normal evaluation of a statement or an diff --git a/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll b/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll index d936a68b361..3a4cf09ca69 100644 --- a/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll +++ b/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll @@ -362,7 +362,7 @@ module Trees { exists(int i, WhenTree branch | branch = this.getChild(i) | last(branch.getLastPattern(), pred, c) and first(this.getChild(i + 1), succ) and - c instanceof FalseCompletion + c.(ConditionalCompletion).getValue() = false ) } } @@ -836,8 +836,10 @@ module Trees { final override AstNode getChildNode(int i) { result = this.getChild(i) } } - private class PatternTree extends StandardPostOrderTree, Pattern { + private class PatternTree extends StandardPreOrderTree, Pattern { final override AstNode getChildNode(int i) { result = this.getChild() and i = 0 } + + final override predicate isHidden() { any() } } private class ProgramTree extends StandardPreOrderTree, Program { @@ -1277,7 +1279,7 @@ module Trees { final override predicate last(AstNode last, Completion c) { last(this.getLastPattern(), last, c) and - c instanceof FalseCompletion + c.(ConditionalCompletion).getValue() = false or last(this.getBody(), last, c) } @@ -1287,13 +1289,15 @@ module Trees { first(this.getPattern(0), succ) and c instanceof SimpleCompletion or - exists(int i, Pattern p | + exists(int i, Pattern p, boolean b | p = this.getPattern(i) and - last(p, pred, c) + last(p, pred, c) and + b = c.(ConditionalCompletion).getValue() | - c instanceof TrueCompletion and first(this.getBody(), succ) + b = true and + first(this.getBody(), succ) or - c instanceof FalseCompletion and + b = false and first(this.getPattern(i + 1), succ) ) } diff --git a/ql/test/library-tests/controlflow/graph/Cfg.expected b/ql/test/library-tests/controlflow/graph/Cfg.expected index e4b7b36ccdb..063725dd7db 100644 --- a/ql/test/library-tests/controlflow/graph/Cfg.expected +++ b/ql/test/library-tests/controlflow/graph/Cfg.expected @@ -84,7 +84,6 @@ nodes | case.rb:2:8:2:9 | x1 | | case.rb:3:5:3:42 | When | | case.rb:3:10:3:10 | 1 | -| case.rb:3:10:3:10 | Pattern | | case.rb:3:17:3:42 | ParenthesizedStatements | | case.rb:3:18:3:41 | If | | case.rb:3:21:3:22 | x2 | @@ -93,7 +92,6 @@ nodes | case.rb:3:34:3:37 | String | | case.rb:4:5:4:24 | When | | case.rb:4:10:4:10 | 2 | -| case.rb:4:10:4:10 | Pattern | | case.rb:4:17:4:20 | puts | | case.rb:4:17:4:24 | MethodCall | | case.rb:4:22:4:24 | String | @@ -177,17 +175,13 @@ nodes | cfg.rb:41:6:41:7 | 10 | | cfg.rb:42:3:42:24 | When | | cfg.rb:42:8:42:8 | 1 | -| cfg.rb:42:8:42:8 | Pattern | | cfg.rb:42:15:42:18 | puts | | cfg.rb:42:15:42:24 | MethodCall | | cfg.rb:42:20:42:24 | String | | cfg.rb:43:3:43:31 | When | | cfg.rb:43:8:43:8 | 2 | -| cfg.rb:43:8:43:8 | Pattern | | cfg.rb:43:11:43:11 | 3 | -| cfg.rb:43:11:43:11 | Pattern | | cfg.rb:43:14:43:14 | 4 | -| cfg.rb:43:14:43:14 | Pattern | | cfg.rb:43:21:43:24 | puts | | cfg.rb:43:21:43:31 | MethodCall | | cfg.rb:43:26:43:31 | String | @@ -198,7 +192,6 @@ nodes | cfg.rb:48:3:48:29 | When | | cfg.rb:48:8:48:8 | b | | cfg.rb:48:8:48:13 | Binary | -| cfg.rb:48:8:48:13 | Pattern | | cfg.rb:48:13:48:13 | 1 | | cfg.rb:48:20:48:23 | puts | | cfg.rb:48:20:48:29 | MethodCall | @@ -206,11 +199,9 @@ nodes | cfg.rb:49:3:49:37 | When | | cfg.rb:49:8:49:8 | b | | cfg.rb:49:8:49:13 | Binary | -| cfg.rb:49:8:49:13 | Pattern | | cfg.rb:49:13:49:13 | 0 | | cfg.rb:49:16:49:16 | b | | cfg.rb:49:16:49:20 | Binary | -| cfg.rb:49:16:49:20 | Pattern | | cfg.rb:49:20:49:20 | 1 | | cfg.rb:49:27:49:30 | puts | | cfg.rb:49:27:49:37 | MethodCall | @@ -1177,9 +1168,8 @@ edges | case.rb:2:3:5:5 | Case | case.rb:2:8:2:9 | x1 | semmle.label | successor | | case.rb:2:8:2:9 | x1 | case.rb:3:5:3:42 | When | semmle.label | successor | | case.rb:3:5:3:42 | When | case.rb:3:10:3:10 | 1 | semmle.label | successor | -| case.rb:3:10:3:10 | 1 | case.rb:3:10:3:10 | Pattern | semmle.label | successor | -| case.rb:3:10:3:10 | Pattern | case.rb:3:18:3:41 | If | semmle.label | true | -| case.rb:3:10:3:10 | Pattern | case.rb:4:5:4:24 | When | semmle.label | false | +| case.rb:3:10:3:10 | 1 | case.rb:3:18:3:41 | If | semmle.label | match | +| case.rb:3:10:3:10 | 1 | case.rb:4:5:4:24 | When | semmle.label | no-match | | case.rb:3:17:3:42 | ParenthesizedStatements | case.rb:1:1:6:3 | exit if_in_case (normal) | semmle.label | successor | | case.rb:3:18:3:41 | If | case.rb:3:21:3:22 | x2 | semmle.label | successor | | case.rb:3:21:3:22 | x2 | case.rb:3:17:3:42 | ParenthesizedStatements | semmle.label | false | @@ -1188,9 +1178,8 @@ edges | case.rb:3:29:3:37 | MethodCall | case.rb:3:17:3:42 | ParenthesizedStatements | semmle.label | successor | | case.rb:3:34:3:37 | String | case.rb:3:29:3:32 | puts | semmle.label | successor | | case.rb:4:5:4:24 | When | case.rb:4:10:4:10 | 2 | semmle.label | successor | -| case.rb:4:10:4:10 | 2 | case.rb:4:10:4:10 | Pattern | semmle.label | successor | -| case.rb:4:10:4:10 | Pattern | case.rb:1:1:6:3 | exit if_in_case (normal) | semmle.label | false | -| case.rb:4:10:4:10 | Pattern | case.rb:4:22:4:24 | String | semmle.label | true | +| case.rb:4:10:4:10 | 2 | case.rb:1:1:6:3 | exit if_in_case (normal) | semmle.label | no-match | +| case.rb:4:10:4:10 | 2 | case.rb:4:22:4:24 | String | semmle.label | match | | case.rb:4:17:4:20 | puts | case.rb:4:17:4:24 | MethodCall | semmle.label | successor | | case.rb:4:17:4:24 | MethodCall | case.rb:1:1:6:3 | exit if_in_case (normal) | semmle.label | successor | | case.rb:4:22:4:24 | String | case.rb:4:17:4:20 | puts | semmle.label | successor | @@ -1268,22 +1257,18 @@ edges | cfg.rb:41:1:45:3 | Case | cfg.rb:41:6:41:7 | 10 | semmle.label | successor | | cfg.rb:41:6:41:7 | 10 | cfg.rb:42:3:42:24 | When | semmle.label | successor | | cfg.rb:42:3:42:24 | When | cfg.rb:42:8:42:8 | 1 | semmle.label | successor | -| cfg.rb:42:8:42:8 | 1 | cfg.rb:42:8:42:8 | Pattern | semmle.label | successor | -| cfg.rb:42:8:42:8 | Pattern | cfg.rb:42:20:42:24 | String | semmle.label | true | -| cfg.rb:42:8:42:8 | Pattern | cfg.rb:43:3:43:31 | When | semmle.label | false | +| cfg.rb:42:8:42:8 | 1 | cfg.rb:42:20:42:24 | String | semmle.label | match | +| cfg.rb:42:8:42:8 | 1 | cfg.rb:43:3:43:31 | When | semmle.label | no-match | | cfg.rb:42:15:42:18 | puts | cfg.rb:42:15:42:24 | MethodCall | semmle.label | successor | | cfg.rb:42:15:42:24 | MethodCall | cfg.rb:47:1:50:3 | Case | semmle.label | successor | | cfg.rb:42:20:42:24 | String | cfg.rb:42:15:42:18 | puts | semmle.label | successor | | cfg.rb:43:3:43:31 | When | cfg.rb:43:8:43:8 | 2 | semmle.label | successor | -| cfg.rb:43:8:43:8 | 2 | cfg.rb:43:8:43:8 | Pattern | semmle.label | successor | -| cfg.rb:43:8:43:8 | Pattern | cfg.rb:43:11:43:11 | 3 | semmle.label | false | -| cfg.rb:43:8:43:8 | Pattern | cfg.rb:43:26:43:31 | String | semmle.label | true | -| cfg.rb:43:11:43:11 | 3 | cfg.rb:43:11:43:11 | Pattern | semmle.label | successor | -| cfg.rb:43:11:43:11 | Pattern | cfg.rb:43:14:43:14 | 4 | semmle.label | false | -| cfg.rb:43:11:43:11 | Pattern | cfg.rb:43:26:43:31 | String | semmle.label | true | -| cfg.rb:43:14:43:14 | 4 | cfg.rb:43:14:43:14 | Pattern | semmle.label | successor | -| cfg.rb:43:14:43:14 | Pattern | cfg.rb:43:26:43:31 | String | semmle.label | true | -| cfg.rb:43:14:43:14 | Pattern | cfg.rb:44:13:44:18 | String | semmle.label | false | +| cfg.rb:43:8:43:8 | 2 | cfg.rb:43:11:43:11 | 3 | semmle.label | no-match | +| cfg.rb:43:8:43:8 | 2 | cfg.rb:43:26:43:31 | String | semmle.label | match | +| cfg.rb:43:11:43:11 | 3 | cfg.rb:43:14:43:14 | 4 | semmle.label | no-match | +| cfg.rb:43:11:43:11 | 3 | cfg.rb:43:26:43:31 | String | semmle.label | match | +| cfg.rb:43:14:43:14 | 4 | cfg.rb:43:26:43:31 | String | semmle.label | match | +| cfg.rb:43:14:43:14 | 4 | cfg.rb:44:13:44:18 | String | semmle.label | no-match | | cfg.rb:43:21:43:24 | puts | cfg.rb:43:21:43:31 | MethodCall | semmle.label | successor | | cfg.rb:43:21:43:31 | MethodCall | cfg.rb:47:1:50:3 | Case | semmle.label | successor | | cfg.rb:43:26:43:31 | String | cfg.rb:43:21:43:24 | puts | semmle.label | successor | @@ -1293,23 +1278,20 @@ edges | cfg.rb:47:1:50:3 | Case | cfg.rb:48:3:48:29 | When | semmle.label | successor | | cfg.rb:48:3:48:29 | When | cfg.rb:48:8:48:8 | b | semmle.label | successor | | cfg.rb:48:8:48:8 | b | cfg.rb:48:13:48:13 | 1 | semmle.label | successor | -| cfg.rb:48:8:48:13 | Binary | cfg.rb:48:8:48:13 | Pattern | semmle.label | successor | -| cfg.rb:48:8:48:13 | Pattern | cfg.rb:48:25:48:29 | String | semmle.label | true | -| cfg.rb:48:8:48:13 | Pattern | cfg.rb:49:3:49:37 | When | semmle.label | false | +| cfg.rb:48:8:48:13 | Binary | cfg.rb:48:25:48:29 | String | semmle.label | true | +| cfg.rb:48:8:48:13 | Binary | cfg.rb:49:3:49:37 | When | semmle.label | false | | cfg.rb:48:13:48:13 | 1 | cfg.rb:48:8:48:13 | Binary | semmle.label | successor | | cfg.rb:48:20:48:23 | puts | cfg.rb:48:20:48:29 | MethodCall | semmle.label | successor | | cfg.rb:48:20:48:29 | MethodCall | cfg.rb:52:11:52:13 | String | semmle.label | successor | | cfg.rb:48:25:48:29 | String | cfg.rb:48:20:48:23 | puts | semmle.label | successor | | cfg.rb:49:3:49:37 | When | cfg.rb:49:8:49:8 | b | semmle.label | successor | | cfg.rb:49:8:49:8 | b | cfg.rb:49:13:49:13 | 0 | semmle.label | successor | -| cfg.rb:49:8:49:13 | Binary | cfg.rb:49:8:49:13 | Pattern | semmle.label | successor | -| cfg.rb:49:8:49:13 | Pattern | cfg.rb:49:16:49:16 | b | semmle.label | false | -| cfg.rb:49:8:49:13 | Pattern | cfg.rb:49:32:49:37 | String | semmle.label | true | +| cfg.rb:49:8:49:13 | Binary | cfg.rb:49:16:49:16 | b | semmle.label | false | +| cfg.rb:49:8:49:13 | Binary | cfg.rb:49:32:49:37 | String | semmle.label | true | | cfg.rb:49:13:49:13 | 0 | cfg.rb:49:8:49:13 | Binary | semmle.label | successor | | cfg.rb:49:16:49:16 | b | cfg.rb:49:20:49:20 | 1 | semmle.label | successor | -| cfg.rb:49:16:49:20 | Binary | cfg.rb:49:16:49:20 | Pattern | semmle.label | successor | -| cfg.rb:49:16:49:20 | Pattern | cfg.rb:49:32:49:37 | String | semmle.label | true | -| cfg.rb:49:16:49:20 | Pattern | cfg.rb:52:11:52:13 | String | semmle.label | false | +| cfg.rb:49:16:49:20 | Binary | cfg.rb:49:32:49:37 | String | semmle.label | true | +| cfg.rb:49:16:49:20 | Binary | cfg.rb:52:11:52:13 | String | semmle.label | false | | cfg.rb:49:20:49:20 | 1 | cfg.rb:49:16:49:20 | Binary | semmle.label | successor | | cfg.rb:49:27:49:30 | puts | cfg.rb:49:27:49:37 | MethodCall | semmle.label | successor | | cfg.rb:49:27:49:37 | MethodCall | cfg.rb:52:11:52:13 | String | semmle.label | successor | From b14a889f5f849d28c8494cb15e172363d10060a5 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Tue, 8 Dec 2020 11:05:04 +0100 Subject: [PATCH 5/8] CFG: Use `MatchingCompletion` for parameters with default values --- .../controlflow/internal/Completion.qll | 2 ++ .../internal/ControlFlowGraphImpl.qll | 35 ++++++++++++++++--- .../controlflow/graph/Cfg.expected | 22 +++++++++--- 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/ql/src/codeql_ruby/controlflow/internal/Completion.qll b/ql/src/codeql_ruby/controlflow/internal/Completion.qll index 0bb18ac5bbb..3b86799b8c9 100644 --- a/ql/src/codeql_ruby/controlflow/internal/Completion.qll +++ b/ql/src/codeql_ruby/controlflow/internal/Completion.qll @@ -197,6 +197,8 @@ private predicate inMatchingContext(AstNode n) { c.getChild(_) = w and w.getPattern(_).getChild() = n ) + or + n.(Trees::DefaultValueParameterTree).hasDefaultValue() } /** diff --git a/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll b/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll index 3a4cf09ca69..37658c3d64b 100644 --- a/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll +++ b/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll @@ -392,6 +392,33 @@ module Trees { private class ConstantTree extends LeafTree, Constant { } + /** A parameter that may have a default value. */ + abstract class DefaultValueParameterTree extends PreOrderTree { + abstract AstNode getDefaultValue(); + + predicate hasDefaultValue() { exists(this.getDefaultValue()) } + + final override predicate propagatesAbnormal(AstNode child) { child = this.getDefaultValue() } + + final override predicate last(AstNode last, Completion c) { + last = this and + exists(this.getDefaultValue()) and + c.(MatchingCompletion).getValue() = true + or + last(this.getDefaultValue(), last, c) + or + last = this and + not exists(this.getDefaultValue()) and + c instanceof SimpleCompletion + } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + pred = this and + first(this.getDefaultValue(), succ) and + c.(MatchingCompletion).getValue() = false + } + } + private class DestructuredLeftAssignmentTree extends StandardPostOrderTree, DestructuredLeftAssignment { final override AstNode getChildNode(int i) { result = this.getChild(i) } @@ -688,8 +715,8 @@ module Trees { final override AstNode getChildNode(int i) { result = this.getChild() and i = 0 } } - private class KeywordParameterTree extends StandardPostOrderTree, KeywordParameter { - final override AstNode getChildNode(int i) { result = this.getValue() and i = 0 } + private class KeywordParameterTree extends DefaultValueParameterTree, KeywordParameter { + final override AstNode getDefaultValue() { result = this.getValue() } } private class LambdaTree extends StandardPreOrderTree, Lambda { @@ -820,8 +847,8 @@ module Trees { } } - private class OptionalParameterTree extends StandardPostOrderTree, OptionalParameter { - final override AstNode getChildNode(int i) { result = this.getValue() and i = 0 } + private class OptionalParameterTree extends DefaultValueParameterTree, OptionalParameter { + final override AstNode getDefaultValue() { result = this.getValue() } } private class PairTree extends StandardPostOrderTree, Pair { diff --git a/ql/test/library-tests/controlflow/graph/Cfg.expected b/ql/test/library-tests/controlflow/graph/Cfg.expected index 063725dd7db..13d02efa769 100644 --- a/ql/test/library-tests/controlflow/graph/Cfg.expected +++ b/ql/test/library-tests/controlflow/graph/Cfg.expected @@ -1072,9 +1072,15 @@ nodes | raise.rb:121:1:124:3 | enter m10 | | raise.rb:121:1:124:3 | exit m10 | | raise.rb:121:1:124:3 | exit m10 (abnormal) | +| raise.rb:121:1:124:3 | exit m10 (normal) | +| raise.rb:121:9:121:31 | OptionalParameter | | raise.rb:121:14:121:18 | raise | | raise.rb:121:14:121:30 | MethodCall | | raise.rb:121:20:121:30 | String | +| raise.rb:122:1:123:51 | Ensure | +| raise.rb:123:3:123:6 | puts | +| raise.rb:123:3:123:51 | MethodCall | +| raise.rb:123:8:123:51 | String | edges | break_ensure.rb:1:1:11:3 | enter m1 | break_ensure.rb:1:8:1:15 | elements | semmle.label | successor | | break_ensure.rb:1:1:11:3 | exit m1 (normal) | break_ensure.rb:1:1:11:3 | exit m1 | semmle.label | successor | @@ -1430,10 +1436,11 @@ edges | cfg.rb:98:25:98:27 | String | cfg.rb:98:18:98:27 | Pair | semmle.label | successor | | cfg.rb:98:30:98:35 | HashSplatArgument | cfg.rb:98:8:98:36 | Hash | semmle.label | successor | | cfg.rb:98:32:98:35 | map1 | cfg.rb:98:30:98:35 | HashSplatArgument | semmle.label | successor | -| cfg.rb:101:1:104:3 | enter parameters | cfg.rb:101:24:101:25 | 42 | semmle.label | successor | +| cfg.rb:101:1:104:3 | enter parameters | cfg.rb:101:16:101:25 | OptionalParameter | semmle.label | successor | | cfg.rb:101:1:104:3 | exit parameters (normal) | cfg.rb:101:1:104:3 | exit parameters | semmle.label | successor | -| cfg.rb:101:16:101:25 | OptionalParameter | cfg.rb:101:28:101:31 | KeywordParameter | semmle.label | successor | -| cfg.rb:101:24:101:25 | 42 | cfg.rb:101:16:101:25 | OptionalParameter | semmle.label | successor | +| cfg.rb:101:16:101:25 | OptionalParameter | cfg.rb:101:24:101:25 | 42 | semmle.label | no-match | +| cfg.rb:101:16:101:25 | OptionalParameter | cfg.rb:101:28:101:31 | KeywordParameter | semmle.label | match | +| cfg.rb:101:24:101:25 | 42 | cfg.rb:101:28:101:31 | KeywordParameter | semmle.label | successor | | cfg.rb:101:28:101:31 | KeywordParameter | cfg.rb:101:34:101:41 | HashSplatParameter | semmle.label | successor | | cfg.rb:101:34:101:41 | HashSplatParameter | cfg.rb:102:8:102:12 | value | semmle.label | successor | | cfg.rb:102:3:102:6 | puts | cfg.rb:102:3:102:12 | MethodCall | semmle.label | successor | @@ -2180,8 +2187,15 @@ edges | raise.rb:117:11:117:22 | String | raise.rb:117:5:117:9 | raise | semmle.label | successor | | raise.rb:117:11:117:22 | [ensure: raise] String | raise.rb:117:5:117:9 | [ensure: raise] raise | semmle.label | successor | | raise.rb:117:11:117:22 | [ensure: return] String | raise.rb:117:5:117:9 | [ensure: return] raise | semmle.label | successor | -| raise.rb:121:1:124:3 | enter m10 | raise.rb:121:20:121:30 | String | semmle.label | successor | +| raise.rb:121:1:124:3 | enter m10 | raise.rb:121:9:121:31 | OptionalParameter | semmle.label | successor | | raise.rb:121:1:124:3 | exit m10 (abnormal) | raise.rb:121:1:124:3 | exit m10 | semmle.label | successor | +| raise.rb:121:1:124:3 | exit m10 (normal) | raise.rb:121:1:124:3 | exit m10 | semmle.label | successor | +| raise.rb:121:9:121:31 | OptionalParameter | raise.rb:121:20:121:30 | String | semmle.label | no-match | +| raise.rb:121:9:121:31 | OptionalParameter | raise.rb:122:1:123:51 | Ensure | semmle.label | match | | raise.rb:121:14:121:18 | raise | raise.rb:121:14:121:30 | MethodCall | semmle.label | successor | | raise.rb:121:14:121:30 | MethodCall | raise.rb:121:1:124:3 | exit m10 (abnormal) | semmle.label | raise | | raise.rb:121:20:121:30 | String | raise.rb:121:14:121:18 | raise | semmle.label | successor | +| raise.rb:122:1:123:51 | Ensure | raise.rb:123:8:123:51 | String | semmle.label | successor | +| raise.rb:123:3:123:6 | puts | raise.rb:123:3:123:51 | MethodCall | semmle.label | successor | +| raise.rb:123:3:123:51 | MethodCall | raise.rb:121:1:124:3 | exit m10 (normal) | semmle.label | successor | +| raise.rb:123:8:123:51 | String | raise.rb:123:3:123:6 | puts | semmle.label | successor | From 89fb2f8498940f9b3d433f8c5755baddf2cf539b Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 14 Dec 2020 11:00:26 +0100 Subject: [PATCH 6/8] CFG: Add `@kind graph` to `Cfg.ql`, and remove labels from ordinary successor edges --- .../codeql_ruby/controlflow/internal/Cfg.ql | 6 +- .../controlflow/graph/Cfg.expected | 5509 ++++++++++------- 2 files changed, 3313 insertions(+), 2202 deletions(-) diff --git a/ql/src/codeql_ruby/controlflow/internal/Cfg.ql b/ql/src/codeql_ruby/controlflow/internal/Cfg.ql index 563f1400079..8ec24510d14 100644 --- a/ql/src/codeql_ruby/controlflow/internal/Cfg.ql +++ b/ql/src/codeql_ruby/controlflow/internal/Cfg.ql @@ -1,3 +1,7 @@ +/** + * @kind graph + */ + import codeql_ruby.controlflow.ControlFlowGraph query predicate nodes(CfgNode n) { any() } @@ -5,6 +9,6 @@ query predicate nodes(CfgNode n) { any() } query predicate edges(CfgNode pred, CfgNode succ, string attr, string val) { exists(SuccessorType t | succ = pred.getASuccessor(t) | attr = "semmle.label" and - val = t.toString() + if t instanceof SuccessorTypes::NormalSuccessor then val = "" else val = t.toString() ) } diff --git a/ql/test/library-tests/controlflow/graph/Cfg.expected b/ql/test/library-tests/controlflow/graph/Cfg.expected index 13d02efa769..d071817a5ef 100644 --- a/ql/test/library-tests/controlflow/graph/Cfg.expected +++ b/ql/test/library-tests/controlflow/graph/Cfg.expected @@ -1,2201 +1,3308 @@ -nodes -| break_ensure.rb:1:1:11:3 | enter m1 | -| break_ensure.rb:1:1:11:3 | exit m1 | -| break_ensure.rb:1:1:11:3 | exit m1 (normal) | -| break_ensure.rb:1:8:1:15 | elements | -| break_ensure.rb:2:3:6:5 | For | -| break_ensure.rb:2:7:2:13 | element | -| break_ensure.rb:2:18:2:25 | elements | -| break_ensure.rb:3:5:5:7 | If | -| break_ensure.rb:3:8:3:8 | x | -| break_ensure.rb:3:8:3:12 | Binary | -| break_ensure.rb:3:12:3:12 | 0 | -| break_ensure.rb:4:7:4:11 | Break | -| break_ensure.rb:7:1:10:5 | Ensure | -| break_ensure.rb:8:3:10:5 | If | -| break_ensure.rb:8:6:8:13 | elements | -| break_ensure.rb:8:6:8:18 | Call | -| break_ensure.rb:8:15:8:18 | nil? | -| break_ensure.rb:9:5:9:8 | puts | -| break_ensure.rb:9:5:9:23 | MethodCall | -| break_ensure.rb:9:10:9:23 | String | -| break_ensure.rb:13:1:25:3 | enter m2 | -| break_ensure.rb:13:1:25:3 | exit m2 | -| break_ensure.rb:13:1:25:3 | exit m2 (normal) | -| break_ensure.rb:13:8:13:15 | elements | -| break_ensure.rb:14:3:24:5 | For | -| break_ensure.rb:14:7:14:13 | element | -| break_ensure.rb:14:18:14:25 | elements | -| break_ensure.rb:16:7:18:9 | If | -| break_ensure.rb:16:10:16:10 | x | -| break_ensure.rb:16:10:16:14 | Binary | -| break_ensure.rb:16:14:16:14 | 0 | -| break_ensure.rb:17:9:17:13 | Break | -| break_ensure.rb:19:5:22:9 | Ensure | -| break_ensure.rb:19:5:22:9 | [ensure: break] Ensure | -| break_ensure.rb:20:7:22:9 | If | -| break_ensure.rb:20:7:22:9 | [ensure: break] If | -| break_ensure.rb:20:10:20:17 | [ensure: break] elements | -| break_ensure.rb:20:10:20:17 | elements | -| break_ensure.rb:20:10:20:22 | Call | -| break_ensure.rb:20:10:20:22 | [ensure: break] Call | -| break_ensure.rb:20:19:20:22 | [ensure: break] nil? | -| break_ensure.rb:20:19:20:22 | nil? | -| break_ensure.rb:21:9:21:12 | [ensure: break] puts | -| break_ensure.rb:21:9:21:12 | puts | -| break_ensure.rb:21:9:21:27 | MethodCall | -| break_ensure.rb:21:9:21:27 | [ensure: break] MethodCall | -| break_ensure.rb:21:14:21:27 | String | -| break_ensure.rb:21:14:21:27 | [ensure: break] String | -| break_ensure.rb:27:1:42:3 | enter m3 | -| break_ensure.rb:27:1:42:3 | exit m3 | -| break_ensure.rb:27:1:42:3 | exit m3 (normal) | -| break_ensure.rb:27:8:27:15 | elements | -| break_ensure.rb:29:5:31:7 | If | -| break_ensure.rb:29:8:29:15 | elements | -| break_ensure.rb:29:8:29:20 | Call | -| break_ensure.rb:29:17:29:20 | nil? | -| break_ensure.rb:30:7:30:12 | Return | -| break_ensure.rb:32:3:39:7 | Ensure | -| break_ensure.rb:32:3:39:7 | [ensure: return] Ensure | -| break_ensure.rb:33:5:39:7 | For | -| break_ensure.rb:33:5:39:7 | [ensure: return] For | -| break_ensure.rb:33:9:33:15 | [ensure: return] element | -| break_ensure.rb:33:9:33:15 | element | -| break_ensure.rb:33:20:33:27 | [ensure: return] elements | -| break_ensure.rb:33:20:33:27 | elements | -| break_ensure.rb:35:9:37:11 | If | -| break_ensure.rb:35:9:37:11 | [ensure: return] If | -| break_ensure.rb:35:12:35:12 | [ensure: return] x | -| break_ensure.rb:35:12:35:12 | x | -| break_ensure.rb:35:12:35:16 | Binary | -| break_ensure.rb:35:12:35:16 | [ensure: return] Binary | -| break_ensure.rb:35:16:35:16 | 0 | -| break_ensure.rb:35:16:35:16 | [ensure: return] 0 | -| break_ensure.rb:36:11:36:15 | Break | -| break_ensure.rb:36:11:36:15 | [ensure: return] Break | -| break_ensure.rb:41:3:41:6 | puts | -| break_ensure.rb:41:3:41:13 | MethodCall | -| break_ensure.rb:41:8:41:13 | String | -| case.rb:1:1:6:3 | enter if_in_case | -| case.rb:1:1:6:3 | exit if_in_case | -| case.rb:1:1:6:3 | exit if_in_case (normal) | -| case.rb:2:3:5:5 | Case | -| case.rb:2:8:2:9 | x1 | -| case.rb:3:5:3:42 | When | -| case.rb:3:10:3:10 | 1 | -| case.rb:3:17:3:42 | ParenthesizedStatements | -| case.rb:3:18:3:41 | If | -| case.rb:3:21:3:22 | x2 | -| case.rb:3:29:3:32 | puts | -| case.rb:3:29:3:37 | MethodCall | -| case.rb:3:34:3:37 | String | -| case.rb:4:5:4:24 | When | -| case.rb:4:10:4:10 | 2 | -| case.rb:4:17:4:20 | puts | -| case.rb:4:17:4:24 | MethodCall | -| case.rb:4:22:4:24 | String | -| cfg.rb:1:1:194:1 | enter top-level | -| cfg.rb:1:1:194:1 | exit top-level | -| cfg.rb:1:1:194:1 | exit top-level (normal) | -| cfg.rb:3:1:3:13 | Alias | -| cfg.rb:3:7:3:9 | foo | -| cfg.rb:3:11:3:13 | bar | -| cfg.rb:5:1:5:1 | b | -| cfg.rb:5:1:5:6 | Assignment | -| cfg.rb:5:5:5:6 | 42 | -| cfg.rb:7:1:7:21 | SymbolArray | -| cfg.rb:7:4:7:12 | BareSymbol | -| cfg.rb:7:7:7:12 | Interpolation | -| cfg.rb:7:10:7:10 | b | -| cfg.rb:7:14:7:20 | BareSymbol | -| cfg.rb:9:1:9:21 | StringArray | -| cfg.rb:9:4:9:12 | BareString | -| cfg.rb:9:7:9:12 | Interpolation | -| cfg.rb:9:10:9:10 | b | -| cfg.rb:9:14:9:20 | BareString | -| cfg.rb:12:3:12:6 | puts | -| cfg.rb:12:3:12:8 | MethodCall | -| cfg.rb:12:8:12:8 | 4 | -| cfg.rb:15:1:17:1 | BeginBlock | -| cfg.rb:15:1:17:1 | enter BEGIN block | -| cfg.rb:15:1:17:1 | exit BEGIN block | -| cfg.rb:15:1:17:1 | exit BEGIN block (normal) | -| cfg.rb:16:3:16:6 | puts | -| cfg.rb:16:3:16:14 | MethodCall | -| cfg.rb:16:8:16:14 | String | -| cfg.rb:19:1:21:1 | EndBlock | -| cfg.rb:19:1:21:1 | enter END block | -| cfg.rb:19:1:21:1 | exit END block | -| cfg.rb:19:1:21:1 | exit END block (normal) | -| cfg.rb:20:3:20:6 | puts | -| cfg.rb:20:3:20:14 | MethodCall | -| cfg.rb:20:8:20:14 | String | -| cfg.rb:23:1:23:2 | 41 | -| cfg.rb:23:1:23:6 | Binary | -| cfg.rb:23:6:23:6 | 1 | -| cfg.rb:25:1:25:1 | 2 | -| cfg.rb:25:1:25:7 | Call | -| cfg.rb:25:1:25:22 | MethodCall | -| cfg.rb:25:3:25:7 | times | -| cfg.rb:25:9:25:22 | enter block | -| cfg.rb:25:9:25:22 | exit block | -| cfg.rb:25:9:25:22 | exit block (normal) | -| cfg.rb:25:12:25:12 | x | -| cfg.rb:25:15:25:18 | puts | -| cfg.rb:25:15:25:20 | MethodCall | -| cfg.rb:25:20:25:20 | x | -| cfg.rb:27:1:27:4 | puts | -| cfg.rb:27:1:27:11 | MethodCall | -| cfg.rb:27:6:27:11 | BlockArgument | -| cfg.rb:27:7:27:11 | Symbol | -| cfg.rb:29:1:29:4 | Proc | -| cfg.rb:29:1:29:8 | Call | -| cfg.rb:29:1:29:24 | MethodCall | -| cfg.rb:29:6:29:8 | new | -| cfg.rb:29:10:29:24 | enter block | -| cfg.rb:29:10:29:24 | exit block | -| cfg.rb:29:10:29:24 | exit block (normal) | -| cfg.rb:29:13:29:14 | BlockParameter | -| cfg.rb:29:17:29:17 | x | -| cfg.rb:29:17:29:22 | Call | -| cfg.rb:29:19:29:22 | call | -| cfg.rb:31:1:33:3 | While | -| cfg.rb:31:7:31:10 | true | -| cfg.rb:32:3:32:9 | Break | -| cfg.rb:32:9:32:9 | 1 | -| cfg.rb:35:1:37:3 | If | -| cfg.rb:35:4:35:8 | false | -| cfg.rb:39:1:39:4 | self | -| cfg.rb:39:1:39:9 | Call | -| cfg.rb:39:1:39:12 | MethodCall | -| cfg.rb:39:6:39:9 | puts | -| cfg.rb:39:11:39:12 | 42 | -| cfg.rb:41:1:45:3 | Case | -| cfg.rb:41:6:41:7 | 10 | -| cfg.rb:42:3:42:24 | When | -| cfg.rb:42:8:42:8 | 1 | -| cfg.rb:42:15:42:18 | puts | -| cfg.rb:42:15:42:24 | MethodCall | -| cfg.rb:42:20:42:24 | String | -| cfg.rb:43:3:43:31 | When | -| cfg.rb:43:8:43:8 | 2 | -| cfg.rb:43:11:43:11 | 3 | -| cfg.rb:43:14:43:14 | 4 | -| cfg.rb:43:21:43:24 | puts | -| cfg.rb:43:21:43:31 | MethodCall | -| cfg.rb:43:26:43:31 | String | -| cfg.rb:44:8:44:11 | puts | -| cfg.rb:44:8:44:18 | MethodCall | -| cfg.rb:44:13:44:18 | String | -| cfg.rb:47:1:50:3 | Case | -| cfg.rb:48:3:48:29 | When | -| cfg.rb:48:8:48:8 | b | -| cfg.rb:48:8:48:13 | Binary | -| cfg.rb:48:13:48:13 | 1 | -| cfg.rb:48:20:48:23 | puts | -| cfg.rb:48:20:48:29 | MethodCall | -| cfg.rb:48:25:48:29 | String | -| cfg.rb:49:3:49:37 | When | -| cfg.rb:49:8:49:8 | b | -| cfg.rb:49:8:49:13 | Binary | -| cfg.rb:49:13:49:13 | 0 | -| cfg.rb:49:16:49:16 | b | -| cfg.rb:49:16:49:20 | Binary | -| cfg.rb:49:20:49:20 | 1 | -| cfg.rb:49:27:49:30 | puts | -| cfg.rb:49:27:49:37 | MethodCall | -| cfg.rb:49:32:49:37 | String | -| cfg.rb:52:1:52:7 | chained | -| cfg.rb:52:1:52:35 | Assignment | -| cfg.rb:52:11:52:13 | String | -| cfg.rb:52:15:52:26 | String | -| cfg.rb:52:16:52:25 | Interpolation | -| cfg.rb:52:18:52:24 | chained | -| cfg.rb:52:28:52:35 | String | -| cfg.rb:54:1:54:9 | character | -| cfg.rb:54:1:54:17 | Assignment | -| cfg.rb:54:13:54:17 | ?\\x40 | -| cfg.rb:58:1:72:3 | Class | -| cfg.rb:58:7:58:11 | Silly | -| cfg.rb:58:13:58:20 | Superclass | -| cfg.rb:58:15:58:20 | Object | -| cfg.rb:59:3:59:9 | complex | -| cfg.rb:59:3:59:17 | Assignment | -| cfg.rb:59:13:59:17 | 10-2i | -| cfg.rb:60:3:60:13 | conditional | -| cfg.rb:60:3:60:40 | Assignment | -| cfg.rb:60:17:60:17 | b | -| cfg.rb:60:17:60:22 | Binary | -| cfg.rb:60:17:60:40 | Conditional | -| cfg.rb:60:21:60:22 | 10 | -| cfg.rb:60:26:60:32 | String | -| cfg.rb:60:36:60:40 | String | -| cfg.rb:61:3:61:3 | C | -| cfg.rb:61:3:61:16 | Assignment | -| cfg.rb:61:7:61:16 | String | -| cfg.rb:62:3:62:13 | DestructuredLeftAssignment | -| cfg.rb:62:3:62:27 | Assignment | -| cfg.rb:62:4:62:4 | x | -| cfg.rb:62:7:62:12 | DestructuredLeftAssignment | -| cfg.rb:62:8:62:8 | y | -| cfg.rb:62:11:62:11 | z | -| cfg.rb:62:17:62:27 | Array | -| cfg.rb:62:18:62:18 | 1 | -| cfg.rb:62:21:62:26 | Array | -| cfg.rb:62:22:62:22 | 2 | -| cfg.rb:62:25:62:25 | 3 | -| cfg.rb:63:3:66:5 | enter pattern | -| cfg.rb:63:3:66:5 | exit pattern | -| cfg.rb:63:3:66:5 | exit pattern (normal) | -| cfg.rb:63:16:63:20 | DestructuredParameter | -| cfg.rb:63:17:63:17 | a | -| cfg.rb:63:19:63:19 | b | -| cfg.rb:64:5:64:8 | puts | -| cfg.rb:64:5:64:10 | MethodCall | -| cfg.rb:64:10:64:10 | a | -| cfg.rb:65:5:65:8 | puts | -| cfg.rb:65:5:65:10 | MethodCall | -| cfg.rb:65:10:65:10 | b | -| cfg.rb:67:3:67:7 | items | -| cfg.rb:67:3:67:19 | Assignment | -| cfg.rb:67:11:67:19 | Array | -| cfg.rb:67:12:67:12 | 1 | -| cfg.rb:67:15:67:15 | 2 | -| cfg.rb:67:18:67:18 | 3 | -| cfg.rb:68:3:68:6 | puts | -| cfg.rb:68:3:68:15 | MethodCall | -| cfg.rb:68:8:68:12 | items | -| cfg.rb:68:8:68:15 | ElementReference | -| cfg.rb:68:14:68:14 | 2 | -| cfg.rb:69:3:71:5 | enter print | -| cfg.rb:69:3:71:5 | exit print | -| cfg.rb:69:3:71:5 | exit print (normal) | -| cfg.rb:70:5:70:8 | puts | -| cfg.rb:70:5:70:16 | MethodCall | -| cfg.rb:70:10:70:16 | String | -| cfg.rb:74:1:74:1 | x | -| cfg.rb:74:1:74:6 | Assignment | -| cfg.rb:74:5:74:6 | 42 | -| cfg.rb:75:1:75:47 | If | -| cfg.rb:75:4:75:4 | x | -| cfg.rb:75:4:75:8 | Binary | -| cfg.rb:75:8:75:8 | 0 | -| cfg.rb:75:15:75:15 | 0 | -| cfg.rb:75:17:75:43 | Elsif | -| cfg.rb:75:23:75:23 | x | -| cfg.rb:75:23:75:28 | Binary | -| cfg.rb:75:27:75:28 | 10 | -| cfg.rb:75:35:75:36 | 10 | -| cfg.rb:75:43:75:43 | x | -| cfg.rb:78:3:78:3 | ; | -| cfg.rb:83:3:83:6 | puts | -| cfg.rb:83:3:83:11 | MethodCall | -| cfg.rb:83:8:83:11 | String | -| cfg.rb:84:1:85:12 | Ensure | -| cfg.rb:85:3:85:6 | puts | -| cfg.rb:85:3:85:12 | MethodCall | -| cfg.rb:85:8:85:12 | String | -| cfg.rb:88:1:88:6 | escape | -| cfg.rb:88:1:88:23 | Assignment | -| cfg.rb:88:10:88:23 | String | -| cfg.rb:88:17:88:20 | Interpolation | -| cfg.rb:88:19:88:19 | x | -| cfg.rb:90:1:93:3 | For | -| cfg.rb:90:5:90:5 | x | -| cfg.rb:90:10:90:26 | Array | -| cfg.rb:90:11:90:13 | 1.4 | -| cfg.rb:90:16:90:18 | 2.5 | -| cfg.rb:90:21:90:25 | 3.4e5 | -| cfg.rb:91:3:91:24 | If | -| cfg.rb:91:6:91:6 | x | -| cfg.rb:91:6:91:10 | Binary | -| cfg.rb:91:10:91:10 | 3 | -| cfg.rb:91:17:91:20 | Next | -| cfg.rb:92:3:92:6 | puts | -| cfg.rb:92:3:92:8 | MethodCall | -| cfg.rb:92:8:92:8 | x | -| cfg.rb:95:1:95:7 | $global | -| cfg.rb:95:1:95:12 | Assignment | -| cfg.rb:95:11:95:12 | 42 | -| cfg.rb:97:1:97:4 | map1 | -| cfg.rb:97:1:97:32 | Assignment | -| cfg.rb:97:8:97:32 | Hash | -| cfg.rb:97:10:97:12 | String | -| cfg.rb:97:10:97:19 | Pair | -| cfg.rb:97:17:97:19 | String | -| cfg.rb:97:22:97:24 | String | -| cfg.rb:97:22:97:29 | Pair | -| cfg.rb:97:27:97:29 | String | -| cfg.rb:98:1:98:4 | map2 | -| cfg.rb:98:1:98:36 | Assignment | -| cfg.rb:98:8:98:36 | Hash | -| cfg.rb:98:10:98:15 | HashSplatArgument | -| cfg.rb:98:12:98:15 | map1 | -| cfg.rb:98:18:98:20 | String | -| cfg.rb:98:18:98:27 | Pair | -| cfg.rb:98:25:98:27 | String | -| cfg.rb:98:30:98:35 | HashSplatArgument | -| cfg.rb:98:32:98:35 | map1 | -| cfg.rb:101:1:104:3 | enter parameters | -| cfg.rb:101:1:104:3 | exit parameters | -| cfg.rb:101:1:104:3 | exit parameters (normal) | -| cfg.rb:101:16:101:25 | OptionalParameter | -| cfg.rb:101:24:101:25 | 42 | -| cfg.rb:101:28:101:31 | KeywordParameter | -| cfg.rb:101:34:101:41 | HashSplatParameter | -| cfg.rb:102:3:102:6 | puts | -| cfg.rb:102:3:102:12 | MethodCall | -| cfg.rb:102:8:102:12 | value | -| cfg.rb:103:3:103:20 | Return | -| cfg.rb:103:10:103:15 | kwargs | -| cfg.rb:103:10:103:20 | ElementReference | -| cfg.rb:103:17:103:19 | key | -| cfg.rb:106:1:106:4 | type | -| cfg.rb:106:1:106:17 | Assignment | -| cfg.rb:106:9:106:17 | String | -| cfg.rb:107:1:107:5 | table | -| cfg.rb:107:1:107:14 | Assignment | -| cfg.rb:107:9:107:14 | String | -| cfg.rb:108:1:108:4 | puts | -| cfg.rb:108:1:108:12 | MethodCall | -| cfg.rb:108:6:108:12 | ParenthesizedStatements | -| cfg.rb:108:7:108:11 | < elements + +# 13| enter m2 +#-----| -> elements + +# 27| enter m3 +#-----| -> elements + +case.rb: +# 1| enter if_in_case +#-----| -> Case + +cfg.rb: +# 1| enter top-level +#-----| -> Alias + +# 15| enter BEGIN block +#-----| -> BeginBlock + +# 19| enter END block +#-----| -> EndBlock + +# 25| enter block +#-----| -> x + +# 29| enter block +#-----| -> BlockParameter + +# 63| enter pattern +#-----| -> a + +# 69| enter print +#-----| -> String + +# 101| enter parameters +#-----| -> OptionalParameter + +# 120| enter lambda +#-----| -> x + +# 120| enter block +#-----| -> y + +# 142| enter print +#-----| -> String + +# 149| enter method +#-----| -> silly + +# 153| enter two_parameters +#-----| -> a + +# 185| enter run_block +#-----| -> Yield + +# 189| enter block +#-----| -> x + +exit.rb: +# 1| enter m1 +#-----| -> x + +# 8| enter m2 +#-----| -> x + +ifs.rb: +# 1| enter m1 +#-----| -> x + +# 11| enter m2 +#-----| -> b + +# 18| enter m3 +#-----| -> x + +# 28| enter m4 +#-----| -> b1 + +loops.rb: +# 1| enter m1 +#-----| -> x + +# 8| enter m2 +#-----| -> x + +# 24| enter m3 +#-----| -> 1 + +# 25| enter do block +#-----| -> x + +raise.rb: +# 1| enter top-level +#-----| -> Class + +# 7| enter m1 +#-----| -> x + +# 14| enter m2 +#-----| -> b + +# 25| enter m3 +#-----| -> b + +# 36| enter m4 +#-----| -> b + +# 47| enter m5 +#-----| -> b + +# 57| enter m6 +#-----| -> b + +# 68| enter m7 +#-----| -> x + +# 79| enter m8 +#-----| -> x + +# 94| enter m9 +#-----| -> x + +# 121| enter m10 +#-----| -> OptionalParameter + +break_ensure.rb: +# 1| elements +#-----| -> elements + +# 2| For +#-----| non-empty -> element +#-----| empty -> Ensure + +# 2| element +#-----| -> If + +# 2| elements +#-----| -> For + +# 3| If +#-----| -> x + +# 3| Binary +#-----| false -> For +#-----| true -> Break + +# 3| x +#-----| -> 0 + +# 3| 0 +#-----| -> Binary + +# 4| Break +#-----| break -> Ensure + +# 7| Ensure +#-----| -> If + +# 8| If +#-----| -> elements + +# 8| Call +#-----| true -> String +#-----| false -> exit m1 (normal) + +# 8| elements +#-----| -> nil? + +# 8| nil? +#-----| -> Call + +# 9| MethodCall +#-----| -> exit m1 (normal) + +# 9| puts +#-----| -> MethodCall + +# 9| String +#-----| -> puts + +# 13| elements +#-----| -> elements + +# 14| For +#-----| non-empty -> element +#-----| empty -> exit m2 (normal) + +# 14| element +#-----| -> If + +# 14| elements +#-----| -> For + +# 16| If +#-----| -> x + +# 16| Binary +#-----| true -> Break +#-----| false -> Ensure + +# 16| x +#-----| -> 0 + +# 16| 0 +#-----| -> Binary + +# 17| Break +#-----| break -> [ensure: break] Ensure + +# 19| Ensure +#-----| -> If + +# 19| [ensure: break] Ensure +#-----| -> [ensure: break] If + +# 20| If +#-----| -> elements + +# 20| [ensure: break] If +#-----| -> [ensure: break] elements + +# 20| Call +#-----| false -> For +#-----| true -> String + +# 20| [ensure: break] Call +#-----| true -> [ensure: break] String +#-----| false -> exit m2 (normal) + +# 20| elements +#-----| -> nil? + +# 20| [ensure: break] elements +#-----| -> [ensure: break] nil? + +# 20| nil? +#-----| -> Call + +# 20| [ensure: break] nil? +#-----| -> [ensure: break] Call + +# 21| MethodCall +#-----| -> For + +# 21| [ensure: break] MethodCall +#-----| break -> exit m2 (normal) + +# 21| puts +#-----| -> MethodCall + +# 21| [ensure: break] puts +#-----| -> [ensure: break] MethodCall + +# 21| String +#-----| -> puts + +# 21| [ensure: break] String +#-----| -> [ensure: break] puts + +# 27| elements +#-----| -> If + +# 29| If +#-----| -> elements + +# 29| Call +#-----| true -> Return +#-----| false -> Ensure + +# 29| elements +#-----| -> nil? + +# 29| nil? +#-----| -> Call + +# 30| Return +#-----| return -> [ensure: return] Ensure + +# 32| Ensure +#-----| -> elements + +# 32| [ensure: return] Ensure +#-----| -> [ensure: return] elements + +# 33| For +#-----| non-empty -> element +#-----| empty -> String + +# 33| [ensure: return] For +#-----| non-empty -> [ensure: return] element +#-----| return -> exit m3 (normal) + +# 33| element +#-----| -> If + +# 33| [ensure: return] element +#-----| -> [ensure: return] If + +# 33| elements +#-----| -> For + +# 33| [ensure: return] elements +#-----| -> [ensure: return] For + +# 35| If +#-----| -> x + +# 35| [ensure: return] If +#-----| -> [ensure: return] x + +# 35| Binary +#-----| false -> For +#-----| true -> Break + +# 35| [ensure: return] Binary +#-----| false -> [ensure: return] For +#-----| true -> [ensure: return] Break + +# 35| x +#-----| -> 0 + +# 35| [ensure: return] x +#-----| -> [ensure: return] 0 + +# 35| 0 +#-----| -> Binary + +# 35| [ensure: return] 0 +#-----| -> [ensure: return] Binary + +# 36| Break +#-----| break -> String + +# 36| [ensure: return] Break +#-----| return -> exit m3 (normal) + +# 41| MethodCall +#-----| -> exit m3 (normal) + +# 41| puts +#-----| -> MethodCall + +# 41| String +#-----| -> puts + +case.rb: +# 2| Case +#-----| -> x1 + +# 2| x1 +#-----| -> When + +# 3| When +#-----| -> 1 + +# 3| 1 +#-----| match -> If +#-----| no-match -> When + +# 3| ParenthesizedStatements +#-----| -> exit if_in_case (normal) + +# 3| If +#-----| -> x2 + +# 3| x2 +#-----| false -> ParenthesizedStatements +#-----| true -> String + +# 3| MethodCall +#-----| -> ParenthesizedStatements + +# 3| puts +#-----| -> MethodCall + +# 3| String +#-----| -> puts + +# 4| When +#-----| -> 2 + +# 4| 2 +#-----| match -> String +#-----| no-match -> exit if_in_case (normal) + +# 4| MethodCall +#-----| -> exit if_in_case (normal) + +# 4| puts +#-----| -> MethodCall + +# 4| String +#-----| -> puts + +cfg.rb: +# 3| Alias +#-----| -> foo + +# 3| foo +#-----| -> bar + +# 3| bar +#-----| -> 42 + +# 5| Assignment +#-----| -> b + +# 5| b +#-----| -> Assignment + +# 5| 42 +#-----| -> b + +# 7| SymbolArray +#-----| -> b + +# 7| BareSymbol +#-----| -> BareSymbol + +# 7| Interpolation +#-----| -> BareSymbol + +# 7| b +#-----| -> Interpolation + +# 7| BareSymbol +#-----| -> SymbolArray + +# 9| StringArray +#-----| -> 4 + +# 9| BareString +#-----| -> BareString + +# 9| Interpolation +#-----| -> BareString + +# 9| b +#-----| -> Interpolation + +# 9| BareString +#-----| -> StringArray + +# 12| MethodCall +#-----| -> 41 + +# 12| puts +#-----| -> MethodCall + +# 12| 4 +#-----| -> puts + +# 15| BeginBlock +#-----| -> String + +# 16| MethodCall +#-----| -> exit BEGIN block (normal) + +# 16| puts +#-----| -> MethodCall + +# 16| String +#-----| -> puts + +# 19| EndBlock +#-----| -> String + +# 20| MethodCall +#-----| -> exit END block (normal) + +# 20| puts +#-----| -> MethodCall + +# 20| String +#-----| -> puts + +# 23| Binary +#-----| -> 2 + +# 23| 41 +#-----| -> 1 + +# 23| 1 +#-----| -> Binary + +# 25| MethodCall +#-----| -> Symbol + +# 25| Call +#-----| -> MethodCall + +# 25| 2 +#-----| -> times + +# 25| times +#-----| -> Call + +# 25| x +#-----| -> x + +# 25| MethodCall +#-----| -> exit block (normal) + +# 25| puts +#-----| -> MethodCall + +# 25| x +#-----| -> puts + +# 27| MethodCall +#-----| -> Proc + +# 27| puts +#-----| -> MethodCall + +# 27| BlockArgument +#-----| -> puts + +# 27| Symbol +#-----| -> BlockArgument + +# 29| MethodCall +#-----| -> While + +# 29| Call +#-----| -> MethodCall + +# 29| Proc +#-----| -> new + +# 29| new +#-----| -> Call + +# 29| BlockParameter +#-----| -> x + +# 29| Call +#-----| -> exit block (normal) + +# 29| x +#-----| -> call + +# 29| call +#-----| -> Call + +# 31| While +#-----| -> true + +# 31| true +#-----| true -> 1 + +# 32| Break +#-----| break -> If + +# 32| 1 +#-----| -> Break + +# 35| If +#-----| -> false + +# 35| false +#-----| false -> 42 + +# 39| MethodCall +#-----| -> Case + +# 39| Call +#-----| -> MethodCall + +# 39| self +#-----| -> puts + +# 39| puts +#-----| -> Call + +# 39| 42 +#-----| -> self + +# 41| Case +#-----| -> 10 + +# 41| 10 +#-----| -> When + +# 42| When +#-----| -> 1 + +# 42| 1 +#-----| match -> String +#-----| no-match -> When + +# 42| MethodCall +#-----| -> Case + +# 42| puts +#-----| -> MethodCall + +# 42| String +#-----| -> puts + +# 43| When +#-----| -> 2 + +# 43| 2 +#-----| no-match -> 3 +#-----| match -> String + +# 43| 3 +#-----| no-match -> 4 +#-----| match -> String + +# 43| 4 +#-----| match -> String +#-----| no-match -> String + +# 43| MethodCall +#-----| -> Case + +# 43| puts +#-----| -> MethodCall + +# 43| String +#-----| -> puts + +# 44| MethodCall +#-----| -> Case + +# 44| puts +#-----| -> MethodCall + +# 44| String +#-----| -> puts + +# 47| Case +#-----| -> When + +# 48| When +#-----| -> b + +# 48| Binary +#-----| true -> String +#-----| false -> When + +# 48| b +#-----| -> 1 + +# 48| 1 +#-----| -> Binary + +# 48| MethodCall +#-----| -> String + +# 48| puts +#-----| -> MethodCall + +# 48| String +#-----| -> puts + +# 49| When +#-----| -> b + +# 49| Binary +#-----| false -> b +#-----| true -> String + +# 49| b +#-----| -> 0 + +# 49| 0 +#-----| -> Binary + +# 49| Binary +#-----| true -> String +#-----| false -> String + +# 49| b +#-----| -> 1 + +# 49| 1 +#-----| -> Binary + +# 49| MethodCall +#-----| -> String + +# 49| puts +#-----| -> MethodCall + +# 49| String +#-----| -> puts + +# 52| Assignment +#-----| -> ?\x40 + +# 52| chained +#-----| -> Assignment + +# 52| String +#-----| -> chained + +# 52| String +#-----| -> String + +# 52| Interpolation +#-----| -> String + +# 52| chained +#-----| -> Interpolation + +# 52| String +#-----| -> chained + +# 54| Assignment +#-----| -> Class + +# 54| character +#-----| -> Assignment + +# 54| ?\x40 +#-----| -> character + +# 58| Class +#-----| -> Silly + +# 58| Silly +#-----| -> Object + +# 58| Superclass +#-----| -> 10-2i + +# 58| Object +#-----| -> Superclass + +# 59| Assignment +#-----| -> Conditional + +# 59| complex +#-----| -> Assignment + +# 59| 10-2i +#-----| -> complex + +# 60| Assignment +#-----| -> String + +# 60| conditional +#-----| -> Assignment + +# 60| Conditional +#-----| -> b + +# 60| Binary +#-----| true -> String +#-----| false -> String + +# 60| b +#-----| -> 10 + +# 60| 10 +#-----| -> Binary + +# 60| String +#-----| -> conditional + +# 60| String +#-----| -> conditional + +# 61| Assignment +#-----| -> 1 + +# 61| C +#-----| -> Assignment + +# 61| String +#-----| -> C + +# 62| Assignment +#-----| -> 1 + +# 62| DestructuredLeftAssignment +#-----| -> Assignment + +# 62| x +#-----| -> y + +# 62| DestructuredLeftAssignment +#-----| -> DestructuredLeftAssignment + +# 62| y +#-----| -> z + +# 62| z +#-----| -> DestructuredLeftAssignment + +# 62| Array +#-----| -> x + +# 62| 1 +#-----| -> 2 + +# 62| Array +#-----| -> Array + +# 62| 2 +#-----| -> 3 + +# 62| 3 +#-----| -> Array + +# 63| DestructuredParameter +#-----| -> a + +# 63| a +#-----| -> b + +# 63| b +#-----| -> DestructuredParameter + +# 64| MethodCall +#-----| -> b + +# 64| puts +#-----| -> MethodCall + +# 64| a +#-----| -> puts + +# 65| MethodCall +#-----| -> exit pattern (normal) + +# 65| puts +#-----| -> MethodCall + +# 65| b +#-----| -> puts + +# 67| Assignment +#-----| -> items + +# 67| items +#-----| -> Assignment + +# 67| Array +#-----| -> items + +# 67| 1 +#-----| -> 2 + +# 67| 2 +#-----| -> 3 + +# 67| 3 +#-----| -> Array + +# 68| MethodCall +#-----| -> 42 + +# 68| puts +#-----| -> MethodCall + +# 68| ElementReference +#-----| -> puts + +# 68| items +#-----| -> 2 + +# 68| 2 +#-----| -> ElementReference + +# 70| MethodCall +#-----| -> exit print (normal) + +# 70| puts +#-----| -> MethodCall + +# 70| String +#-----| -> puts + +# 74| Assignment +#-----| -> If + +# 74| x +#-----| -> Assignment + +# 74| 42 +#-----| -> x + +# 75| If +#-----| -> x + +# 75| Binary +#-----| true -> 0 +#-----| false -> Elsif + +# 75| x +#-----| -> 0 + +# 75| 0 +#-----| -> Binary + +# 75| 0 +#-----| -> ; + +# 75| Elsif +#-----| -> x + +# 75| Binary +#-----| true -> 10 +#-----| false -> x + +# 75| x +#-----| -> 10 + +# 75| 10 +#-----| -> Binary + +# 75| 10 +#-----| -> ; + +# 75| x +#-----| -> ; + +# 78| ; +#-----| -> String + +# 83| MethodCall +#-----| -> Ensure + +# 83| puts +#-----| -> MethodCall + +# 83| String +#-----| -> puts + +# 84| Ensure +#-----| -> String + +# 85| MethodCall +#-----| -> x + +# 85| puts +#-----| -> MethodCall + +# 85| String +#-----| -> puts + +# 88| Assignment +#-----| -> 1.4 + +# 88| escape +#-----| -> Assignment + +# 88| String +#-----| -> escape + +# 88| Interpolation +#-----| -> String + +# 88| x +#-----| -> Interpolation + +# 90| For +#-----| non-empty -> x +#-----| empty -> 42 + +# 90| x +#-----| -> If + +# 90| Array +#-----| -> For + +# 90| 1.4 +#-----| -> 2.5 + +# 90| 2.5 +#-----| -> 3.4e5 + +# 90| 3.4e5 +#-----| -> Array + +# 91| If +#-----| -> x + +# 91| Binary +#-----| true -> Next +#-----| false -> x + +# 91| x +#-----| -> 3 + +# 91| 3 +#-----| -> Binary + +# 91| Next +#-----| next -> For + +# 92| MethodCall +#-----| -> For + +# 92| puts +#-----| -> MethodCall + +# 92| x +#-----| -> puts + +# 95| Assignment +#-----| -> String + +# 95| $global +#-----| -> Assignment + +# 95| 42 +#-----| -> $global + +# 97| Assignment +#-----| -> map1 + +# 97| map1 +#-----| -> Assignment + +# 97| Hash +#-----| -> map1 + +# 97| Pair +#-----| -> String + +# 97| String +#-----| -> String + +# 97| String +#-----| -> Pair + +# 97| Pair +#-----| -> Hash + +# 97| String +#-----| -> String + +# 97| String +#-----| -> Pair + +# 98| Assignment +#-----| -> String + +# 98| map2 +#-----| -> Assignment + +# 98| Hash +#-----| -> map2 + +# 98| HashSplatArgument +#-----| -> String + +# 98| map1 +#-----| -> HashSplatArgument + +# 98| Pair +#-----| -> map1 + +# 98| String +#-----| -> String + +# 98| String +#-----| -> Pair + +# 98| HashSplatArgument +#-----| -> Hash + +# 98| map1 +#-----| -> HashSplatArgument + +# 101| OptionalParameter +#-----| no-match -> 42 +#-----| match -> KeywordParameter + +# 101| 42 +#-----| -> KeywordParameter + +# 101| KeywordParameter +#-----| -> HashSplatParameter + +# 101| HashSplatParameter +#-----| -> value + +# 102| MethodCall +#-----| -> kwargs + +# 102| puts +#-----| -> MethodCall + +# 102| value +#-----| -> puts + +# 103| Return +#-----| return -> exit parameters (normal) + +# 103| ElementReference +#-----| -> Return + +# 103| kwargs +#-----| -> key + +# 103| key +#-----| -> ElementReference + +# 106| Assignment +#-----| -> String + +# 106| type +#-----| -> Assignment + +# 106| String +#-----| -> type + +# 107| Assignment +#-----| -> < Assignment + +# 107| String +#-----| -> table + +# 108| MethodCall +#-----| -> IfModifier + +# 108| puts +#-----| -> MethodCall + +# 108| ParenthesizedStatements +#-----| -> puts + +# 108| < table + +# 108| HeredocBody +#-----| -> ParenthesizedStatements + +# 109| Interpolation +#-----| -> type + +# 109| table +#-----| -> Interpolation + +# 110| Interpolation +#-----| -> HeredocBody + +# 110| type +#-----| -> Interpolation + +# 113| IfModifier +#-----| -> b + +# 113| MethodCall +#-----| -> Class + +# 113| puts +#-----| -> MethodCall + +# 113| String +#-----| -> puts + +# 113| Binary +#-----| true -> String +#-----| false -> Class + +# 113| b +#-----| -> 10 + +# 113| 10 +#-----| -> Binary + +# 115| Class +#-----| -> C + +# 115| C +#-----| -> 42 + +# 116| Assignment +#-----| -> 10 + +# 116| @field +#-----| -> Assignment + +# 116| 42 +#-----| -> @field + +# 117| Assignment +#-----| -> swap + +# 117| @@static_field +#-----| -> Assignment + +# 117| 10 +#-----| -> @@static_field + +# 120| Assignment +#-----| -> Module + +# 120| swap +#-----| -> Assignment + +# 120| DestructuredParameter +#-----| -> exit lambda (normal) + +# 120| x +#-----| -> y + +# 120| y +#-----| -> DestructuredParameter + +# 120| Array +#-----| -> exit block (normal) + +# 120| y +#-----| -> x + +# 120| x +#-----| -> Array + +# 122| Module +#-----| -> M + +# 122| M +#-----| -> nil + +# 123| Assignment +#-----| -> 2 + +# 123| nothing +#-----| -> Assignment + +# 123| nil +#-----| -> nothing + +# 124| Assignment +#-----| -> some + +# 124| some +#-----| -> Assignment + +# 124| 2 +#-----| -> some + +# 125| OperatorAssignment +#-----| -> 2 + +# 125| some +#-----| -> 10 + +# 125| 10 +#-----| -> OperatorAssignment + +# 126| Assignment +#-----| -> 0 + +# 126| last +#-----| -> Assignment + +# 126| ParenthesizedStatements +#-----| -> last + +# 126| 2 +#-----| -> 4 + +# 126| 4 +#-----| -> 7 + +# 126| 7 +#-----| -> ParenthesizedStatements + +# 127| Assignment +#-----| -> 1 + +# 127| range +#-----| -> Assignment + +# 127| Range +#-----| -> range + +# 127| 0 +#-----| -> 9 + +# 127| 9 +#-----| -> Range + +# 128| Assignment +#-----| -> range + +# 128| half +#-----| -> Assignment + +# 128| Binary +#-----| -> half + +# 128| Binary +#-----| -> 1 + +# 128| 1 +#-----| -> Rational + +# 128| Rational +#-----| -> Binary + +# 128| Binary +#-----| -> Binary + +# 128| 1 +#-----| -> Rational + +# 128| Rational +#-----| -> Binary + +# 129| Assignment +#-----| -> 5 + +# 129| regex +#-----| -> Assignment + +# 129| Regex +#-----| -> regex + +# 129| Interpolation +#-----| -> Regex + +# 129| range +#-----| -> Interpolation + +# 130| Assignment +#-----| -> RescueModifier + +# 130| Constant +#-----| -> Assignment + +# 130| 5 +#-----| -> Constant + +# 133| RescueModifier +#-----| -> 1 + +# 133| Binary +#-----| raise -> String +#-----| -> 1 + +# 133| 1 +#-----| -> 0 + +# 133| 0 +#-----| -> Binary + +# 133| MethodCall +#-----| -> 1 + +# 133| puts +#-----| -> MethodCall + +# 133| String +#-----| -> puts + +# 135| Assignment +#-----| -> M + +# 135| DestructuredLeftAssignment +#-----| -> Assignment + +# 135| RestAssignment +#-----| -> last + +# 135| init +#-----| -> RestAssignment + +# 135| last +#-----| -> DestructuredLeftAssignment + +# 135| 1 +#-----| -> 2 + +# 135| 2 +#-----| -> 3 + +# 135| 3 +#-----| -> init + +# 137| ScopeResolution +#-----| -> M + +# 137| M +#-----| -> Constant + +# 137| Constant +#-----| -> ScopeResolution + +# 138| ScopeResolution +#-----| -> Silly + +# 138| Call +#-----| -> Constant + +# 138| M +#-----| -> itself + +# 138| itself +#-----| -> Call + +# 138| Constant +#-----| -> ScopeResolution + +# 140| Call +#-----| -> Silly + +# 140| Silly +#-----| -> itself + +# 140| itself +#-----| -> Call + +# 143| MethodCall +#-----| -> super + +# 143| puts +#-----| -> MethodCall + +# 143| String +#-----| -> puts + +# 144| MethodCall +#-----| -> exit print (normal) + +# 144| puts +#-----| -> MethodCall + +# 144| MethodCall +#-----| -> puts + +# 144| Call +#-----| -> MethodCall + +# 144| super +#-----| -> print + +# 144| print +#-----| -> Call + +# 148| Assignment +#-----| -> 1 + +# 148| silly +#-----| -> Assignment + +# 148| Call +#-----| -> silly + +# 148| Silly +#-----| -> new + +# 148| new +#-----| -> Call + +# 149| silly +#-----| -> SplatParameter + +# 149| SplatParameter +#-----| -> x + +# 150| MethodCall +#-----| -> exit method (normal) + +# 150| puts +#-----| -> MethodCall + +# 150| x +#-----| -> puts + +# 153| a +#-----| -> b + +# 153| b +#-----| -> exit two_parameters (normal) + +# 155| MethodCall +#-----| -> __FILE__ + +# 155| two_parameters +#-----| -> MethodCall + +# 155| SplatArgument +#-----| -> two_parameters + +# 155| Array +#-----| -> SplatArgument + +# 155| 1 +#-----| -> 2 + +# 155| 2 +#-----| -> Array + +# 157| Assignment +#-----| -> Symbol + +# 157| scriptfile +#-----| -> Assignment + +# 157| Subshell +#-----| -> scriptfile + +# 157| Interpolation +#-----| -> Subshell + +# 157| __FILE__ +#-----| -> Interpolation + +# 159| Assignment +#-----| -> true + +# 159| symbol +#-----| -> Assignment + +# 159| Symbol +#-----| -> symbol + +# 161| Assignment +#-----| -> true + +# 161| x +#-----| -> Assignment + +# 161| true +#-----| -> x + +# 162| Assignment +#-----| -> 42 + +# 162| x +#-----| -> Assignment + +# 162| Unary +#-----| -> x + +# 162| true +#-----| -> Unary + +# 163| Assignment +#-----| -> Undef + +# 163| x +#-----| -> Assignment + +# 163| Unary +#-----| -> x + +# 163| 42 +#-----| -> Unary + +# 165| Undef +#-----| -> two_parameters + +# 165| two_parameters +#-----| -> Unless + +# 167| Unless +#-----| -> x + +# 167| Binary +#-----| false -> String +#-----| true -> String + +# 167| x +#-----| -> 10 + +# 167| 10 +#-----| -> Binary + +# 167| MethodCall +#-----| -> UnlessModifier + +# 167| puts +#-----| -> MethodCall + +# 167| String +#-----| -> puts + +# 167| MethodCall +#-----| -> UnlessModifier + +# 167| puts +#-----| -> MethodCall + +# 167| String +#-----| -> puts + +# 169| UnlessModifier +#-----| -> x + +# 169| MethodCall +#-----| -> Until + +# 169| puts +#-----| -> MethodCall + +# 169| String +#-----| -> puts + +# 169| Binary +#-----| false -> String +#-----| true -> Until + +# 169| x +#-----| -> 0 + +# 169| 0 +#-----| -> Binary + +# 171| Until +#-----| -> x + +# 171| Binary +#-----| false -> x +#-----| true -> 0 + +# 171| x +#-----| -> 10 + +# 171| 10 +#-----| -> Binary + +# 171| OperatorAssignment +#-----| -> String + +# 171| x +#-----| -> 10 + +# 171| 10 +#-----| -> OperatorAssignment + +# 171| MethodCall +#-----| -> x + +# 171| puts +#-----| -> MethodCall + +# 171| String +#-----| -> puts + +# 173| Assignment +#-----| -> UntilModifier + +# 173| i +#-----| -> Assignment + +# 173| 0 +#-----| -> i + +# 174| UntilModifier +#-----| -> i + +# 174| ParenthesizedStatements +#-----| -> i + +# 174| MethodCall +#-----| -> i + +# 174| puts +#-----| -> MethodCall + +# 174| String +#-----| -> puts + +# 174| OperatorAssignment +#-----| -> ParenthesizedStatements + +# 174| i +#-----| -> 1 + +# 174| 1 +#-----| -> OperatorAssignment + +# 174| Binary +#-----| false -> String +#-----| true -> 0 + +# 174| i +#-----| -> 10 + +# 174| 10 +#-----| -> Binary + +# 176| Assignment +#-----| -> While + +# 176| x +#-----| -> Assignment + +# 176| 0 +#-----| -> x + +# 177| While +#-----| -> x + +# 177| Binary +#-----| true -> x +#-----| false -> WhileModifier + +# 177| x +#-----| -> 10 + +# 177| 10 +#-----| -> Binary + +# 178| OperatorAssignment +#-----| -> If + +# 178| x +#-----| -> 1 + +# 178| 1 +#-----| -> OperatorAssignment + +# 179| If +#-----| -> x + +# 179| Binary +#-----| true -> Redo +#-----| false -> x + +# 179| x +#-----| -> 5 + +# 179| 5 +#-----| -> Binary + +# 179| Redo +#-----| redo -> x + +# 180| MethodCall +#-----| -> x + +# 180| puts +#-----| -> MethodCall + +# 180| x +#-----| -> puts + +# 183| WhileModifier +#-----| -> i + +# 183| ParenthesizedStatements +#-----| -> i + +# 183| MethodCall +#-----| -> i + +# 183| puts +#-----| -> MethodCall + +# 183| String +#-----| -> puts + +# 183| OperatorAssignment +#-----| -> ParenthesizedStatements + +# 183| i +#-----| -> 1 + +# 183| 1 +#-----| -> OperatorAssignment + +# 183| Binary +#-----| true -> String +#-----| false -> run_block + +# 183| i +#-----| -> 0 + +# 183| 0 +#-----| -> Binary + +# 186| Yield +#-----| -> 42 + +# 186| 42 + +# 189| MethodCall +#-----| -> exit top-level (normal) + +# 189| run_block +#-----| -> MethodCall + +# 189| x +#-----| -> x + +# 189| MethodCall +#-----| -> exit block (normal) + +# 189| puts +#-----| -> MethodCall + +# 189| x +#-----| -> puts + +exit.rb: +# 1| x +#-----| -> If + +# 2| If +#-----| -> x + +# 2| Binary +#-----| true -> 1 +#-----| false -> String + +# 2| x +#-----| -> 2 + +# 2| 2 +#-----| -> Binary + +# 3| MethodCall +#-----| exit -> exit m1 (abnormal) + +# 3| exit +#-----| -> MethodCall + +# 3| 1 +#-----| -> exit + +# 5| MethodCall +#-----| -> exit m1 (normal) + +# 5| puts +#-----| -> MethodCall + +# 5| String +#-----| -> puts + +# 8| x +#-----| -> If + +# 9| If +#-----| -> x + +# 9| Binary +#-----| true -> String +#-----| false -> String + +# 9| x +#-----| -> 2 + +# 9| 2 +#-----| -> Binary + +# 10| MethodCall +#-----| exit -> exit m2 (abnormal) + +# 10| abort +#-----| -> MethodCall + +# 10| String +#-----| -> abort + +# 12| MethodCall +#-----| -> exit m2 (normal) + +# 12| puts +#-----| -> MethodCall + +# 12| String +#-----| -> puts + +ifs.rb: +# 1| x +#-----| -> If + +# 2| If +#-----| -> x + +# 2| Binary +#-----| true -> String +#-----| false -> Elsif + +# 2| x +#-----| -> 2 + +# 2| 2 +#-----| -> Binary + +# 3| MethodCall +#-----| -> exit m1 (normal) + +# 3| puts +#-----| -> MethodCall + +# 3| String +#-----| -> puts + +# 4| Elsif +#-----| -> x + +# 4| [false] Binary +#-----| false -> String + +# 4| [true] Binary +#-----| true -> String + +# 4| [false] Binary +#-----| false -> [false] Binary + +# 4| [true] Binary +#-----| true -> x + +# 4| Binary +#-----| false -> [false] Binary +#-----| true -> x + +# 4| x +#-----| -> 2 + +# 4| 2 +#-----| -> Binary + +# 4| Binary +#-----| false -> [false] Binary +#-----| true -> [true] Binary + +# 4| x +#-----| -> 0 + +# 4| 0 +#-----| -> Binary + +# 4| [false] Unary +#-----| false -> [false] Binary + +# 4| [true] Unary +#-----| true -> [true] Binary + +# 4| [false] ParenthesizedStatements +#-----| false -> [true] Unary + +# 4| [true] ParenthesizedStatements +#-----| true -> [false] Unary + +# 4| Binary +#-----| false -> [false] ParenthesizedStatements +#-----| true -> [true] ParenthesizedStatements + +# 4| x +#-----| -> 5 + +# 4| 5 +#-----| -> Binary + +# 5| MethodCall +#-----| -> exit m1 (normal) + +# 5| puts +#-----| -> MethodCall + +# 5| String +#-----| -> puts + +# 7| MethodCall +#-----| -> exit m1 (normal) + +# 7| puts +#-----| -> MethodCall + +# 7| String +#-----| -> puts + +# 11| b +#-----| -> If + +# 12| If +#-----| -> b + +# 12| b +#-----| true -> 0 +#-----| false -> 1 + +# 13| Return +#-----| return -> exit m2 (normal) + +# 13| 0 +#-----| -> Return + +# 15| Return +#-----| return -> exit m2 (normal) + +# 15| 1 +#-----| -> Return + +# 18| x +#-----| -> If + +# 19| If +#-----| -> x + +# 19| Binary +#-----| true -> x +#-----| false -> x + +# 19| x +#-----| -> 0 + +# 19| 0 +#-----| -> Binary + +# 20| Assignment +#-----| -> If + +# 20| x +#-----| -> Assignment + +# 20| Unary +#-----| -> x + +# 20| x +#-----| -> Unary + +# 21| If +#-----| -> x + +# 21| Binary +#-----| true -> x +#-----| false -> x + +# 21| x +#-----| -> 10 + +# 21| 10 +#-----| -> Binary + +# 22| Assignment +#-----| -> x + +# 22| x +#-----| -> Assignment + +# 22| Binary +#-----| -> x + +# 22| x +#-----| -> 1 + +# 22| 1 +#-----| -> Binary + +# 25| MethodCall +#-----| -> exit m3 (normal) + +# 25| puts +#-----| -> MethodCall + +# 25| x +#-----| -> puts + +# 28| b1 +#-----| -> b2 + +# 28| b2 +#-----| -> b3 + +# 28| b3 +#-----| -> Conditional + +# 29| Return +#-----| return -> exit m4 (normal) + +# 29| Conditional +#-----| -> Conditional + +# 29| ParenthesizedStatements +#-----| true -> String +#-----| false -> String + +# 29| Conditional +#-----| -> b1 + +# 29| b1 +#-----| true -> b2 +#-----| false -> b3 + +# 29| b2 +#-----| -> ParenthesizedStatements + +# 29| b3 +#-----| -> ParenthesizedStatements + +# 29| String +#-----| -> Return + +# 29| String +#-----| -> Return + +loops.rb: +# 1| x +#-----| -> While + +# 2| While +#-----| -> x + +# 2| Binary +#-----| true -> x +#-----| false -> exit m1 (normal) + +# 2| x +#-----| -> 0 + +# 2| 0 +#-----| -> Binary + +# 3| MethodCall +#-----| -> x + +# 3| puts +#-----| -> MethodCall + +# 3| x +#-----| -> puts + +# 4| OperatorAssignment +#-----| -> x + +# 4| x +#-----| -> 1 + +# 4| 1 +#-----| -> OperatorAssignment + +# 8| x +#-----| -> While + +# 9| While +#-----| -> x + +# 9| Binary +#-----| true -> x +#-----| false -> String + +# 9| x +#-----| -> 0 + +# 9| 0 +#-----| -> Binary + +# 10| MethodCall +#-----| -> x + +# 10| puts +#-----| -> MethodCall + +# 10| x +#-----| -> puts + +# 11| OperatorAssignment +#-----| -> If + +# 11| x +#-----| -> 1 + +# 11| 1 +#-----| -> OperatorAssignment + +# 12| If +#-----| -> x + +# 12| Binary +#-----| true -> Break +#-----| false -> Elsif + +# 12| x +#-----| -> 100 + +# 12| 100 +#-----| -> Binary + +# 13| Break +#-----| break -> String + +# 14| Elsif +#-----| -> x + +# 14| Binary +#-----| true -> Next +#-----| false -> Elsif + +# 14| x +#-----| -> 50 + +# 14| 50 +#-----| -> Binary + +# 15| Next +#-----| next -> x + +# 16| Elsif +#-----| -> x + +# 16| Binary +#-----| true -> Redo +#-----| false -> String + +# 16| x +#-----| -> 10 + +# 16| 10 +#-----| -> Binary + +# 17| Redo +#-----| redo -> x + +# 19| MethodCall +#-----| -> x + +# 19| puts +#-----| -> MethodCall + +# 19| String +#-----| -> puts + +# 21| MethodCall +#-----| -> exit m2 (normal) + +# 21| puts +#-----| -> MethodCall + +# 21| String +#-----| -> puts + +# 25| MethodCall +#-----| -> exit m3 (normal) + +# 25| Call +#-----| -> MethodCall + +# 25| Array +#-----| -> each + +# 25| 1 +#-----| -> 2 + +# 25| 2 +#-----| -> 3 + +# 25| 3 +#-----| -> Array + +# 25| each +#-----| -> Call + +# 25| x +#-----| -> x + +# 26| MethodCall +#-----| -> exit do block (normal) + +# 26| puts +#-----| -> MethodCall + +# 26| x +#-----| -> puts + +raise.rb: +# 1| Class +#-----| -> ExceptionA + +# 1| ExceptionA +#-----| -> Exception + +# 1| Superclass +#-----| -> Class + +# 1| Exception +#-----| -> Superclass + +# 4| Class +#-----| -> ExceptionB + +# 4| ExceptionB +#-----| -> Exception + +# 4| Superclass +#-----| -> exit top-level (normal) + +# 4| Exception +#-----| -> Superclass + +# 7| x +#-----| -> If + +# 8| If +#-----| -> x + +# 8| Binary +#-----| true -> String +#-----| false -> String + +# 8| x +#-----| -> 2 + +# 8| 2 +#-----| -> Binary + +# 9| MethodCall +#-----| raise -> exit m1 (abnormal) + +# 9| raise +#-----| -> MethodCall + +# 9| String +#-----| -> raise + +# 11| MethodCall +#-----| -> exit m1 (normal) + +# 11| puts +#-----| -> MethodCall + +# 11| String +#-----| -> puts + +# 14| b +#-----| -> If + +# 16| If +#-----| -> b + +# 16| b +#-----| true -> ExceptionA +#-----| false -> String + +# 17| MethodCall +#-----| raise -> Rescue + +# 17| raise +#-----| -> MethodCall + +# 17| ExceptionA +#-----| -> raise + +# 19| Rescue +#-----| -> ExceptionA + +# 19| ExceptionA +#-----| match -> String +#-----| raise -> exit m2 (abnormal) + +# 20| MethodCall +#-----| -> String + +# 20| puts +#-----| -> MethodCall + +# 20| String +#-----| -> puts + +# 22| MethodCall +#-----| -> exit m2 (normal) + +# 22| puts +#-----| -> MethodCall + +# 22| String +#-----| -> puts + +# 25| b +#-----| -> If + +# 27| If +#-----| -> b + +# 27| b +#-----| true -> ExceptionA +#-----| false -> String + +# 28| MethodCall +#-----| raise -> Rescue + +# 28| raise +#-----| -> MethodCall + +# 28| ExceptionA +#-----| -> raise + +# 30| Rescue +#-----| -> String + +# 31| MethodCall +#-----| -> String + +# 31| puts +#-----| -> MethodCall + +# 31| String +#-----| -> puts + +# 33| MethodCall +#-----| -> exit m3 (normal) + +# 33| puts +#-----| -> MethodCall + +# 33| String +#-----| -> puts + +# 36| b +#-----| -> If + +# 38| If +#-----| -> b + +# 38| b +#-----| true -> ExceptionA +#-----| false -> String + +# 39| MethodCall +#-----| raise -> Rescue + +# 39| raise +#-----| -> MethodCall + +# 39| ExceptionA +#-----| -> raise + +# 41| Rescue +#-----| -> e + +# 41| e +#-----| -> String + +# 42| MethodCall +#-----| -> String + +# 42| puts +#-----| -> MethodCall + +# 42| String +#-----| -> puts + +# 44| MethodCall +#-----| -> exit m4 (normal) + +# 44| puts +#-----| -> MethodCall + +# 44| String +#-----| -> puts + +# 47| b +#-----| -> If + +# 49| If +#-----| -> b + +# 49| b +#-----| true -> ExceptionA +#-----| false -> String + +# 50| MethodCall +#-----| raise -> Rescue + +# 50| raise +#-----| -> MethodCall + +# 50| ExceptionA +#-----| -> raise + +# 52| Rescue +#-----| -> e + +# 52| e +#-----| -> String + +# 54| MethodCall +#-----| -> exit m5 (normal) + +# 54| puts +#-----| -> MethodCall + +# 54| String +#-----| -> puts + +# 57| b +#-----| -> If + +# 59| If +#-----| -> b + +# 59| b +#-----| true -> ExceptionA +#-----| false -> String + +# 60| MethodCall +#-----| raise -> Rescue + +# 60| raise +#-----| -> MethodCall + +# 60| ExceptionA +#-----| -> raise + +# 62| Rescue +#-----| -> ExceptionA + +# 62| ExceptionA +#-----| no-match -> ExceptionB +#-----| match -> e + +# 62| ExceptionB +#-----| match -> e +#-----| raise -> exit m6 (abnormal) + +# 62| e +#-----| -> String + +# 63| MethodCall +#-----| -> String + +# 63| puts +#-----| -> MethodCall + +# 63| String +#-----| -> puts + +# 65| MethodCall +#-----| -> exit m6 (normal) + +# 65| puts +#-----| -> MethodCall + +# 65| String +#-----| -> puts + +# 68| x +#-----| -> If + +# 69| If +#-----| -> x + +# 69| Binary +#-----| true -> String +#-----| false -> Elsif + +# 69| x +#-----| -> 2 + +# 69| 2 +#-----| -> Binary + +# 70| MethodCall +#-----| raise -> [ensure: raise] Ensure + +# 70| raise +#-----| -> MethodCall + +# 70| String +#-----| -> raise + +# 71| Elsif +#-----| -> x + +# 71| Binary +#-----| true -> String +#-----| false -> String + +# 71| x +#-----| -> 0 + +# 71| 0 +#-----| -> Binary + +# 72| Return +#-----| return -> [ensure: return] Ensure + +# 72| String +#-----| -> Return + +# 74| MethodCall +#-----| -> Ensure + +# 74| puts +#-----| -> MethodCall + +# 74| String +#-----| -> puts + +# 75| Ensure +#-----| -> String + +# 75| [ensure: return] Ensure +#-----| -> [ensure: return] String + +# 75| [ensure: raise] Ensure +#-----| -> [ensure: raise] String + +# 76| MethodCall +#-----| -> exit m7 (normal) + +# 76| [ensure: return] MethodCall +#-----| return -> exit m7 (normal) + +# 76| [ensure: raise] MethodCall +#-----| raise -> exit m7 (abnormal) + +# 76| puts +#-----| -> MethodCall + +# 76| [ensure: return] puts +#-----| -> [ensure: return] MethodCall + +# 76| [ensure: raise] puts +#-----| -> [ensure: raise] MethodCall + +# 76| String +#-----| -> puts + +# 76| [ensure: return] String +#-----| -> [ensure: return] puts + +# 76| [ensure: raise] String +#-----| -> [ensure: raise] puts + +# 79| x +#-----| -> String + +# 80| MethodCall +#-----| -> If + +# 80| puts +#-----| -> MethodCall + +# 80| String +#-----| -> puts + +# 82| If +#-----| -> x + +# 82| Binary +#-----| true -> String +#-----| false -> Elsif + +# 82| x +#-----| -> 2 + +# 82| 2 +#-----| -> Binary + +# 83| MethodCall +#-----| raise -> [ensure: raise] Ensure + +# 83| raise +#-----| -> MethodCall + +# 83| String +#-----| -> raise + +# 84| Elsif +#-----| -> x + +# 84| Binary +#-----| true -> String +#-----| false -> String + +# 84| x +#-----| -> 0 + +# 84| 0 +#-----| -> Binary + +# 85| Return +#-----| return -> [ensure: return] Ensure + +# 85| String +#-----| -> Return + +# 87| MethodCall +#-----| -> Ensure + +# 87| puts +#-----| -> MethodCall + +# 87| String +#-----| -> puts + +# 88| Ensure +#-----| -> String + +# 88| [ensure: return] Ensure +#-----| -> [ensure: return] String + +# 88| [ensure: raise] Ensure +#-----| -> [ensure: raise] String + +# 89| MethodCall +#-----| -> String + +# 89| [ensure: return] MethodCall +#-----| return -> exit m8 (normal) + +# 89| [ensure: raise] MethodCall +#-----| raise -> exit m8 (abnormal) + +# 89| puts +#-----| -> MethodCall + +# 89| [ensure: return] puts +#-----| -> [ensure: return] MethodCall + +# 89| [ensure: raise] puts +#-----| -> [ensure: raise] MethodCall + +# 89| String +#-----| -> puts + +# 89| [ensure: return] String +#-----| -> [ensure: return] puts + +# 89| [ensure: raise] String +#-----| -> [ensure: raise] puts + +# 91| MethodCall +#-----| -> exit m8 (normal) + +# 91| puts +#-----| -> MethodCall + +# 91| String +#-----| -> puts + +# 94| x +#-----| -> b1 + +# 94| b1 +#-----| -> b2 + +# 94| b2 +#-----| -> String + +# 95| MethodCall +#-----| -> If + +# 95| puts +#-----| -> MethodCall + +# 95| String +#-----| -> puts + +# 97| If +#-----| -> x + +# 97| Binary +#-----| true -> String +#-----| false -> Elsif + +# 97| x +#-----| -> 2 + +# 97| 2 +#-----| -> Binary + +# 98| MethodCall +#-----| raise -> [ensure: raise] Ensure + +# 98| raise +#-----| -> MethodCall + +# 98| String +#-----| -> raise + +# 99| Elsif +#-----| -> x + +# 99| Binary +#-----| true -> String +#-----| false -> String + +# 99| x +#-----| -> 0 + +# 99| 0 +#-----| -> Binary + +# 100| Return +#-----| return -> [ensure: return] Ensure + +# 100| String +#-----| -> Return + +# 102| MethodCall +#-----| -> Ensure + +# 102| puts +#-----| -> MethodCall + +# 102| String +#-----| -> puts + +# 103| Ensure +#-----| -> String + +# 103| [ensure: return] Ensure +#-----| -> [ensure: return] String + +# 103| [ensure: raise] Ensure +#-----| -> [ensure: raise] String + +# 104| MethodCall +#-----| -> If + +# 104| [ensure: return] MethodCall +#-----| -> [ensure: return] If + +# 104| [ensure: raise] MethodCall +#-----| -> [ensure: raise] If + +# 104| puts +#-----| -> MethodCall + +# 104| [ensure: return] puts +#-----| -> [ensure: return] MethodCall + +# 104| [ensure: raise] puts +#-----| -> [ensure: raise] MethodCall + +# 104| String +#-----| -> puts + +# 104| [ensure: return] String +#-----| -> [ensure: return] puts + +# 104| [ensure: raise] String +#-----| -> [ensure: raise] puts + +# 106| If +#-----| -> b1 + +# 106| [ensure: return] If +#-----| -> [ensure: return] b1 + +# 106| [ensure: raise] If +#-----| -> [ensure: raise] b1 + +# 106| b1 +#-----| true -> String +#-----| false -> Ensure + +# 106| [ensure: return] b1 +#-----| true -> [ensure: return] String +#-----| false -> [ensure: return] Ensure + +# 106| [ensure: raise] b1 +#-----| true -> [ensure: raise] String +#-----| false -> [ensure: raise] Ensure + +# 107| MethodCall +#-----| raise -> [ensure(1): raise] Ensure + +# 107| [ensure: return] MethodCall +#-----| raise -> [ensure: return, ensure(1): raise] Ensure + +# 107| [ensure: raise] MethodCall +#-----| raise -> [ensure: raise, ensure(1): raise] Ensure + +# 107| raise +#-----| -> MethodCall + +# 107| [ensure: return] raise +#-----| -> [ensure: return] MethodCall + +# 107| [ensure: raise] raise +#-----| -> [ensure: raise] MethodCall + +# 107| String +#-----| -> raise + +# 107| [ensure: return] String +#-----| -> [ensure: return] raise + +# 107| [ensure: raise] String +#-----| -> [ensure: raise] raise + +# 109| Ensure +#-----| -> String + +# 109| [ensure(1): raise] Ensure +#-----| -> [ensure(1): raise] String + +# 109| [ensure: return] Ensure +#-----| -> [ensure: return] String + +# 109| [ensure: return, ensure(1): raise] Ensure +#-----| -> [ensure: return, ensure(1): raise] String + +# 109| [ensure: raise] Ensure +#-----| -> [ensure: raise] String + +# 109| [ensure: raise, ensure(1): raise] Ensure +#-----| -> [ensure: raise, ensure(1): raise] String + +# 110| MethodCall +#-----| -> String + +# 110| [ensure(1): raise] MethodCall +#-----| raise -> [ensure: raise] Ensure + +# 110| [ensure: return] MethodCall +#-----| return -> [ensure: return] Ensure + +# 110| [ensure: return, ensure(1): raise] MethodCall +#-----| raise -> [ensure: raise] Ensure + +# 110| [ensure: raise] MethodCall +#-----| raise -> [ensure: raise] Ensure + +# 110| [ensure: raise, ensure(1): raise] MethodCall +#-----| raise -> [ensure: raise] Ensure + +# 110| puts +#-----| -> MethodCall + +# 110| [ensure(1): raise] puts +#-----| -> [ensure(1): raise] MethodCall + +# 110| [ensure: return] puts +#-----| -> [ensure: return] MethodCall + +# 110| [ensure: return, ensure(1): raise] puts +#-----| -> [ensure: return, ensure(1): raise] MethodCall + +# 110| [ensure: raise] puts +#-----| -> [ensure: raise] MethodCall + +# 110| [ensure: raise, ensure(1): raise] puts +#-----| -> [ensure: raise, ensure(1): raise] MethodCall + +# 110| String +#-----| -> puts + +# 110| [ensure(1): raise] String +#-----| -> [ensure(1): raise] puts + +# 110| [ensure: return] String +#-----| -> [ensure: return] puts + +# 110| [ensure: return, ensure(1): raise] String +#-----| -> [ensure: return, ensure(1): raise] puts + +# 110| [ensure: raise] String +#-----| -> [ensure: raise] puts + +# 110| [ensure: raise, ensure(1): raise] String +#-----| -> [ensure: raise, ensure(1): raise] puts + +# 113| MethodCall +#-----| -> Ensure + +# 113| puts +#-----| -> MethodCall + +# 113| String +#-----| -> puts + +# 114| Ensure +#-----| -> String + +# 114| [ensure: return] Ensure +#-----| -> [ensure: return] String + +# 114| [ensure: raise] Ensure +#-----| -> [ensure: raise] String + +# 115| MethodCall +#-----| -> If + +# 115| [ensure: return] MethodCall +#-----| -> [ensure: return] If + +# 115| [ensure: raise] MethodCall +#-----| -> [ensure: raise] If + +# 115| puts +#-----| -> MethodCall + +# 115| [ensure: return] puts +#-----| -> [ensure: return] MethodCall + +# 115| [ensure: raise] puts +#-----| -> [ensure: raise] MethodCall + +# 115| String +#-----| -> puts + +# 115| [ensure: return] String +#-----| -> [ensure: return] puts + +# 115| [ensure: raise] String +#-----| -> [ensure: raise] puts + +# 116| If +#-----| -> b2 + +# 116| [ensure: return] If +#-----| -> [ensure: return] b2 + +# 116| [ensure: raise] If +#-----| -> [ensure: raise] b2 + +# 116| b2 +#-----| true -> String +#-----| false -> exit m9 (normal) + +# 116| [ensure: return] b2 +#-----| true -> [ensure: return] String +#-----| return -> exit m9 (normal) + +# 116| [ensure: raise] b2 +#-----| true -> [ensure: raise] String +#-----| raise -> exit m9 (abnormal) + +# 117| MethodCall +#-----| raise -> exit m9 (abnormal) + +# 117| [ensure: return] MethodCall +#-----| raise -> exit m9 (abnormal) + +# 117| [ensure: raise] MethodCall +#-----| raise -> exit m9 (abnormal) + +# 117| raise +#-----| -> MethodCall + +# 117| [ensure: return] raise +#-----| -> [ensure: return] MethodCall + +# 117| [ensure: raise] raise +#-----| -> [ensure: raise] MethodCall + +# 117| String +#-----| -> raise + +# 117| [ensure: return] String +#-----| -> [ensure: return] raise + +# 117| [ensure: raise] String +#-----| -> [ensure: raise] raise + +# 121| OptionalParameter +#-----| no-match -> String +#-----| match -> Ensure + +# 121| MethodCall +#-----| raise -> exit m10 (abnormal) + +# 121| raise +#-----| -> MethodCall + +# 121| String +#-----| -> raise + +# 122| Ensure +#-----| -> String + +# 123| MethodCall +#-----| -> exit m10 (normal) + +# 123| puts +#-----| -> MethodCall + +# 123| String +#-----| -> puts + +break_ensure.rb: +# 1| exit m1 + +# 13| exit m2 + +# 27| exit m3 + +case.rb: +# 1| exit if_in_case + +cfg.rb: +# 1| exit top-level + +# 15| exit BEGIN block + +# 19| exit END block + +# 25| exit block + +# 29| exit block + +# 63| exit pattern + +# 69| exit print + +# 101| exit parameters + +# 120| exit lambda + +# 120| exit block + +# 142| exit print + +# 149| exit method + +# 153| exit two_parameters + +# 189| exit block + +exit.rb: +# 1| exit m1 + +# 8| exit m2 + +ifs.rb: +# 1| exit m1 + +# 11| exit m2 + +# 18| exit m3 + +# 28| exit m4 + +loops.rb: +# 1| exit m1 + +# 8| exit m2 + +# 24| exit m3 + +# 25| exit do block + +raise.rb: +# 1| exit top-level + +# 7| exit m1 + +# 14| exit m2 + +# 25| exit m3 + +# 36| exit m4 + +# 47| exit m5 + +# 57| exit m6 + +# 68| exit m7 + +# 79| exit m8 + +# 94| exit m9 + +# 121| exit m10 + +break_ensure.rb: +# 1| exit m1 (normal) +#-----| -> exit m1 + +# 13| exit m2 (normal) +#-----| -> exit m2 + +# 27| exit m3 (normal) +#-----| -> exit m3 + +case.rb: +# 1| exit if_in_case (normal) +#-----| -> exit if_in_case + +cfg.rb: +# 1| exit top-level (normal) +#-----| -> exit top-level + +# 15| exit BEGIN block (normal) +#-----| -> exit BEGIN block + +# 19| exit END block (normal) +#-----| -> exit END block + +# 25| exit block (normal) +#-----| -> exit block + +# 29| exit block (normal) +#-----| -> exit block + +# 63| exit pattern (normal) +#-----| -> exit pattern + +# 69| exit print (normal) +#-----| -> exit print + +# 101| exit parameters (normal) +#-----| -> exit parameters + +# 120| exit lambda (normal) +#-----| -> exit lambda + +# 120| exit block (normal) +#-----| -> exit block + +# 142| exit print (normal) +#-----| -> exit print + +# 149| exit method (normal) +#-----| -> exit method + +# 153| exit two_parameters (normal) +#-----| -> exit two_parameters + +# 189| exit block (normal) +#-----| -> exit block + +exit.rb: +# 1| exit m1 (abnormal) +#-----| -> exit m1 + +# 1| exit m1 (normal) +#-----| -> exit m1 + +# 8| exit m2 (abnormal) +#-----| -> exit m2 + +# 8| exit m2 (normal) +#-----| -> exit m2 + +ifs.rb: +# 1| exit m1 (normal) +#-----| -> exit m1 + +# 11| exit m2 (normal) +#-----| -> exit m2 + +# 18| exit m3 (normal) +#-----| -> exit m3 + +# 28| exit m4 (normal) +#-----| -> exit m4 + +loops.rb: +# 1| exit m1 (normal) +#-----| -> exit m1 + +# 8| exit m2 (normal) +#-----| -> exit m2 + +# 24| exit m3 (normal) +#-----| -> exit m3 + +# 25| exit do block (normal) +#-----| -> exit do block + +raise.rb: +# 1| exit top-level (normal) +#-----| -> exit top-level + +# 7| exit m1 (abnormal) +#-----| -> exit m1 + +# 7| exit m1 (normal) +#-----| -> exit m1 + +# 14| exit m2 (abnormal) +#-----| -> exit m2 + +# 14| exit m2 (normal) +#-----| -> exit m2 + +# 25| exit m3 (normal) +#-----| -> exit m3 + +# 36| exit m4 (normal) +#-----| -> exit m4 + +# 47| exit m5 (normal) +#-----| -> exit m5 + +# 57| exit m6 (abnormal) +#-----| -> exit m6 + +# 57| exit m6 (normal) +#-----| -> exit m6 + +# 68| exit m7 (abnormal) +#-----| -> exit m7 + +# 68| exit m7 (normal) +#-----| -> exit m7 + +# 79| exit m8 (abnormal) +#-----| -> exit m8 + +# 79| exit m8 (normal) +#-----| -> exit m8 + +# 94| exit m9 (abnormal) +#-----| -> exit m9 + +# 94| exit m9 (normal) +#-----| -> exit m9 + +# 121| exit m10 (abnormal) +#-----| -> exit m10 + +# 121| exit m10 (normal) +#-----| -> exit m10 From ec4ead2117fbee3f88edfa17ac98b7ba0278754d Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 14 Dec 2020 14:53:35 +0100 Subject: [PATCH 7/8] Apply suggestions from code review Co-authored-by: Arthur Baars --- .../internal/ControlFlowGraphImpl.qll | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll b/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll index 37658c3d64b..b37af98d2c6 100644 --- a/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll +++ b/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll @@ -377,12 +377,9 @@ module Trees { private class ClassTree extends RescueEnsureBlockTree, Class { final override AstNode getChildNode(int i, boolean rescuable) { - rescuable = true and - ( - result = this.getName() and i = 0 - or - result = this.getChild(i - 1) - ) + result = this.getName() and i = 0 and rescuable = false + or + result = this.getChild(i - 1) and rescuable = true } } @@ -824,12 +821,9 @@ module Trees { private class ModuleTree extends RescueEnsureBlockTree, Module { final override AstNode getChildNode(int i, boolean rescuable) { - rescuable = true and - ( - result = this.getName() and i = 0 - or - result = this.getChild(i - 1) - ) + result = this.getName() and i = 0 and rescuable = false + or + result = this.getChild(i - 1) and rescuable = true } } @@ -1229,7 +1223,7 @@ module Trees { final override AstNode getChildNode(int i, boolean rescuable) { result = this.getObject() and i = 0 and - rescuable = true // TODO: CHECK THIS + rescuable = false or result = this.getParameters() and i = 1 and From a76e6848c73433f27cb22d6f0560c9cde364121d Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 14 Dec 2020 15:09:17 +0100 Subject: [PATCH 8/8] CFG: Address more review comments --- .../controlflow/internal/Completion.qll | 19 +-- .../internal/ControlFlowGraphImpl.qll | 142 ++++++++---------- .../controlflow/internal/NonReturning.qll | 2 +- .../controlflow/graph/Cfg.expected | 94 +++++++++++- .../library-tests/controlflow/graph/raise.rb | 18 ++- 5 files changed, 176 insertions(+), 99 deletions(-) diff --git a/ql/src/codeql_ruby/controlflow/internal/Completion.qll b/ql/src/codeql_ruby/controlflow/internal/Completion.qll index 3b86799b8c9..ebd84634ea1 100644 --- a/ql/src/codeql_ruby/controlflow/internal/Completion.qll +++ b/ql/src/codeql_ruby/controlflow/internal/Completion.qll @@ -222,8 +222,13 @@ class SimpleCompletion extends NonNestedNormalCompletion, TSimpleCompletion { * completion (`EmptinessCompletion`), or a matching completion (`MatchingCompletion`). */ abstract class ConditionalCompletion extends NonNestedNormalCompletion { + boolean value; + + bindingset[value] + ConditionalCompletion() { any() } + /** Gets the Boolean value of this conditional completion. */ - abstract boolean getValue(); + final boolean getValue() { result = value } } /** @@ -231,12 +236,8 @@ abstract class ConditionalCompletion extends NonNestedNormalCompletion { * with a Boolean value. */ class BooleanCompletion extends ConditionalCompletion, TBooleanCompletion { - private boolean value; - BooleanCompletion() { this = TBooleanCompletion(value) } - override boolean getValue() { result = value } - /** Gets the dual Boolean completion. */ BooleanCompletion getDual() { result = TBooleanCompletion(value.booleanNot()) } @@ -260,12 +261,8 @@ class FalseCompletion extends BooleanCompletion { * a test in a `for in` statement. */ class EmptinessCompletion extends ConditionalCompletion, TEmptinessCompletion { - private boolean value; - EmptinessCompletion() { this = TEmptinessCompletion(value) } - override boolean getValue() { result = value } - override EmptinessSuccessor getAMatchingSuccessorType() { result.getValue() = value } override string toString() { if value = true then result = "empty" else result = "non-empty" } @@ -276,12 +273,8 @@ class EmptinessCompletion extends ConditionalCompletion, TEmptinessCompletion { * a test in a `rescue` statement. */ class MatchingCompletion extends ConditionalCompletion, TMatchingCompletion { - private boolean value; - MatchingCompletion() { this = TMatchingCompletion(value) } - override boolean getValue() { result = value } - override MatchingSuccessor getAMatchingSuccessorType() { result.getValue() = value } override string toString() { if value = true then result = "match" else result = "no-match" } diff --git a/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll b/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll index b37af98d2c6..66199da4003 100644 --- a/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll +++ b/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll @@ -476,41 +476,13 @@ module Trees { private class ExceptionsTree extends PreOrderTree, Exceptions { final override predicate propagatesAbnormal(AstNode child) { none() } - /** Holds if this exception filter belongs to a final `rescue` block. */ - pragma[noinline] - private predicate inLastRescue() { - exists(RescueEnsureBlockTree block, Rescue rescue, int i | - rescue = block.getRescue(i) and - this = rescue.getExceptions() and - not exists(block.getRescue(i + 1)) - ) - } - final override predicate last(AstNode last, Completion c) { last(this.getChild(_), last, c) and c.(MatchingCompletion).getValue() = true or - exists(int lst, Completion c0 | - last(this.getChild(lst), last, c0) and + exists(int lst | + last(this.getChild(lst), last, c) and not exists(this.getChild(lst + 1)) - | - // The last exception filter in a last `rescue` block propagates - // the exception when it doesn't match - this.inLastRescue() and - c = - any(NestedEnsureCompletion nec | - nec.getOuterCompletion() instanceof RaiseCompletion and - nec.getInnerCompletion() = c0 and - c0.(MatchingCompletion).getValue() = false and - nec.getNestLevel() = 0 - ) - or - c = c0 and - ( - not this.inLastRescue() - or - not c0.(MatchingCompletion).getValue() = false - ) ) } @@ -889,23 +861,36 @@ module Trees { private class RescueTree extends PreOrderTree, Rescue { final override predicate propagatesAbnormal(AstNode child) { child = this.getExceptions() } - final override predicate last(AstNode last, Completion c) { - last(this.getExceptions(), last, c) and - c.(MatchingCompletion).getValue() = false - or + predicate lastMatch(AstNode last, Completion c) { last(this.getBody(), last, c) or - not exists(this.getExceptions()) and not exists(this.getBody()) and ( last(this.getVariable(), last, c) or not exists(this.getVariable()) and - last = this and - c.isValidFor(this) + ( + last(this.getExceptions(), last, c) and + c.(MatchingCompletion).getValue() = true + or + not exists(this.getExceptions()) and + last = this and + c.isValidFor(this) + ) ) } + predicate lastNoMatch(AstNode last, Completion c) { + last(this.getExceptions(), last, c) and + c.(MatchingCompletion).getValue() = false + } + + final override predicate last(AstNode last, Completion c) { + this.lastNoMatch(last, c) + or + this.lastMatch(last, c) + } + final override predicate succ(AstNode pred, AstNode succ, Completion c) { exists(AstNode next | pred = this and @@ -983,7 +968,11 @@ module Trees { final private predicate hasEnsure() { exists(this.getEnsure()) } - final override predicate propagatesAbnormal(AstNode child) { child = this.getEnsure() } + final override predicate propagatesAbnormal(AstNode child) { + child = this.getEnsure() + or + child = this.getBodyChild(_, false) + } /** * Gets a descendant that belongs to the `ensure` block of this block, if any. @@ -1045,37 +1034,39 @@ module Trees { */ pragma[nomagic] private AstNode getAnEnsurePredecessor(Completion c, boolean ensurable) { - exists(boolean ensurable0 | - // Exit completions ignore the `ensure` block (TODO: CHECK THIS) - if c instanceof ExitCompletion then ensurable = false else ensurable = ensurable0 - | - this.lastBody(result, c, ensurable0) and - ( - // Any non-throw completion will always continue directly to the `ensure` block, - // unless there is an `else` block - not c instanceof RaiseCompletion and - not exists(this.getElse()) - or - // Any completion will continue to the `ensure` block when there are no `rescue` - // blocks - not exists(this.getRescue(_)) - ) + this.lastBody(result, c, ensurable) and + ( + // Any non-throw completion will always continue directly to the `ensure` block, + // unless there is an `else` block + not c instanceof RaiseCompletion and + not exists(this.getElse()) or - // Last element from any of the `rescue` blocks continues to the `ensure` block - last(this.getRescue(_).getBody(), result, c) and - ensurable0 = true - or - // Last element of last `rescue` block continues to the `ensure` block - exists(int lst | - last(this.getRescue(lst), result, c) and - not exists(this.getRescue(lst + 1)) and - ensurable0 = true - ) - or - // Last element of `else` block continues to the `ensure` block - last(this.getElse(), result, c) and - ensurable0 = true + // Any completion will continue to the `ensure` block when there are no `rescue` + // blocks + not exists(this.getRescue(_)) ) + or + // Last element from any matching `rescue` block continues to the `ensure` block + this.getRescue(_).(RescueTree).lastMatch(result, c) and + ensurable = true + or + // If the last `rescue` block does not match, continue to the `ensure` block + exists(int lst, MatchingCompletion mc | + this.getRescue(lst).(RescueTree).lastNoMatch(result, mc) and + mc.getValue() = false and + not exists(this.getRescue(lst + 1)) and + c = + any(NestedEnsureCompletion nec | + nec.getOuterCompletion() instanceof RaiseCompletion and + nec.getInnerCompletion() = mc and + nec.getNestLevel() = 0 + ) and + ensurable = true + ) + or + // Last element of `else` block continues to the `ensure` block + last(this.getElse(), result, c) and + ensurable = true } pragma[nomagic] @@ -1129,19 +1120,10 @@ module Trees { first(this.getRescue(0), succ) and c instanceof RaiseCompletion or - exists(Rescue rescue, int i | rescue = this.getRescue(i) | - pred = rescue and - last(this.getRescue(i), rescue, c) and - ( - // Flow from one `rescue` clause to the next when there is no match - first(this.getRescue(i + 1), succ) and - c.(MatchingCompletion).getValue() = false - or - // Flow from last `rescue` clause to `ensure` block - not exists(this.getRescue(i + 1)) and - first(this.getEnsure(), succ) and - c.getInnerCompletion().(MatchingCompletion).getValue() = false - ) + // Flow from one `rescue` clause to the next when there is no match + exists(RescueTree rescue, int i | rescue = this.getRescue(i) | + rescue.lastNoMatch(pred, c) and + first(this.getRescue(i + 1), succ) ) or // Flow from body to `else` block when no exception diff --git a/ql/src/codeql_ruby/controlflow/internal/NonReturning.qll b/ql/src/codeql_ruby/controlflow/internal/NonReturning.qll index dc065750c5f..acba5a0ed26 100644 --- a/ql/src/codeql_ruby/controlflow/internal/NonReturning.qll +++ b/ql/src/codeql_ruby/controlflow/internal/NonReturning.qll @@ -18,5 +18,5 @@ private class RaiseCall extends NonReturningCall, MethodCall { private class ExitCall extends NonReturningCall, MethodCall { ExitCall() { this.getMethod().toString() in ["abort", "exit"] } - override ExitCompletion getACompletion() { any() } + override ExitCompletion getACompletion() { not result instanceof NestedCompletion } } diff --git a/ql/test/library-tests/controlflow/graph/Cfg.expected b/ql/test/library-tests/controlflow/graph/Cfg.expected index d071817a5ef..5426d01dd9a 100644 --- a/ql/test/library-tests/controlflow/graph/Cfg.expected +++ b/ql/test/library-tests/controlflow/graph/Cfg.expected @@ -125,6 +125,9 @@ raise.rb: # 121| enter m10 #-----| -> OptionalParameter +# 128| enter m11 +#-----| -> b + break_ensure.rb: # 1| elements #-----| -> elements @@ -3059,16 +3062,91 @@ raise.rb: # 121| String #-----| -> raise -# 122| Ensure +# 124| Ensure #-----| -> String -# 123| MethodCall +# 125| MethodCall #-----| -> exit m10 (normal) -# 123| puts +# 125| puts #-----| -> MethodCall -# 123| String +# 125| String +#-----| -> puts + +# 128| b +#-----| -> If + +# 130| If +#-----| -> b + +# 130| b +#-----| true -> ExceptionA +#-----| false -> Ensure + +# 131| MethodCall +#-----| raise -> Rescue + +# 131| raise +#-----| -> MethodCall + +# 131| ExceptionA +#-----| -> raise + +# 133| Rescue +#-----| -> ExceptionA + +# 133| ExceptionA +#-----| no-match -> Rescue +#-----| match -> Ensure + +# 134| Rescue +#-----| -> ExceptionB + +# 134| ExceptionB +#-----| match -> String +#-----| raise -> [ensure: raise] Ensure + +# 135| MethodCall +#-----| -> Ensure + +# 135| puts +#-----| -> MethodCall + +# 135| String +#-----| -> puts + +# 136| Ensure +#-----| -> String + +# 136| [ensure: raise] Ensure +#-----| -> [ensure: raise] String + +# 137| MethodCall +#-----| -> String + +# 137| [ensure: raise] MethodCall +#-----| raise -> exit m11 (abnormal) + +# 137| puts +#-----| -> MethodCall + +# 137| [ensure: raise] puts +#-----| -> [ensure: raise] MethodCall + +# 137| String +#-----| -> puts + +# 137| [ensure: raise] String +#-----| -> [ensure: raise] puts + +# 139| MethodCall +#-----| -> exit m11 (normal) + +# 139| puts +#-----| -> MethodCall + +# 139| String #-----| -> puts break_ensure.rb: @@ -3156,6 +3234,8 @@ raise.rb: # 121| exit m10 +# 128| exit m11 + break_ensure.rb: # 1| exit m1 (normal) #-----| -> exit m1 @@ -3306,3 +3386,9 @@ raise.rb: # 121| exit m10 (normal) #-----| -> exit m10 + +# 128| exit m11 (abnormal) +#-----| -> exit m11 + +# 128| exit m11 (normal) +#-----| -> exit m11 diff --git a/ql/test/library-tests/controlflow/graph/raise.rb b/ql/test/library-tests/controlflow/graph/raise.rb index d9db0cc2821..cf775077594 100644 --- a/ql/test/library-tests/controlflow/graph/raise.rb +++ b/ql/test/library-tests/controlflow/graph/raise.rb @@ -119,6 +119,22 @@ ensure end def m10(p = (raise "Exception")) +rescue + puts "Will not get executed if p is not supplied" ensure puts "Will not get executed if p is not supplied" -end \ No newline at end of file +end + +def m11 b + begin + if b + raise ExceptionA + end + rescue ExceptionA + rescue ExceptionB + puts "ExceptionB" + ensure + puts "Ensure" + end + puts "End m5" +end