diff --git a/ruby/ql/src/utils/modeleditor/FrameworkModeAccessPaths.ql b/ruby/ql/src/utils/modeleditor/FrameworkModeAccessPaths.ql new file mode 100644 index 000000000000..d992f1c46f8a --- /dev/null +++ b/ruby/ql/src/utils/modeleditor/FrameworkModeAccessPaths.ql @@ -0,0 +1,89 @@ +/** + * @name Fetch a subset of valid access paths of input and output parameters of a method (framework mode). + * @description A list of access paths for input and output parameters of a method. Excludes test and generated code. + * @kind table + * @id ruby/utils/modeleditor/framework-mode-access-paths + * @tags modeleditor access-paths framework-mode + */ + +private import ruby +private import codeql.ruby.AST +private import codeql.ruby.ApiGraphs +private import queries.modeling.internal.Util as Util + +predicate simpleParameters(string type, string path, string value, DataFlow::Node node) { + exists(DataFlow::MethodNode methodNode, DataFlow::ParameterNode paramNode | + methodNode.getLocation().getFile() instanceof Util::RelevantFile and + ( + // Check that this parameter belongs to this method + // Block parameter explicitly excluded because it's already included + // as part of the blockArguments predicate + paramNode = Util::getAnyParameter(methodNode) and + paramNode != methodNode.getBlockParameter() + ) + | + Util::pathToMethod(methodNode, type, path) and + value = Util::getArgumentPath(paramNode) and + node = paramNode + ) +} + +predicate blockArguments(string type, string path, string value, DataFlow::Node node) { + exists(DataFlow::MethodNode methodNode, DataFlow::CallNode callNode | + methodNode.getLocation().getFile() instanceof Util::RelevantFile and + callNode = methodNode.getABlockCall() + | + ( + exists(DataFlow::VariableAccessNode argNode, int i | + argNode = callNode.getPositionalArgument(i) + | + value = "Argument[block].Parameter[" + i + "]" and + node = argNode + ) + or + exists(DataFlow::ExprNode argNode, string keyword | + argNode = callNode.getKeywordArgument(keyword) + | + value = "Argument[block].Parameter[" + keyword + ":]" and + node = argNode + ) + or + value = "Argument[block]" and + node = callNode + ) and + Util::pathToMethod(methodNode, type, path) + ) +} + +predicate returnValue(string type, string path, string value, DataFlow::Node node) { + exists(DataFlow::MethodNode methodNode, DataFlow::Node returnNode | + methodNode.getLocation().getFile() instanceof Util::RelevantFile and + returnNode = methodNode.getAReturnNode() + | + Util::pathToMethod(methodNode, type, path) and + value = "ReturnValue" and + node = returnNode + ) +} + +predicate inputAccessPaths( + string type, string path, string value, DataFlow::Node node, string defType +) { + simpleParameters(type, path, value, node) and defType = "parameter" + or + blockArguments(type, path, value, node) and defType = "parameter" +} + +predicate outputAccessPaths( + string type, string path, string value, DataFlow::Node node, string defType +) { + simpleParameters(type, path, value, node) and defType = "parameter" + or + blockArguments(type, path, value, node) and defType = "parameter" + or + returnValue(type, path, value, node) and defType = "return" +} + +query predicate input = inputAccessPaths/5; + +query predicate output = outputAccessPaths/5; diff --git a/ruby/ql/test/query-tests/utils/modeleditor/FrameworkModeAccessPaths.expected b/ruby/ql/test/query-tests/utils/modeleditor/FrameworkModeAccessPaths.expected new file mode 100644 index 000000000000..6951f1a23226 --- /dev/null +++ b/ruby/ql/test/query-tests/utils/modeleditor/FrameworkModeAccessPaths.expected @@ -0,0 +1,75 @@ +input +| A | Method[bar] | Argument[0] | lib/mylib.rb:13:11:13:11 | x | parameter | +| A | Method[bar] | Argument[self] | lib/mylib.rb:13:3:14:5 | self in bar | parameter | +| A | Method[foo] | Argument[0] | lib/mylib.rb:7:11:7:11 | x | parameter | +| A | Method[foo] | Argument[1] | lib/mylib.rb:7:14:7:14 | y | parameter | +| A | Method[foo] | Argument[2] | lib/mylib.rb:7:17:7:20 | key1 | parameter | +| A | Method[foo] | Argument[block] | lib/mylib.rb:8:5:8:32 | call to call | parameter | +| A | Method[foo] | Argument[block] | lib/mylib.rb:10:5:10:26 | yield ... | parameter | +| A | Method[foo] | Argument[block].Parameter[0] | lib/mylib.rb:8:16:8:16 | x | parameter | +| A | Method[foo] | Argument[block].Parameter[0] | lib/mylib.rb:10:11:10:11 | x | parameter | +| A | Method[foo] | Argument[block].Parameter[1] | lib/mylib.rb:8:19:8:19 | y | parameter | +| A | Method[foo] | Argument[block].Parameter[1] | lib/mylib.rb:10:14:10:14 | y | parameter | +| A | Method[foo] | Argument[block].Parameter[key2:] | lib/mylib.rb:8:28:8:31 | key1 | parameter | +| A | Method[foo] | Argument[block].Parameter[key2:] | lib/mylib.rb:10:23:10:26 | key1 | parameter | +| A | Method[foo] | Argument[key1:] | lib/mylib.rb:7:17:7:20 | key1 | parameter | +| A | Method[foo] | Argument[self] | lib/mylib.rb:7:3:11:5 | self in foo | parameter | +| A! | Method[new] | Argument[0] | lib/mylib.rb:4:18:4:18 | x | parameter | +| A! | Method[new] | Argument[1] | lib/mylib.rb:4:21:4:21 | y | parameter | +| A! | Method[new] | Argument[self] | lib/mylib.rb:4:3:5:5 | self in initialize | parameter | +| A! | Method[self_foo] | Argument[0] | lib/mylib.rb:16:21:16:21 | x | parameter | +| A! | Method[self_foo] | Argument[1] | lib/mylib.rb:16:24:16:24 | y | parameter | +| A! | Method[self_foo] | Argument[self] | lib/mylib.rb:16:3:17:5 | self in self_foo | parameter | +| A::ANested | Method[foo] | Argument[0] | lib/mylib.rb:25:13:25:13 | x | parameter | +| A::ANested | Method[foo] | Argument[1] | lib/mylib.rb:25:16:25:16 | y | parameter | +| A::ANested | Method[foo] | Argument[self] | lib/mylib.rb:25:5:26:7 | self in foo | parameter | +| B | Method[foo] | Argument[0] | lib/other.rb:6:11:6:11 | x | parameter | +| B | Method[foo] | Argument[1] | lib/other.rb:6:14:6:14 | y | parameter | +| B | Method[foo] | Argument[self] | lib/other.rb:6:3:7:5 | self in foo | parameter | +| M1 | Method[foo] | Argument[0] | lib/module.rb:2:11:2:11 | x | parameter | +| M1 | Method[foo] | Argument[1] | lib/module.rb:2:14:2:14 | y | parameter | +| M1 | Method[foo] | Argument[self] | lib/module.rb:2:3:3:5 | self in foo | parameter | +| M1! | Method[self_foo] | Argument[0] | lib/module.rb:5:21:5:21 | x | parameter | +| M1! | Method[self_foo] | Argument[1] | lib/module.rb:5:24:5:24 | y | parameter | +| M1! | Method[self_foo] | Argument[self] | lib/module.rb:5:3:6:5 | self in self_foo | parameter | +| OtherLib::A | Method[foo] | Argument[0] | other_lib/lib/other_gem.rb:3:17:3:17 | x | parameter | +| OtherLib::A | Method[foo] | Argument[1] | other_lib/lib/other_gem.rb:3:20:3:20 | y | parameter | +| OtherLib::A | Method[foo] | Argument[self] | other_lib/lib/other_gem.rb:3:9:4:11 | self in foo | parameter | +output +| A | Method[bar] | Argument[0] | lib/mylib.rb:13:11:13:11 | x | parameter | +| A | Method[bar] | Argument[self] | lib/mylib.rb:13:3:14:5 | self in bar | parameter | +| A | Method[foo] | Argument[0] | lib/mylib.rb:7:11:7:11 | x | parameter | +| A | Method[foo] | Argument[1] | lib/mylib.rb:7:14:7:14 | y | parameter | +| A | Method[foo] | Argument[2] | lib/mylib.rb:7:17:7:20 | key1 | parameter | +| A | Method[foo] | Argument[block] | lib/mylib.rb:8:5:8:32 | call to call | parameter | +| A | Method[foo] | Argument[block] | lib/mylib.rb:10:5:10:26 | yield ... | parameter | +| A | Method[foo] | Argument[block].Parameter[0] | lib/mylib.rb:8:16:8:16 | x | parameter | +| A | Method[foo] | Argument[block].Parameter[0] | lib/mylib.rb:10:11:10:11 | x | parameter | +| A | Method[foo] | Argument[block].Parameter[1] | lib/mylib.rb:8:19:8:19 | y | parameter | +| A | Method[foo] | Argument[block].Parameter[1] | lib/mylib.rb:10:14:10:14 | y | parameter | +| A | Method[foo] | Argument[block].Parameter[key2:] | lib/mylib.rb:8:28:8:31 | key1 | parameter | +| A | Method[foo] | Argument[block].Parameter[key2:] | lib/mylib.rb:10:23:10:26 | key1 | parameter | +| A | Method[foo] | Argument[key1:] | lib/mylib.rb:7:17:7:20 | key1 | parameter | +| A | Method[foo] | Argument[self] | lib/mylib.rb:7:3:11:5 | self in foo | parameter | +| A | Method[foo] | ReturnValue | lib/mylib.rb:10:5:10:26 | yield ... | return | +| A! | Method[new] | Argument[0] | lib/mylib.rb:4:18:4:18 | x | parameter | +| A! | Method[new] | Argument[1] | lib/mylib.rb:4:21:4:21 | y | parameter | +| A! | Method[new] | Argument[self] | lib/mylib.rb:4:3:5:5 | self in initialize | parameter | +| A! | Method[self_foo] | Argument[0] | lib/mylib.rb:16:21:16:21 | x | parameter | +| A! | Method[self_foo] | Argument[1] | lib/mylib.rb:16:24:16:24 | y | parameter | +| A! | Method[self_foo] | Argument[self] | lib/mylib.rb:16:3:17:5 | self in self_foo | parameter | +| A::ANested | Method[foo] | Argument[0] | lib/mylib.rb:25:13:25:13 | x | parameter | +| A::ANested | Method[foo] | Argument[1] | lib/mylib.rb:25:16:25:16 | y | parameter | +| A::ANested | Method[foo] | Argument[self] | lib/mylib.rb:25:5:26:7 | self in foo | parameter | +| B | Method[foo] | Argument[0] | lib/other.rb:6:11:6:11 | x | parameter | +| B | Method[foo] | Argument[1] | lib/other.rb:6:14:6:14 | y | parameter | +| B | Method[foo] | Argument[self] | lib/other.rb:6:3:7:5 | self in foo | parameter | +| M1 | Method[foo] | Argument[0] | lib/module.rb:2:11:2:11 | x | parameter | +| M1 | Method[foo] | Argument[1] | lib/module.rb:2:14:2:14 | y | parameter | +| M1 | Method[foo] | Argument[self] | lib/module.rb:2:3:3:5 | self in foo | parameter | +| M1! | Method[self_foo] | Argument[0] | lib/module.rb:5:21:5:21 | x | parameter | +| M1! | Method[self_foo] | Argument[1] | lib/module.rb:5:24:5:24 | y | parameter | +| M1! | Method[self_foo] | Argument[self] | lib/module.rb:5:3:6:5 | self in self_foo | parameter | +| OtherLib::A | Method[foo] | Argument[0] | other_lib/lib/other_gem.rb:3:17:3:17 | x | parameter | +| OtherLib::A | Method[foo] | Argument[1] | other_lib/lib/other_gem.rb:3:20:3:20 | y | parameter | +| OtherLib::A | Method[foo] | Argument[self] | other_lib/lib/other_gem.rb:3:9:4:11 | self in foo | parameter | diff --git a/ruby/ql/test/query-tests/utils/modeleditor/FrameworkModeAccessPaths.qlref b/ruby/ql/test/query-tests/utils/modeleditor/FrameworkModeAccessPaths.qlref new file mode 100644 index 000000000000..8407cd817f14 --- /dev/null +++ b/ruby/ql/test/query-tests/utils/modeleditor/FrameworkModeAccessPaths.qlref @@ -0,0 +1 @@ +utils/modeleditor/FrameworkModeAccessPaths.ql diff --git a/ruby/ql/test/query-tests/utils/modeleditor/FrameworkModeEndpoints.expected b/ruby/ql/test/query-tests/utils/modeleditor/FrameworkModeEndpoints.expected index fdfd9173720e..a42da32525d9 100644 --- a/ruby/ql/test/query-tests/utils/modeleditor/FrameworkModeEndpoints.expected +++ b/ruby/ql/test/query-tests/utils/modeleditor/FrameworkModeEndpoints.expected @@ -1,13 +1,13 @@ | lib/module.rb:1:1:7:3 | M1 | mylib | M1 | | | false | module.rb | | | lib/module.rb:2:3:3:5 | foo | mylib | M1 | foo | (x,y) | false | module.rb | | | lib/module.rb:5:3:6:5 | self_foo | mylib | M1! | self_foo | (x,y) | false | module.rb | | -| lib/mylib.rb:3:1:30:3 | A | mylib | A | | | false | mylib.rb | | +| lib/mylib.rb:3:1:33:3 | A | mylib | A | | | false | mylib.rb | | | lib/mylib.rb:4:3:5:5 | initialize | mylib | A! | new | (x,y) | false | mylib.rb | | -| lib/mylib.rb:7:3:8:5 | foo | mylib | A | foo | (x,y,key1:) | false | mylib.rb | | -| lib/mylib.rb:10:3:11:5 | bar | mylib | A | bar | (x) | false | mylib.rb | | -| lib/mylib.rb:13:3:14:5 | self_foo | mylib | A! | self_foo | (x,y) | false | mylib.rb | | -| lib/mylib.rb:21:3:29:5 | ANested | mylib | A::ANested | | | false | mylib.rb | | -| lib/mylib.rb:22:5:23:7 | foo | mylib | A::ANested | foo | (x,y) | false | mylib.rb | | +| lib/mylib.rb:7:3:11:5 | foo | mylib | A | foo | (x,y,key1:) | false | mylib.rb | | +| lib/mylib.rb:13:3:14:5 | bar | mylib | A | bar | (x) | false | mylib.rb | | +| lib/mylib.rb:16:3:17:5 | self_foo | mylib | A! | self_foo | (x,y) | false | mylib.rb | | +| lib/mylib.rb:24:3:32:5 | ANested | mylib | A::ANested | | | false | mylib.rb | | +| lib/mylib.rb:25:5:26:7 | foo | mylib | A::ANested | foo | (x,y) | false | mylib.rb | | | lib/other.rb:3:1:8:3 | B | mylib | B | | | false | other.rb | | | lib/other.rb:6:3:7:5 | foo | mylib | B | foo | (x,y) | false | other.rb | | | lib/other.rb:10:1:12:3 | C | mylib | C | | | false | other.rb | | diff --git a/ruby/ql/test/query-tests/utils/modeleditor/lib/mylib.rb b/ruby/ql/test/query-tests/utils/modeleditor/lib/mylib.rb index 699c814dbf77..66c0d35ac234 100644 --- a/ruby/ql/test/query-tests/utils/modeleditor/lib/mylib.rb +++ b/ruby/ql/test/query-tests/utils/modeleditor/lib/mylib.rb @@ -5,6 +5,9 @@ def initialize(x, y) end def foo(x, y, key1:, **kwargs, &block) + block.call(x, y, key2: key1) + + yield x, y, key2: key1 end def bar(x, *args)