Files
codeql/python/ql/src/experimental/dataflow/internal/DataFlowPublic.qll
Taus Brock-Nannestad 0e2ec0dfb4 Python: Remove cartesian product in TKwOverflowNode
With CP:

(0s) Tuple counts for dom#DataFlowPublic::TKwOverflowNode#ff:
1209    ~0%       {2} r1 = JOIN project#AstGenerated::Function_::getKwarg_dispred#ff AS L WITH ObjectAPI::CallableValue::getScope_dispred#ff_10#join_rhs AS R ON FIRST 1 OUTPUT R.<1>, L.<0>
4329    ~0%       {3} r2 = JOIN r1 WITH DataFlowPrivate::ArgumentPassing::connects#bb_10#join_rhs AS R ON FIRST 1 OUTPUT R.<1>, r1.<1>, r1.<0>
7819    ~2%       {4} r3 = JOIN r2 WITH Flow::CallNode::getArgByName_dispred#fff AS R ON FIRST 1 OUTPUT r2.<1>, r2.<2>, r2.<0>, R.<1>
7114    ~1%       {4} r4 = r3 AND NOT Function::Function::getArgByName_dispred#fff_01#antijoin_rhs AS R(r3.<0>, r3.<3>)
7114    ~76%      {2} r5 = SCAN r4 OUTPUT r4.<2>, r4.<1>
1123    ~0%       {1} r6 = JOIN project#Exprs::Call::getKwargs_dispred#ff AS L WITH py_flow_bb_node_10#join_rhs AS R ON FIRST 1 OUTPUT R.<1>
1123    ~0%       {1} r7 = JOIN r6 WITH Flow::CallNode#class#f AS R ON FIRST 1 OUTPUT r6.<0>
1357707 ~0%       {2} r8 = JOIN r7 WITH project#AstGenerated::Function_::getKwarg_dispred#ff AS R CARTESIAN PRODUCT OUTPUT R.<0>, r7.<0>
1357707 ~0%       {2} r9 = JOIN r8 WITH ObjectAPI::CallableValue::getScope_dispred#ff_10#join_rhs AS R ON FIRST 1 OUTPUT r8.<1>, R.<1>
1364821 ~0%       {2} r10 = r5 \/ r9
                  return r10

Without CP:

(13s) Tuple counts for dom#DataFlowPublic::TKwOverflowNode#ff:
1209    ~0%       {2} r1 = JOIN project#AstGenerated::Function_::getKwarg_dispred#ff AS L WITH ObjectAPI::CallableValue::getScope_dispred#ff_10#join_rhs AS R ON FIRST 1 OUTPUT R.<1>, L.<0>
19175   ~4%       {3} r2 = JOIN r1 WITH DataFlowPrivate::ArgumentPassing::connects#ff_10#join_rhs AS R ON FIRST 1 OUTPUT R.<1>, r1.<1>, r1.<0>
7819    ~2%       {4} r3 = JOIN r2 WITH Flow::CallNode::getArgByName_dispred#fff AS R ON FIRST 1 OUTPUT r2.<1>, r2.<2>, r2.<0>, R.<1>
7114    ~1%       {4} r4 = r3 AND NOT Function::Function::getArgByName_dispred#fff_01#antijoin_rhs AS R(r3.<0>, r3.<3>)
7114    ~76%      {2} r5 = SCAN r4 OUTPUT r4.<2>, r4.<1>
1123    ~0%       {1} r6 = JOIN project#Exprs::Call::getKwargs_dispred#ff AS L WITH py_flow_bb_node_10#join_rhs AS R ON FIRST 1 OUTPUT R.<1>
574     ~0%       {2} r7 = JOIN r6 WITH DataFlowPrivate::ArgumentPassing::connects#ff AS R ON FIRST 1 OUTPUT R.<1>, r6.<0>
524     ~1%       {3} r8 = JOIN r7 WITH ObjectAPI::CallableValue::getScope_dispred#ff AS R ON FIRST 1 OUTPUT R.<1>, r7.<1>, r7.<0>
291     ~0%       {2} r9 = JOIN r8 WITH project#AstGenerated::Function_::getKwarg_dispred#ff AS R ON FIRST 1 OUTPUT r8.<1>, r8.<2>
7405    ~72%      {2} r10 = r5 \/ r9
                  return r10
2020-10-20 17:29:26 +02:00

423 lines
14 KiB
Plaintext

/**
* Provides Python-specific definitions for use in the data flow library.
*/
private import python
private import DataFlowPrivate
import experimental.dataflow.TypeTracker
import Attributes
private import semmle.python.essa.SsaCompute
/**
* IPA type for data flow nodes.
*
* Flow between SSA variables are computed in `Essa.qll`
*
* Flow from SSA variables to control flow nodes are generally via uses.
*
* Flow from control flow nodes to SSA variables are generally via assignments.
*
* The current implementation of these cross flows can be seen in `EssaTaintTracking`.
*/
newtype TNode =
/** A node corresponding to an SSA variable. */
TEssaNode(EssaVariable var) or
/** A node corresponding to a control flow node. */
TCfgNode(ControlFlowNode node) { isExpressionNode(node) } or
/** A synthetic node representing the value of an object before a state change */
TSyntheticPreUpdateNode(NeedsSyntheticPreUpdateNode post) or
/** A synthetic node representing the value of an object after a state change. */
TSyntheticPostUpdateNode(NeedsSyntheticPostUpdateNode pre) or
/** A node representing a global (module-level) variable in a specific module. */
TModuleVariableNode(Module m, GlobalVariable v) { v.getScope() = m and v.escapes() } or
/**
* A node representing the overflow positional arguments to a call.
* That is, `call` contains more positional arguments than there are
* positional parameters in `callable`. The extra ones are passed as
* a tuple to a starred parameter; this synthetic node represents that tuple.
*/
TPosOverflowNode(CallNode call, CallableValue callable) {
exists(getPositionalOverflowArg(call, callable, _))
} or
/**
* A node representing the overflow keyword arguments to a call.
* That is, `call` contains keyword arguments for keys that do not have
* keyword parameters in `callable`. These extra ones are passed as
* a dictionary to a doubly starred parameter; this synthetic node
* represents that dictionary.
*/
TKwOverflowNode(CallNode call, CallableValue callable) {
exists(getKeywordOverflowArg(call, callable, _))
or
ArgumentPassing::connects(call, callable) and
exists(call.getNode().getKwargs()) and
callable.getScope().hasKwArg()
} or
/**
* A node representing an unpacked element of a dictionary argument.
* That is, `call` contains argument `**{"foo": bar}` which is passed
* to parameter `foo` of `callable`.
*/
TKwUnpacked(CallNode call, CallableValue callable, string name) {
call_unpacks(call, _, callable, name, _)
}
/**
* An element, viewed as a node in a data flow graph. Either an SSA variable
* (`EssaNode`) or a control flow node (`CfgNode`).
*/
class Node extends TNode {
/** Gets a textual representation of this element. */
string toString() { result = "Data flow node" }
/** Gets the scope of this node. */
Scope getScope() { none() }
private DataFlowCallable getCallableScope(Scope s) {
result.getScope() = s
or
not exists(DataFlowCallable c | c.getScope() = s) and
result = getCallableScope(s.getEnclosingScope())
}
/** Gets the enclosing callable of this node. */
DataFlowCallable getEnclosingCallable() { result = getCallableScope(this.getScope()) }
/** Gets the location of this node */
Location getLocation() { none() }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Convenience method for casting to EssaNode and calling getVar. */
EssaVariable asVar() { none() }
/** Convenience method for casting to CfgNode and calling getNode. */
ControlFlowNode asCfgNode() { none() }
/** Convenience method for casting to ExprNode and calling getNode and getNode again. */
Expr asExpr() { none() }
/**
* Gets a node that this node may flow to using one heap and/or interprocedural step.
*
* See `TypeTracker` for more details about how to use this.
*/
pragma[inline]
Node track(TypeTracker t2, TypeTracker t) { t = t2.step(this, result) }
}
class EssaNode extends Node, TEssaNode {
EssaVariable var;
EssaNode() { this = TEssaNode(var) }
EssaVariable getVar() { result = var }
override EssaVariable asVar() { result = var }
/** Gets a textual representation of this element. */
override string toString() { result = var.toString() }
override Scope getScope() { result = var.getScope() }
override Location getLocation() { result = var.getDefinition().getLocation() }
}
class CfgNode extends Node, TCfgNode {
ControlFlowNode node;
CfgNode() { this = TCfgNode(node) }
ControlFlowNode getNode() { result = node }
override ControlFlowNode asCfgNode() { result = node }
/** Gets a textual representation of this element. */
override string toString() { result = node.toString() }
override Scope getScope() { result = node.getScope() }
override Location getLocation() { result = node.getLocation() }
}
/**
* An expression, viewed as a node in a data flow graph.
*
* Note that because of control-flow splitting, one `Expr` may correspond
* to multiple `ExprNode`s, just like it may correspond to multiple
* `ControlFlow::Node`s.
*/
class ExprNode extends CfgNode {
ExprNode() { isExpressionNode(node) }
override Expr asExpr() { result = node.getNode() }
}
/** Gets a node corresponding to expression `e`. */
ExprNode exprNode(DataFlowExpr e) { result.getNode().getNode() = e }
/**
* The value of a parameter at function entry, viewed as a node in a data
* flow graph.
*/
class ParameterNode extends EssaNode {
ParameterNode() { var instanceof ParameterDefinition }
/**
* Holds if this node is the parameter of callable `c` at the
* (zero-based) index `i`.
*/
predicate isParameterOf(DataFlowCallable c, int i) {
var.(ParameterDefinition).getDefiningNode() = c.getParameter(i)
}
override DataFlowCallable getEnclosingCallable() { this.isParameterOf(result, _) }
/** Gets the `Parameter` this `ParameterNode` represents. */
Parameter getParameter() { result = var.(ParameterDefinition).getParameter() }
}
/**
* A node associated with an object after an operation that might have
* changed its state.
*
* This can be either the argument to a callable after the callable returns
* (which might have mutated the argument), or the qualifier of a field after
* an update to the field.
*
* Nodes corresponding to AST elements, for example `ExprNode`s, usually refer
* to the value before the update with the exception of `ObjectCreationNode`s,
* which represents the value _after_ the constructor has run.
*/
abstract class PostUpdateNode extends Node {
/** Gets the node before the state update. */
abstract Node getPreUpdateNode();
}
/**
* A data flow node corresponding to a module-level (global) variable that is accessed outside of the module scope.
*
* Global variables may appear twice in the data flow graph, as both `EssaNode`s and
* `ModuleVariableNode`s. The former is used to represent data flow between global variables as it
* occurs during module initialization, and the latter is used to represent data flow via global
* variable reads and writes during run-time.
*
* It is possible for data to flow from assignments made at module initialization time to reads made
* at run-time, but not vice versa. For example, there will be flow from `SOURCE` to `SINK` in the
* following snippet:
*
* ```python
* g = SOURCE
*
* def foo():
* SINK(g)
* ```
* but not the other way round:
*
* ```python
* SINK(g)
*
* def bar()
* global g
* g = SOURCE
* ```
*
* Data flow through `ModuleVariableNode`s is represented as `jumpStep`s, and so any write of a
* global variable can flow to any read of the same variable.
*/
class ModuleVariableNode extends Node, TModuleVariableNode {
Module mod;
GlobalVariable var;
ModuleVariableNode() { this = TModuleVariableNode(mod, var) }
override Scope getScope() { result = mod }
override string toString() {
result = "ModuleVariableNode for " + var.toString() + " in " + mod.toString()
}
/** Gets the module in which this variable appears. */
Module getModule() { result = mod }
/** Gets the global variable corresponding to this node. */
GlobalVariable getVariable() { result = var }
/** Gets a node that reads this variable. */
Node getARead() {
result.asCfgNode() = var.getALoad().getAFlowNode() and
// Ignore reads that happen when the module is imported. These are only executed once.
not result.getScope() = mod
}
/** Gets an `EssaNode` that corresponds to an assignment of this global variable. */
EssaNode getAWrite() {
result.asVar().getDefinition().(EssaNodeDefinition).definedBy(var, any(DefinitionNode defn))
}
override DataFlowCallable getEnclosingCallable() { result.(DataFlowModuleScope).getScope() = mod }
override Location getLocation() { result = mod.getLocation() }
}
/**
* The node holding the extra positional arguments to a call. This node is passed as a tuple
* to the starred parameter of the callable.
*/
class PosOverflowNode extends Node, TPosOverflowNode {
CallNode call;
PosOverflowNode() { this = TPosOverflowNode(call, _) }
override string toString() { result = "PosOverflowNode for " + call.getNode().toString() }
override Location getLocation() { result = call.getLocation() }
}
/**
* The node holding the extra keyword arguments to a call. This node is passed as a dictionary
* to the doubly starred parameter of the callable.
*/
class KwOverflowNode extends Node, TKwOverflowNode {
CallNode call;
KwOverflowNode() { this = TKwOverflowNode(call, _) }
override string toString() { result = "KwOverflowNode for " + call.getNode().toString() }
override Location getLocation() { result = call.getLocation() }
}
/**
* The node representing the synthetic argument of a call that is unpacked from a dictionary
* argument.
*/
class KwUnpacked extends Node, TKwUnpacked {
CallNode call;
string name;
KwUnpacked() { this = TKwUnpacked(call, _, name) }
override string toString() { result = "KwUnpacked " + name }
override Location getLocation() { result = call.getLocation() }
}
/**
* A node that controls whether other nodes are evaluated.
*/
class GuardNode extends ControlFlowNode {
ConditionBlock conditionBlock;
GuardNode() { this = conditionBlock.getLastNode() }
/** Holds if this guard controls block `b` upon evaluating to `branch`. */
predicate controlsBlock(BasicBlock b, boolean branch) { conditionBlock.controls(b, branch) }
}
/**
* A guard that validates some expression.
*
* To use this in a configuration, extend the class and provide a
* characteristic predicate precisely specifying the guard, and override
* `checks` to specify what is being validated and in which branch.
*
* It is important that all extending classes in scope are disjoint.
*/
class BarrierGuard extends GuardNode {
/** Holds if this guard validates `node` upon evaluating to `branch`. */
abstract predicate checks(ControlFlowNode node, boolean branch);
/** Gets a node guarded by this guard. */
final ExprNode getAGuardedNode() {
exists(EssaDefinition def, ControlFlowNode node, boolean branch |
AdjacentUses::useOfDef(def, node) and
this.checks(node, branch) and
AdjacentUses::useOfDef(def, result.asCfgNode()) and
this.controlsBlock(result.asCfgNode().getBasicBlock(), branch)
)
}
}
/**
* A reference contained in an object. This is either a field or a property.
*/
newtype TContent =
/** An element of a list. */
TListElementContent() or
/** An element of a set. */
TSetElementContent() or
/** An element of a tuple at a specifik index. */
TTupleElementContent(int index) { exists(any(TupleNode tn).getElement(index)) } or
/** An element of a dictionary under a specific key. */
TDictionaryElementContent(string key) {
key = any(KeyValuePair kvp).getKey().(StrConst).getS()
or
key = any(Keyword kw).getArg()
} or
/** An element of a dictionary at any key. */
TDictionaryElementAnyContent() or
/** An object attribute. */
TAttributeContent(string attr) { attr = any(Attribute a).getName() }
class Content extends TContent {
/** Gets a textual representation of this element. */
string toString() { result = "Content" }
}
class ListElementContent extends TListElementContent, Content {
override string toString() { result = "List element" }
}
class SetElementContent extends TSetElementContent, Content {
override string toString() { result = "Set element" }
}
class TupleElementContent extends TTupleElementContent, Content {
int index;
TupleElementContent() { this = TTupleElementContent(index) }
/** Gets the index for this tuple element. */
int getIndex() { result = index }
override string toString() { result = "Tuple element at index " + index.toString() }
}
class DictionaryElementContent extends TDictionaryElementContent, Content {
string key;
DictionaryElementContent() { this = TDictionaryElementContent(key) }
/** Gets the key for this dictionary element. */
string getKey() { result = key }
override string toString() { result = "Dictionary element at key " + key }
}
class DictionaryElementAnyContent extends TDictionaryElementAnyContent, Content {
override string toString() { result = "Any dictionary element" }
}
class AttributeContent extends TAttributeContent, Content {
private string attr;
AttributeContent() { this = TAttributeContent(attr) }
/** Gets the name of the attribute under which this content is stored. */
string getAttribute() { result = attr }
override string toString() { result = "Attribute " + attr }
}