-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Python: Captured variables for type tracking and the API graph #12537
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Python: Captured variables for type tracking and the API graph #12537
Conversation
73829e8 to
0723a05
Compare
|
Evaluation showed a modest slow-down. This is expected, since we compute a bigger graph. |
This is a condensed versio of the user reported example found [here](https://github.com/dsp-testing/apictf/blob/eb377d5918bb4ac316a32361e5e0c082e61036d6/app.py#L278) The `MISSING` annotation indicates where our API graph falls short.
type tracking and the API graph.
- In `TypeTrackerSpecific.qll` we add a jump step
- to every scope entry definition
- from the value of any defining `DefinitionNode`
(In our example, the definition is the class name, `Users`,
while the assigned value is the class definition, and it is
the latter which receives flow in this case.)
- In `LocalSources.qll` we allow scope entry definitions as local sources.
- This feels natural enough, as they are a local source for the value, they represent.
It is perhaps a bit funne to see an Ssa variable here,
rather than a control flow node.
- This is necessary in order for type tracking to see the local flow
from the scope entry definition.
- In `ApiGraphs.qll` we no longer restrict the result of `trackUseNode`
to be an `ExprNode`. To keep the positive formulation, we do not
prohibit module variable nodes. Instead we restrict to the new
`LocalSourceNodeNotModule` which avoids those cases.
Adjusted `tracked.ql` - no need to annotate results on line 0 this could happen for global SSA variables - no need to annotate scope entry definitons they look a bit weird, as the annotation goes on the line of the function definition.
66d83fb to
f9bffb5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have not looked at the QL code yet, just skimmed the examples added.
I would like you to add more examples of edge cases, and things we still don't quite handle correctly even with this PR.
I would assume we handle Foo/Bar correctly in the examples below, but not so sure about Baz (can differentiate on whether a subclass of A or B reaches the print statement).
def func()
if <cond>:
class Foo(A): pass
else:
class Foo(B): pass
class Bar(A): pass
class Bar(B): pass
class Baz(A): pass
def other_func():
print(Foo)
print(Bar)
print(Baz)
class Baz(B): pass
other_func()I guess there are also cases where a reference to a global variables is captured, and the variables is altered in a function (using global modifier) -- but this might be covered by our tests already?
Lastly, do we have tests that cover capturing by value, such as the code below?
def func():
a = 1
def inner(a_val=a):
print(a_val, a)
a = 2
inner()
python/ql/test/library-tests/ApiGraphs/py3/test_captured_flask.py
Outdated
Show resolved
Hide resolved
Co-authored-by: Rasmus Wriedt Larsen <rasmuswriedtlarsen@gmail.com>
…aptured-variables-for-typetracking
…aptured-variables-for-typetracking
Revert "python: no longer missing" This reverts commit f796177.
also rename `collections.py` so it does not clash with the standard library name. This clash is an issue when testing locally.
|
Nice suggestions for missing tests. Some of them are more dataflow than type-tracking, but good to have so I added them anyway 👍 |
Instead of reusing `nonSink0` for both captureOut1NotCalled and captureOut2NotCalled tests (I used 1/2 naming scheme to match things up nicely). I also added a comment highlighting that `m` is the function that is not called (since I overlooked that initially :O)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made a small set of changes locally to use fresh variables names in some tests, and instead of requesting that you click buttons to apply these changes, I simply pushed the changes on the PR myself.
However, there are a few minor things I don't feel as confident about, so doing this in normal review style 😊
But overall this looks really good, and we have a really nice set of improvements to our results 💪 🎉
We should perhaps check more thoroughly that the jump step is always correct.
In the PR description you said the above, so just want to check whether this is something we need to look at more thoroughly? (to me it looks ok)
python/ql/test/experimental/dataflow/variable-capture/by_value.py
Outdated
Show resolved
Hide resolved
|
|
||
| def other_func(): | ||
| print(Foo) #$ use=moduleImport("foo").getMember("A").getASubclass() use=moduleImport("foo").getMember("B").getASubclass() | ||
| print(Bar) #$ use=moduleImport("foo").getMember("B").getASubclass() MISSING: use=moduleImport("foo").getMember("A").getASubclass() The MISSING here is documenting correct behaviour |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree. There is no path where the Bar printed on this line can be a subclass of A.
| print(Bar) #$ use=moduleImport("foo").getMember("B").getASubclass() MISSING: use=moduleImport("foo").getMember("A").getASubclass() The MISSING here is documenting correct behaviour | |
| print(Bar) #$ use=moduleImport("foo").getMember("B").getASubclass() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no path where the
Barprinted on this line can be a subclass ofA.
On this we agree, but if I simply leave out the annotation, we do not know if it finds such a path or not. This because we are in the context of "admissible annotations", so it would not complain if it sees something but does not find an annotation. I tried to express that the MISSING annotation is used to document the fact that no path is, correctly, found.
python/ql/lib/semmle/python/dataflow/new/internal/LocalSources.qll
Outdated
Show resolved
Hide resolved
Co-authored-by: Rasmus Wriedt Larsen <rasmuswriedtlarsen@gmail.com>
I think it is ok :-) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Besides a small nit, this looks ready to be merged for me 👍
python/ql/test/library-tests/ApiGraphs/py3/test_captured_inheritance.py
Outdated
Show resolved
Hide resolved
|
Interesting question from @calumgrant: Would a DCA run show, via some meta query, that we find new edges in the call graph? |
yes, if you run this meta query: https://github.com/github/codeql/blob/main/python/ql/src/meta/analysis-quality/CallGraph.ql |
…itance.py Co-authored-by: Rasmus Wriedt Larsen <rasmuswriedtlarsen@gmail.com>
Thanks, I ran that and a few more. It seems to have made a positive difference on the call graph :-) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From my sampling of the new call-edges, they all look good 👍
We should perhaps check more thoroughly that the jump step is always correct.
From all the fixed results, it looks good, though...
In
TypeTrackerSpecific.qllwe add a jump stepDefinitionNode(In our example, the definition is the class name,
Users,while the assigned value is the class definition, and it is
the latter which receives flow in this case.)
In
LocalSources.qllwe allow scope entry definitions as local sources.It is perhaps a bit funne to see an Ssa variable here,
rather than a control flow node.
from the scope entry definition.
In
ApiGraphs.qllwe no longer restrict the result oftrackUseNodeto be an
ExprNode. To keep the positive formulation, we do notprohibit module variable nodes explicitly. Instead we restrict to the new
LocalSourceNodeNotModulewhich has a restricted charpred.Adjusted
tracked.ql, the type tracking test:this could happen for global SSA variables
they look a bit weird, as the annotation goes on the
line of the function definition.