diff --git a/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsSpecific.qll b/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsSpecific.qll index 38a1198193e..d0a5d1b9da5 100644 --- a/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsSpecific.qll +++ b/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsSpecific.qll @@ -108,6 +108,23 @@ API::Node getExtraSuccessorFromInvoke(API::CallNode node, AccessPathToken token) ) } +pragma[inline] +API::Node getAFuzzySuccessor(API::Node node) { + result = node.getAMember() + or + result = node.getParameter(_) + or + result = node.getKeywordParameter(_) + or + result = node.getReturn() + or + result = node.getASubscript() + or + result = node.getAwaited() + or + result = node.getASubclass() +} + /** * Holds if `invoke` matches the PY-specific call site filter in `token`. */ diff --git a/python/ql/test/library-tests/frameworks/data/test.expected b/python/ql/test/library-tests/frameworks/data/test.expected index 68de6ecd878..dffe93790d0 100644 --- a/python/ql/test/library-tests/frameworks/data/test.expected +++ b/python/ql/test/library-tests/frameworks/data/test.expected @@ -11,6 +11,11 @@ taintFlow | test.py:83:50:83:60 | ControlFlowNode for getSource() | test.py:83:8:83:61 | ControlFlowNode for Attribute() | | test.py:86:49:86:59 | ControlFlowNode for getSource() | test.py:86:8:86:60 | ControlFlowNode for Attribute() | | test.py:87:56:87:66 | ControlFlowNode for getSource() | test.py:87:8:87:67 | ControlFlowNode for Attribute() | +| test.py:114:19:114:29 | ControlFlowNode for getSource() | test.py:114:19:114:29 | ControlFlowNode for getSource() | +| test.py:115:20:115:30 | ControlFlowNode for getSource() | test.py:115:20:115:30 | ControlFlowNode for getSource() | +| test.py:116:31:116:41 | ControlFlowNode for getSource() | test.py:116:31:116:41 | ControlFlowNode for getSource() | +| test.py:117:31:117:41 | ControlFlowNode for getSource() | test.py:117:31:117:41 | ControlFlowNode for getSource() | +| test.py:118:35:118:45 | ControlFlowNode for getSource() | test.py:118:35:118:45 | ControlFlowNode for getSource() | isSink | test.py:4:8:4:8 | ControlFlowNode for x | test-sink | | test.py:7:17:7:17 | ControlFlowNode for x | test-sink | @@ -50,6 +55,11 @@ isSink | test.py:91:21:91:23 | ControlFlowNode for one | test-sink | | test.py:91:30:91:32 | ControlFlowNode for two | test-sink | | test.py:98:6:98:9 | ControlFlowNode for baz2 | test-sink | +| test.py:114:19:114:29 | ControlFlowNode for getSource() | test-sink | +| test.py:115:20:115:30 | ControlFlowNode for getSource() | test-sink | +| test.py:116:31:116:41 | ControlFlowNode for getSource() | test-sink | +| test.py:117:31:117:41 | ControlFlowNode for getSource() | test-sink | +| test.py:118:35:118:45 | ControlFlowNode for getSource() | test-sink | isSource | test.py:3:5:3:15 | ControlFlowNode for getSource() | test-source | | test.py:9:8:9:14 | ControlFlowNode for alias() | test-source | @@ -89,6 +99,12 @@ isSource | test.py:104:32:104:37 | ControlFlowNode for param2 | test-source | | test.py:107:24:107:28 | ControlFlowNode for name1 | test-source | | test.py:107:31:107:35 | ControlFlowNode for name2 | test-source | +| test.py:114:19:114:29 | ControlFlowNode for getSource() | test-source | +| test.py:115:20:115:30 | ControlFlowNode for getSource() | test-source | +| test.py:116:31:116:41 | ControlFlowNode for getSource() | test-source | +| test.py:117:31:117:41 | ControlFlowNode for getSource() | test-source | +| test.py:118:35:118:45 | ControlFlowNode for getSource() | test-source | +| test.py:119:20:119:30 | ControlFlowNode for getSource() | test-source | syntaxErrors | Member[foo | | Member[foo] .Member[bar] | diff --git a/python/ql/test/library-tests/frameworks/data/test.py b/python/ql/test/library-tests/frameworks/data/test.py index ea1a6e0d4d4..65d9ea037c3 100644 --- a/python/ql/test/library-tests/frameworks/data/test.py +++ b/python/ql/test/library-tests/frameworks/data/test.py @@ -106,3 +106,14 @@ class OtherSubClass (ArgPos.MyClass): def anyNamed(self, name1, name2=2): # Parameter[any-named] matches all non-self named parameters pass + +import testlib as testlib +import testlib.nestedlib as testlib2 +import otherlib as otherlib + +testlib.fuzzyCall(getSource()) # NOT OK +testlib2.fuzzyCall(getSource()) # NOT OK +testlib.foo.bar.baz.fuzzyCall(getSource()) # NOT OK +testlib.foo().bar().fuzzyCall(getSource()) # NOT OK +testlib.foo(lambda x: x.fuzzyCall(getSource())) # NOT OK +otherlib.fuzzyCall(getSource()) # OK diff --git a/python/ql/test/library-tests/frameworks/data/test.ql b/python/ql/test/library-tests/frameworks/data/test.ql index 6c7639dc0c3..d9f270e6caf 100644 --- a/python/ql/test/library-tests/frameworks/data/test.ql +++ b/python/ql/test/library-tests/frameworks/data/test.ql @@ -51,6 +51,8 @@ class Sinks extends ModelInput::SinkModelCsv { // testing package syntax "foo1.bar;Member[baz1].Argument[any];test-sink", // "foo2;Member[bar].Member[baz2].Argument[any];test-sink", // + // testing fuzzy + "testlib;Fuzzy.Member[fuzzyCall].Argument[0];test-sink", // ] } }