Python: Dataflow improvements#7807
Conversation
Before we didn't show how we treated the value _after_ the check. But we do actually handle this nicely 💪
All the same tests are present in `fieldflow/test.py`
Also fixes a bug in the tests
I deleted the old tests, so it's very clear what tests to look for
I went with NormalDataflowTest to signify that if you don't know what you're looking for, this is probably the one. I did not want to just call it DataflowTest, since that becomes a big vague when there are also `FlowTest.qll` and `MaximalFlowTest.qll` -- I'm open to renaming this though 👍
I had to rewrite the SINK1-SINK7 definitions, since this new requirement complained that we had to add this `MISSING: flow` annotation :D Doing this implementation also revealed that there was a bug, since I did not compare files when checking for these `MISSING:` annotations. So fixed that up in the implementation for inline taint tests as well. (extra whitespace in argumentPassing.py to avoid changing line numbers for other tests)
I checked to see that the tests still works. If I deleted the `arg5` annotation, it got failures: ```diff diff --git a/python/ql/test/experimental/dataflow/coverage/argumentPassing.py b/python/ql/test/experimental/dataflow/coverage/argumentPassing.py index e218bdd..71816c1e01 100644 --- a/python/ql/test/experimental/dataflow/coverage/argumentPassing.py +++ b/python/ql/test/experimental/dataflow/coverage/argumentPassing.py @@ -46,7 +46,7 @@ def argument_passing( c, d=arg4, #$ arg4 func=argument_passing *, - e=arg5, #$ arg5 func=argument_passing + e=arg5, f, **g, ): diff --git a/python/ql/test/experimental/dataflow/coverage/argumentRoutingTest.expected b/python/ql/test/experimental/dataflow/coverage/argumentRoutingTest.expected index e69de29..22037a40c3 100644 --- a/python/ql/test/experimental/dataflow/coverage/argumentRoutingTest.expected +++ b/python/ql/test/experimental/dataflow/coverage/argumentRoutingTest.expected @@ -0,0 +1,2 @@ +| argumentPassing.py:49:7:49:10 | ControlFlowNode for arg5 | Unexpected result: arg5= | +| argumentPassing.py:49:7:49:10 | ControlFlowNode for arg5 | Unexpected result: func=argument_passing | ```
I feel like they don't bring any value anymore, since we have the nice inline expectation tests. If I'm wrong, happy to revert this commit though.
Note that this doesn't actually add the desired flow from setattr, due to missing post-update note. This will be fixed in later commit.
Notice the strange thing with treating `mypkg.foo(42)` as a ClassCall, but completely ignoring `mypkg.subpkg.bar(43)` -- due to having the two `ClassValue`s: - `Missing module attribute mypkg.foo` - `Missing module attribute mypkg.subpkg` But not `Missing module attribute mypkg.subpkg` with the current import structure.
This means that DataFlowCall is only for resolvable calls, which might not seem like a big thing in itself, but enables the next commit to actually work :P
47db18d to
dd3e94d
Compare
Besides solving the problem with `setattr`, it also solved some old problems with json library modeling (yay).
I went with `minorAnalysis` instead of `majorAnalysis`, since I don't think the impact of this change will be major (but that's just my gut feeling).
dd3e94d to
14a1aa0
Compare
|
Performance looks ok. There are some changes in |
| s = TAINTED_STRING | ||
| ensure_tainted(s) # $ tainted |
There was a problem hiding this comment.
I am not sure these are without value. I think they check that the barrier guard did not conclude the entire block (everything dominated by the true edge) taint-free..
There was a problem hiding this comment.
Fair point. I think the division between the tests in that file and test_logical is not super clear, but I've expanded the test in both files a bit, so everything should be covered with tests now 👍
| @@ -1,59 +0,0 @@ | |||
| from python.ql.test.experimental.dataflow.testDefinitions import * | |||
There was a problem hiding this comment.
The tests in this file are not quite duplicates of those in test.py, since here it all happens in the global scope. It is possible that the tests could be arranged better, but just deleting these is perhaps not the best way.
There was a problem hiding this comment.
Does field-flow work in a different way when things are on the global scope?
I must admit I did not consider if global scope could play a part here, but if the only difference is that things are in a global scope vs. defined in functions, I don't see the extra value from having both.
There was a problem hiding this comment.
Global variables are distinct from local ones in our analysis, so I think we need to test both contexts.
| case 42 as x: | ||
| SINK(x) #$ flow="SOURCE, l:-2 -> x" flow="42, l:-1 -> x" |
There was a problem hiding this comment.
I am not sure I understand why this is preferred. In fact, it might be good to not just test strings all the time? (42 is a documented source)
There was a problem hiding this comment.
Because you're matching against SOURCE, which is the string source -- so without this change, running validTest.py fails 😬
| obj.foo = x | ||
|
|
||
|
|
||
| def test_example1(): |
There was a problem hiding this comment.
The names here used to refer to the docs (but I can see there were no link in the code), but it might be time to move on from that...
There was a problem hiding this comment.
I see. Unless you're feeling very strongly about it, I think the new names are a bit more easy 😊
There was a problem hiding this comment.
Agreed, you have expanded the tests beyond those examples, so telling names are more valuable now.
Based on review conversation
RasmusWL
left a comment
There was a problem hiding this comment.
I think I have been able to answer/fix all the review comments
| case 42 as x: | ||
| SINK(x) #$ flow="SOURCE, l:-2 -> x" flow="42, l:-1 -> x" |
There was a problem hiding this comment.
Because you're matching against SOURCE, which is the string source -- so without this change, running validTest.py fails 😬
| override predicate hasActualResult(Location location, string element, string tag, string value) { | ||
| super.hasActualResult(location, element, tag, value) | ||
| } |
There was a problem hiding this comment.
no, that was a leftover from being able to quick-eval it 😄 I've removed it again
| @@ -1 +0,0 @@ | |||
| import semmle.python.dataflow.new.internal.DataFlowImplConsistency::Consistency | |||
There was a problem hiding this comment.
No, I've re-added it. I looked into adding consistency queries to our codeql test run setup, so that these would be part of ALL tests, but I ended up not doing this -- but that's why they were deleted.
| obj.foo = x | ||
|
|
||
|
|
||
| def test_example1(): |
There was a problem hiding this comment.
I see. Unless you're feeling very strongly about it, I think the new names are a bit more easy 😊
| @@ -1,59 +0,0 @@ | |||
| from python.ql.test.experimental.dataflow.testDefinitions import * | |||
There was a problem hiding this comment.
Does field-flow work in a different way when things are on the global scope?
I must admit I did not consider if global scope could play a part here, but if the only difference is that things are in a global scope vs. defined in functions, I don't see the extra value from having both.
| s = TAINTED_STRING | ||
| ensure_tainted(s) # $ tainted |
There was a problem hiding this comment.
Fair point. I think the division between the tests in that file and test_logical is not super clear, but I've expanded the test in both files a bit, so everything should be covered with tests now 👍
yoff
left a comment
There was a problem hiding this comment.
Nearly there. I do think that testing in both local and global scope is necessary, due to how different we treat those two contexts (separate classes for local and global variables, different behaviour with respect to import time and runtime). I agree, though, that this distinction is not at all clear in the original test-files. I look forward to seeing this merged :-)
I thought it was interesting that it did not propagate flow to the uses inside the functions :O
Notice that these tests don't pass, to show how they differ in the next commit!
yoff
left a comment
There was a problem hiding this comment.
Thanks for organising the missing bits. I am happy to merge this once the tests pass 👍
|
Ohhh... I had not accounted for |
This is very suspicious
By giving all variables unique names I also added a comment with the function name from the normal tests, so its' easily visible what these tests are testing
Since that apparently impacts call graph resolution with points-to :O Also interesting that global flow was only not working for those cases because of the tricky ifs... still need to 100% figure out how those ifs are messing up the analysis :|
TL;DR; we used a too low value for `--max-import-depth` :(
I think we should write our tests in a way that puts points-to in the best condition to resolve calls. Although this specific change did not change much, it should help set us up for success in the future 👍
Apparently there are minor differences with `test-6-max-import-depth-2` where under Python 2 `isfile_no_problem.py` still works as before
yoff
left a comment
There was a problem hiding this comment.
Wow, quite a twist at the end. Do you want to leave the investigations in this open state with all the monitoring in place, or would you rather put it on a branch until it is concluded? I feel it could take some time to sort out, and that we might not want to invest too much effort on the points-to implementation, so perhaps it is indeed better close it off and just leave the evidence/monitoring as is...
My intention was not to do any more investigation, but just leave the "evidence" as is 😊 |
yoff
left a comment
There was a problem hiding this comment.
LGTM, thanks for the ride :-)
This turned out to be a bit of random bag of things, and the first many commits is refactoring.
Will strongly recommend reading this PR commit by commit.
The main goal was to improve field-flow for attributes, such that we use AttrRead/AttrWrite modeling, and clear content when writing new value to attribute.
Ended up improving a few of the json library modeling, since I added post-update nodes to arguments of calls we can't resolve as well 👍