Python: add module for post-update flow

The logic for adding flow out of post-update-nodes
was also duplicated, so I added a module for that also.

The two modules are now only used together. I did not
fuse them, though, as I think they may not be used
together in the case of use-use-flow.
This commit is contained in:
Rasmus Lerchedahl Petersen
2023-10-27 15:07:12 +02:00
parent e745df6478
commit 1f8a51cffe

View File

@@ -381,60 +381,83 @@ module EssaFlow {
//--------
// Local flow
//--------
signature predicate stepSig(Node nodeFrom, Node nodeTo);
/** A module for transforming step relations. */
module StepRelationTransformations {
/** A step relation */
signature predicate stepSig(Node nodeFrom, Node nodeTo);
/**
* A module to separate import-time from run-time.
*
* We really have two local flow relations, on for module initialisation time (or _import time_) and one for runtime.
* Consider a read from a global variable `x = foo`. At import time there should be a local flow step from `foo` to `x`,
* while at runtime there should be a jump step from the module variable corresponding to `foo` to `x`.
*
* Similarly for, a write `foo = y`, at import time, there is a local flow step from `y` to `foo` while at runtime there
* is a jump step from `y` to the module variable corresponding to `foo`.
*
* We need a way of distinguishing if we are looking at import time or runtime. We have the following helpful facts:
* - All top-level executable statements are import time (and import time only)
* - All -non-top-level code may be executed at runtime (but could also be executed at import time)
*
* We could write an analysis to determine which functions are called at import time, but until we have that, we will go
* with the heuristic that global variables act according to import time rules at top-level program points and according
* to runtime rules everywhere else. This will forego some import time local flow but otherwise be consistent.
*/
module Separate<stepSig/2 rawStep> {
/**
* Holds if `node` is found at the top level of a module.
* A module to separate import-time from run-time.
*
* We really have two local flow relations, on for module initialisation time (or _import time_) and one for runtime.
* Consider a read from a global variable `x = foo`. At import time there should be a local flow step from `foo` to `x`,
* while at runtime there should be a jump step from the module variable corresponding to `foo` to `x`.
*
* Similarly for, a write `foo = y`, at import time, there is a local flow step from `y` to `foo` while at runtime there
* is a jump step from `y` to the module variable corresponding to `foo`.
*
* We need a way of distinguishing if we are looking at import time or runtime. We have the following helpful facts:
* - All top-level executable statements are import time (and import time only)
* - All -non-top-level code may be executed at runtime (but could also be executed at import time)
*
* We could write an analysis to determine which functions are called at import time, but until we have that, we will go
* with the heuristic that global variables act according to import time rules at top-level program points and according
* to runtime rules everywhere else. This will forego some import time local flow but otherwise be consistent.
*/
pragma[inline]
predicate isTopLevel(Node node) { node.getScope() instanceof Module }
module Separate<stepSig/2 rawStep> {
/**
* Holds if `node` is found at the top level of a module.
*/
pragma[inline]
predicate isTopLevel(Node node) { node.getScope() instanceof Module }
/** Holds if a step can be taken from `nodeFrom` to `nodeTo` at import time. */
predicate importTimeStep(Node nodeFrom, Node nodeTo) {
// As a proxy for whether statements can be executed at import time,
// we check if they appear at the top level.
// This will miss statements inside functions called from the top level.
isTopLevel(nodeFrom) and
isTopLevel(nodeTo) and
rawStep(nodeFrom, nodeTo)
}
/** Holds if a step can be taken from `nodeFrom` to `nodeTo` at import time. */
predicate importTimeStep(Node nodeFrom, Node nodeTo) {
// As a proxy for whether statements can be executed at import time,
// we check if they appear at the top level.
// This will miss statements inside functions called from the top level.
isTopLevel(nodeFrom) and
isTopLevel(nodeTo) and
rawStep(nodeFrom, nodeTo)
}
/** Holds if a step can be taken from `nodeFrom` to `nodeTo` at runtime. */
predicate runtimeStep(Node nodeFrom, Node nodeTo) {
// Anything not at the top level can be executed at runtime.
not isTopLevel(nodeFrom) and
not isTopLevel(nodeTo) and
rawStep(nodeFrom, nodeTo)
/** Holds if a step can be taken from `nodeFrom` to `nodeTo` at runtime. */
predicate runtimeStep(Node nodeFrom, Node nodeTo) {
// Anything not at the top level can be executed at runtime.
not isTopLevel(nodeFrom) and
not isTopLevel(nodeTo) and
rawStep(nodeFrom, nodeTo)
}
/**
* Holds if a step can be taken from `nodeFrom` to `nodeTo`.
*/
predicate step(Node nodeFrom, Node nodeTo) {
importTimeStep(nodeFrom, nodeTo) or
runtimeStep(nodeFrom, nodeTo)
}
}
/**
* Holds if a step can be taken from `nodeFrom` to `nodeTo`.
* A module to add steps from post-update nodes.
* Whenever there is a step from `x` to `y`,
* we add a step from `[post] x` to `y`.
*/
predicate step(Node nodeFrom, Node nodeTo) {
importTimeStep(nodeFrom, nodeTo) or
runtimeStep(nodeFrom, nodeTo)
module IncludePostUpdateFlow<stepSig/2 rawStep> {
predicate step(Node nodeFrom, Node nodeTo) {
// If a raw step can be taken out of a node `node`, a step can be taken
// both out of `node` and any post-update node of `node`.
exists(Node node | rawStep(node, nodeTo) |
nodeFrom = node
or
nodeFrom.(PostUpdateNode).getPreUpdateNode() = node
)
}
}
}
import StepRelationTransformations
/**
* This is the local flow predicate that is used as a building block in global
* data flow.
@@ -455,12 +478,7 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
* or at runtime when callables in the module are called.
*/
predicate simpleLocalFlowStepForTypetracking(Node nodeFrom, Node nodeTo) {
// If there is local flow out of a node `node`, we want flow
// both out of `node` and any post-update node of `node`.
exists(Node node |
nodeFrom = update(node) and
Separate<EssaFlow::essaFlowStep/2>::step(node, nodeTo)
)
IncludePostUpdateFlow<Separate<EssaFlow::essaFlowStep/2>::step/2>::step(nodeFrom, nodeTo)
}
private predicate summaryLocalStep(Node nodeFrom, Node nodeTo) {
@@ -469,12 +487,7 @@ private predicate summaryLocalStep(Node nodeFrom, Node nodeTo) {
}
predicate summaryFlowSteps(Node nodeFrom, Node nodeTo) {
// If there is local flow out of a node `node`, we want flow
// both out of `node` and any post-update node of `node`.
exists(Node node |
nodeFrom = update(node) and
Separate<summaryLocalStep/2>::step(node, nodeTo)
)
IncludePostUpdateFlow<Separate<summaryLocalStep/2>::step/2>::step(nodeFrom, nodeTo)
}
/** `ModuleVariable`s are accessed via jump steps at runtime. */
@@ -498,15 +511,6 @@ predicate runtimeJumpStep(Node nodeFrom, Node nodeTo) {
)
}
/**
* Holds if `result` is either `node`, or the post-update node for `node`.
*/
private Node update(Node node) {
result = node
or
result.(PostUpdateNode).getPreUpdateNode() = node
}
//--------
// Type pruning
//--------