Skip to content
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: Model parameter with default value as DefinitionNode #13685

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

RasmusWL
Copy link
Member

@RasmusWL RasmusWL commented Jul 7, 2023

This PR took a series of unexpected events (some could have been avoided with better planning), so the commits end up taking a few twists and turns. I think everything should be very straightforward if reading commit-by-commit 😊

@github-actions github-actions bot added the Python label Jul 7, 2023
@RasmusWL RasmusWL force-pushed the captured-variables-default-param-value branch from d3e4dc7 to 70994b9 Compare July 7, 2023 10:14
@RasmusWL
Copy link
Member Author

RasmusWL commented Jul 7, 2023

There might also be an option to fix this problem by using EssaNodeDefinition instead, since we have this

class ParameterDefinition extends EssaNodeDefinition {

But just rewriting the code to be the following didn't work as I wanted. Even though it fixes the missing flow to x added in bea0700, it also loses flow to x_alias 😞

predicate capturedJumpStep(Node nodeFrom, Node nodeTo) {
  exists(SsaSourceVariable var, EssaNodeDefinition essaDef, ControlFlowNode val |
    essaDef.definedBy(var, val)
  |
    nodeTo.asVar().(ScopeEntryDefinition).getSourceVariable() = var and
    nodeFrom.asCfgNode() = val and
    var.getScope().getScope*() = nodeFrom.getScope()
  )
}

I pursued this approach a bit since I wasn't 100% happy about the .getScope() checks added to DataFlowPrivate.

I should have added some tests to highlight what the state of dataflow from default values were before this change, since I expect that it's handled to some degree, I just haven't found the logic yet.

@RasmusWL
Copy link
Member Author

RasmusWL commented Jul 7, 2023

Clearly I was a bit to eager to just accept changes in 70994b9, and now had to fix things up. Keeping that history, since otherwise the changes in 43b0250 might seem strange.

The additions to ImpliesDataflow.expected should actually be considered bad, since it selects things that have points-to flow, but no dataflow. However, the tests highlighted below shows that we still have the flow that we want! (so no problem after all)

@expects(3)
def test_with_default_param_value(x=SOURCE, /, y=SOURCE, *, z=SOURCE):
SINK(x) #$ flow="SOURCE, l:-1 -> x"
SINK(y) #$ flow="SOURCE, l:-2 -> y"
SINK(z) #$ flow="SOURCE, l:-3 -> z"

@RasmusWL
Copy link
Member Author

RasmusWL commented Jul 7, 2023

I pursued this approach a bit since I wasn't 100% happy about the .getScope() checks added to DataFlowPrivate.

I think with the current code I'm quite happy. The change below doesn't feel entirely good, but currently I don't see a better approach 😐

   AssignmentDefinition() {
     SsaSource::assignment_definition(this.getSourceVariable(), this.getDefiningNode(), value) and
+    not this instanceof ParameterDefinition
   }

I should have added some tests to highlight what the state of dataflow from default values were before this change, since I expect that it's handled to some degree, I just haven't found the logic yet.

Found the original implementation which I eliminated in 4e8a114

Somehow the previous fix didn't work :O
@RasmusWL
Copy link
Member Author

RasmusWL commented Jul 7, 2023

I'm flabbergasted that the previous code did not work.

   exists(ParameterDefinition param |
     nodeFrom.asCfgNode() = param.getDefault() and
-    nodeTo.asCfgNode() = param.getDefiningNode()
+    nodeTo.asVar() = param.getVariable()
   )

How does making the change above make us not find the proper flow??? In the code below, with the fix of this commit we have

tracked --jumpstep--> CFN param x --local--> ESSA X --local--> CFN first-use x

With the change before (and outlined in the diff above) we would have the steps, so CLEARLY that should just work!

  • CFN param x --local--> ESSA X
  • tracked --jumpstep--> ESSA X
  • ESSA X --local--> CFN first-use x
def foo(x=tracked):
    print(x)

it just doesn't make sense to me

@RasmusWL RasmusWL marked this pull request as ready for review July 10, 2023 08:41
@RasmusWL RasmusWL requested a review from a team as a code owner July 10, 2023 08:41
@calumgrant calumgrant requested a review from yoff July 10, 2023 09:13
@RasmusWL
Copy link
Member Author

Performance looks fine, and results indicate that we've found new ways to find the same alerts as before.

@yoff
Copy link
Contributor

yoff commented Jul 10, 2023

I'm flabbergasted that the previous code did not work.

   exists(ParameterDefinition param |
     nodeFrom.asCfgNode() = param.getDefault() and
-    nodeTo.asCfgNode() = param.getDefiningNode()
+    nodeTo.asVar() = param.getVariable()
   )

How does making the change above make us not find the proper flow??? In the code below, with the fix of this commit we have

tracked --jumpstep--> CFN param x --local--> ESSA X --local--> CFN first-use x

With the change before (and outlined in the diff above) we would have the steps, so CLEARLY that should just work!

  • CFN param x --local--> ESSA X
  • tracked --jumpstep--> ESSA X
  • ESSA X --local--> CFN first-use x
def foo(x=tracked):
    print(x)

it just doesn't make sense to me

Took me a bit to understand this. The reason is that in your second chain, the link

  • tracked --jumpstep--> ESSA X

is neither followed in the local flowsTo part, because it is not a local step (as it is a jump step), nor is it followed in the type tracking part because ESSA X is not a local source node. So it is not a valid chain of steps for type tracking. It should be for dataflow, though, but that is not what the test is concerned with.

(In the first case above, CFN param x is a local source node and so the chain is valid for type tracking.

Copy link
Contributor

@yoff yoff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, as I understand it, in the following program

def f(x, y=5):
  pass

we have two EssaDefinitions, namely for ESSA x and ESSA y, and their defining nodes (via getDefiningNode) are CFG x and CFG y respectively. This makes sense, since these may receive flow from outside during invocation of f.

Additionally, we have a DefinitionNode, namely CFG y, and its defining node (via getValue) is Expr 5. This quite nicely represents additional possible flow "from outside" (at definition time, at least) and is mirrored in the newly added jump step.

We do not consider y = 5 an assignment because it does not happen locally; we would have to split y into two or something like that to make it local flow. Did you consider moving this restriction up into SsaSource::assignment_definition?

Apart from this one thing, I think it hangs together quite nicely 🙂

I also checked that

predicate test(Node node) {
  jumpStepSharedWithTypeTracker(_, node) and
  not node instanceof LocalSourceNode
}

is currently empty on two large databases I had lying around.

@@ -501,7 +501,8 @@ class AssignmentDefinition extends EssaNodeDefinition {
ControlFlowNode value;

AssignmentDefinition() {
SsaSource::assignment_definition(this.getSourceVariable(), this.getDefiningNode(), value)
SsaSource::assignment_definition(this.getSourceVariable(), this.getDefiningNode(), value) and
not this instanceof ParameterDefinition
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider moving this restriction into assignment_definition.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that's better 👍 Done in 98ed5cf

@RasmusWL RasmusWL requested a review from yoff July 12, 2023 09:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants