mirror of
https://github.com/github/codeql.git
synced 2026-04-23 07:45:17 +02:00
Merge pull request #11376 from RasmusWL/call-graph-code
Python: New type-tracking based call-graph
This commit is contained in:
@@ -125,7 +125,7 @@ class ControlFlowNode extends @py_flow_node {
|
||||
/** Gets a textual representation of this element. */
|
||||
cached
|
||||
string toString() {
|
||||
Stages::DataFlow::ref() and
|
||||
Stages::AST::ref() and
|
||||
exists(Scope s | s.getEntryNode() = this | result = "Entry node for " + s.toString())
|
||||
or
|
||||
exists(Scope s | s.getANormalExit() = this | result = "Exit node for " + s.toString())
|
||||
@@ -411,6 +411,12 @@ class CallNode extends ControlFlowNode {
|
||||
result.getNode() = this.getNode().getStarArg() and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
}
|
||||
|
||||
/** Gets a dictionary (**) argument of this call, if any. */
|
||||
ControlFlowNode getKwargs() {
|
||||
result.getNode() = this.getNode().getKwargs() and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
}
|
||||
}
|
||||
|
||||
/** A control flow corresponding to an attribute expression, such as `value.attr` */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,838 +0,0 @@
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Points-to based call-graph.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import DataFlowPublic
|
||||
private import semmle.python.SpecialMethods
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
|
||||
/** A parameter position represented by an integer. */
|
||||
class ParameterPosition extends int {
|
||||
ParameterPosition() { exists(any(DataFlowCallable c).getParameter(this)) }
|
||||
|
||||
/** Holds if this position represents a positional parameter at position `pos`. */
|
||||
predicate isPositional(int pos) { this = pos } // with the current representation, all parameters are positional
|
||||
}
|
||||
|
||||
/** An argument position represented by an integer. */
|
||||
class ArgumentPosition extends int {
|
||||
ArgumentPosition() { this in [-2, -1] or exists(any(Call c).getArg(this)) }
|
||||
|
||||
/** Holds if this position represents a positional argument at position `pos`. */
|
||||
predicate isPositional(int pos) { this = pos } // with the current representation, all arguments are positional
|
||||
}
|
||||
|
||||
/** Holds if arguments at position `apos` match parameters at position `ppos`. */
|
||||
pragma[inline]
|
||||
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) { ppos = apos }
|
||||
|
||||
/**
|
||||
* Computes routing of arguments to parameters
|
||||
*
|
||||
* When a call contains more positional arguments than there are positional parameters,
|
||||
* the extra positional arguments are passed as a tuple to a starred parameter. This is
|
||||
* achieved by synthesizing a node `TPosOverflowNode(call, callable)`
|
||||
* that represents the tuple of extra positional arguments. There is a store step from each
|
||||
* extra positional argument to this node.
|
||||
*
|
||||
* CURRENTLY NOT SUPPORTED:
|
||||
* When a call contains an iterable unpacking argument, such as `func(*args)`, it is expanded into positional arguments.
|
||||
*
|
||||
* CURRENTLY NOT SUPPORTED:
|
||||
* If a call contains an iterable unpacking argument, such as `func(*args)`, and the callee contains a starred argument, any extra
|
||||
* positional arguments are passed to the starred argument.
|
||||
*
|
||||
* When a call contains keyword arguments that do not correspond to keyword parameters, these
|
||||
* extra keyword arguments are passed as a dictionary to a doubly starred parameter. This is
|
||||
* achieved by synthesizing a node `TKwOverflowNode(call, callable)`
|
||||
* that represents the dictionary of extra keyword arguments. There is a store step from each
|
||||
* extra keyword argument to this node.
|
||||
*
|
||||
* When a call contains a dictionary unpacking argument, such as `func(**kwargs)`, with entries corresponding to a keyword parameter,
|
||||
* the value at such a key is unpacked and passed to the parameter. This is achieved
|
||||
* by synthesizing an argument node `TKwUnpacked(call, callable, name)` representing the unpacked
|
||||
* value. This node is used as the argument passed to the matching keyword parameter. There is a read
|
||||
* step from the dictionary argument to the synthesized argument node.
|
||||
*
|
||||
* When a call contains a dictionary unpacking argument, such as `func(**kwargs)`, and the callee contains a doubly starred parameter,
|
||||
* entries which are not unpacked are passed to the doubly starred parameter. This is achieved by
|
||||
* adding a dataflow step from the dictionary argument to `TKwOverflowNode(call, callable)` and a
|
||||
* step to clear content of that node at any unpacked keys.
|
||||
*
|
||||
* ## Examples:
|
||||
* Assume that we have the callable
|
||||
* ```python
|
||||
* def f(x, y, *t, **d):
|
||||
* pass
|
||||
* ```
|
||||
* Then the call
|
||||
* ```python
|
||||
* f(0, 1, 2, a=3)
|
||||
* ```
|
||||
* will be modeled as
|
||||
* ```python
|
||||
* f(0, 1, [*t], [**d])
|
||||
* ```
|
||||
* where `[` and `]` denotes synthesized nodes, so `[*t]` is the synthesized tuple argument
|
||||
* `TPosOverflowNode` and `[**d]` is the synthesized dictionary argument `TKwOverflowNode`.
|
||||
* There will be a store step from `2` to `[*t]` at pos `0` and one from `3` to `[**d]` at key
|
||||
* `a`.
|
||||
*
|
||||
* For the call
|
||||
* ```python
|
||||
* f(0, **{"y": 1, "a": 3})
|
||||
* ```
|
||||
* no tuple argument is synthesized. It is modeled as
|
||||
* ```python
|
||||
* f(0, [y=1], [**d])
|
||||
* ```
|
||||
* where `[y=1]` is the synthesized unpacked argument `TKwUnpacked` (with `name` = `y`). There is
|
||||
* a read step from `**{"y": 1, "a": 3}` to `[y=1]` at key `y` to get the value passed to the parameter
|
||||
* `y`. There is a dataflow step from `**{"y": 1, "a": 3}` to `[**d]` to transfer the content and
|
||||
* a clearing of content at key `y` for node `[**d]`, since that value has been unpacked.
|
||||
*/
|
||||
module ArgumentPassing {
|
||||
/**
|
||||
* Holds if `call` represents a `DataFlowCall` to a `DataFlowCallable` represented by `callable`.
|
||||
*
|
||||
* It _may not_ be the case that `call = callable.getACall()`, i.e. if `call` represents a `ClassCall`.
|
||||
*
|
||||
* Used to limit the size of predicates.
|
||||
*/
|
||||
predicate connects(CallNode call, CallableValue callable) {
|
||||
exists(NormalCall c |
|
||||
call = c.getNode() and
|
||||
callable = c.getCallable().getCallableValue()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `n`th parameter of `callable`.
|
||||
* If the callable has a starred parameter, say `*tuple`, that is matched with `n=-1`.
|
||||
* If the callable has a doubly starred parameter, say `**dict`, that is matched with `n=-2`.
|
||||
* Note that, unlike other languages, we do _not_ use -1 for the position of `self` in Python,
|
||||
* as it is an explicit parameter at position 0.
|
||||
*/
|
||||
NameNode getParameter(CallableValue callable, int n) {
|
||||
// positional parameter
|
||||
result = callable.getParameter(n)
|
||||
or
|
||||
// starred parameter, `*tuple`
|
||||
exists(Function f |
|
||||
f = callable.getScope() and
|
||||
n = -1 and
|
||||
result = f.getVararg().getAFlowNode()
|
||||
)
|
||||
or
|
||||
// doubly starred parameter, `**dict`
|
||||
exists(Function f |
|
||||
f = callable.getScope() and
|
||||
n = -2 and
|
||||
result = f.getKwarg().getAFlowNode()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A type representing a mapping from argument indices to parameter indices.
|
||||
* We currently use two mappings: NoShift, the identity, used for ordinary
|
||||
* function calls, and ShiftOneUp which is used for calls where an extra argument
|
||||
* is inserted. These include method calls, constructor calls and class calls.
|
||||
* In these calls, the argument at index `n` is mapped to the parameter at position `n+1`.
|
||||
*/
|
||||
newtype TArgParamMapping =
|
||||
TNoShift() or
|
||||
TShiftOneUp()
|
||||
|
||||
/** A mapping used for parameter passing. */
|
||||
abstract class ArgParamMapping extends TArgParamMapping {
|
||||
/** Gets the index of the parameter that corresponds to the argument at index `argN`. */
|
||||
bindingset[argN]
|
||||
abstract int getParamN(int argN);
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
abstract string toString();
|
||||
}
|
||||
|
||||
/** A mapping that passes argument `n` to parameter `n`. */
|
||||
class NoShift extends ArgParamMapping, TNoShift {
|
||||
NoShift() { this = TNoShift() }
|
||||
|
||||
override string toString() { result = "NoShift [n -> n]" }
|
||||
|
||||
bindingset[argN]
|
||||
override int getParamN(int argN) { result = argN }
|
||||
}
|
||||
|
||||
/** A mapping that passes argument `n` to parameter `n+1`. */
|
||||
class ShiftOneUp extends ArgParamMapping, TShiftOneUp {
|
||||
ShiftOneUp() { this = TShiftOneUp() }
|
||||
|
||||
override string toString() { result = "ShiftOneUp [n -> n+1]" }
|
||||
|
||||
bindingset[argN]
|
||||
override int getParamN(int argN) { result = argN + 1 }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node representing the argument to `call` that is passed to the parameter at
|
||||
* (zero-based) index `paramN` in `callable`. If this is a positional argument, it must appear
|
||||
* at an index, `argN`, in `call` which satisfies `paramN = mapping.getParamN(argN)`.
|
||||
*
|
||||
* `mapping` will be the identity for function calls, but not for method- or constructor calls,
|
||||
* where the first parameter is `self` and the first positional argument is passed to the second positional parameter.
|
||||
* Similarly for classmethod calls, where the first parameter is `cls`.
|
||||
*
|
||||
* NOT SUPPORTED: Keyword-only parameters.
|
||||
*/
|
||||
Node getArg(CallNode call, ArgParamMapping mapping, CallableValue callable, int paramN) {
|
||||
connects(call, callable) and
|
||||
(
|
||||
// positional argument
|
||||
exists(int argN |
|
||||
paramN = mapping.getParamN(argN) and
|
||||
result = TCfgNode(call.getArg(argN))
|
||||
)
|
||||
or
|
||||
// keyword argument
|
||||
// TODO: Since `getArgName` have no results for keyword-only parameters,
|
||||
// these are currently not supported.
|
||||
exists(Function f, string argName |
|
||||
f = callable.getScope() and
|
||||
f.getArgName(paramN) = argName and
|
||||
result = TCfgNode(call.getArgByName(unbind_string(argName)))
|
||||
)
|
||||
or
|
||||
// a synthesized argument passed to the starred parameter (at position -1)
|
||||
callable.getScope().hasVarArg() and
|
||||
paramN = -1 and
|
||||
result = TPosOverflowNode(call, callable)
|
||||
or
|
||||
// a synthesized argument passed to the doubly starred parameter (at position -2)
|
||||
callable.getScope().hasKwArg() and
|
||||
paramN = -2 and
|
||||
result = TKwOverflowNode(call, callable)
|
||||
or
|
||||
// argument unpacked from dict
|
||||
exists(string name |
|
||||
call_unpacks(call, mapping, callable, name, paramN) and
|
||||
result = TKwUnpackedNode(call, callable, name)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Currently required in `getArg` in order to prevent a bad join. */
|
||||
bindingset[result, s]
|
||||
private string unbind_string(string s) { result <= s and s <= result }
|
||||
|
||||
/** Gets the control flow node that is passed as the `n`th overflow positional argument. */
|
||||
ControlFlowNode getPositionalOverflowArg(CallNode call, CallableValue callable, int n) {
|
||||
connects(call, callable) and
|
||||
exists(Function f, int posCount, int argNr |
|
||||
f = callable.getScope() and
|
||||
f.hasVarArg() and
|
||||
posCount = f.getPositionalParameterCount() and
|
||||
result = call.getArg(argNr) and
|
||||
argNr >= posCount and
|
||||
argNr = posCount + n
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the control flow node that is passed as the overflow keyword argument with key `key`. */
|
||||
ControlFlowNode getKeywordOverflowArg(CallNode call, CallableValue callable, string key) {
|
||||
connects(call, callable) and
|
||||
exists(Function f |
|
||||
f = callable.getScope() and
|
||||
f.hasKwArg() and
|
||||
not exists(f.getArgByName(key)) and
|
||||
result = call.getArgByName(key)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `call` unpacks a dictionary argument in order to pass it via `name`.
|
||||
* It will then be passed to the parameter of `callable` at index `paramN`.
|
||||
*/
|
||||
predicate call_unpacks(
|
||||
CallNode call, ArgParamMapping mapping, CallableValue callable, string name, int paramN
|
||||
) {
|
||||
connects(call, callable) and
|
||||
exists(Function f |
|
||||
f = callable.getScope() and
|
||||
not exists(int argN | paramN = mapping.getParamN(argN) | exists(call.getArg(argN))) and // no positional argument available
|
||||
name = f.getArgName(paramN) and
|
||||
// not exists(call.getArgByName(name)) and // only matches keyword arguments not preceded by **
|
||||
// TODO: make the below logic respect control flow splitting (by not going to the AST).
|
||||
not call.getNode().getANamedArg().(Keyword).getArg() = name and // no keyword argument available
|
||||
paramN >= 0 and
|
||||
paramN < f.getPositionalParameterCount() + f.getKeywordOnlyParameterCount() and
|
||||
exists(call.getNode().getKwargs()) // dict argument available
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
import ArgumentPassing
|
||||
|
||||
/** A callable defined in library code, identified by a unique string. */
|
||||
abstract class LibraryCallable extends string {
|
||||
bindingset[this]
|
||||
LibraryCallable() { any() }
|
||||
|
||||
/** Gets a call to this library callable. */
|
||||
abstract CallCfgNode getACall();
|
||||
|
||||
/** Gets a data-flow node, where this library callable is used as a call-back. */
|
||||
abstract ArgumentNode getACallback();
|
||||
}
|
||||
|
||||
/**
|
||||
* IPA type for DataFlowCallable.
|
||||
*
|
||||
* A callable is either a function value, a class value, or a module (for enclosing `ModuleVariableNode`s).
|
||||
* A module has no calls.
|
||||
*/
|
||||
newtype TDataFlowCallable =
|
||||
TCallableValue(CallableValue callable) {
|
||||
callable instanceof FunctionValue and
|
||||
not callable.(FunctionValue).isLambda()
|
||||
or
|
||||
callable instanceof ClassValue
|
||||
} or
|
||||
TLambda(Function lambda) { lambda.isLambda() } or
|
||||
TModule(Module m) or
|
||||
TLibraryCallable(LibraryCallable callable)
|
||||
|
||||
/** A callable. */
|
||||
class DataFlowCallable extends TDataFlowCallable {
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { result = "DataFlowCallable" }
|
||||
|
||||
/** Gets a call to this callable. */
|
||||
CallNode getACall() { none() }
|
||||
|
||||
/** Gets the scope of this callable */
|
||||
Scope getScope() { none() }
|
||||
|
||||
/** Gets the specified parameter of this callable */
|
||||
NameNode getParameter(int n) { none() }
|
||||
|
||||
/** Gets the name of this callable. */
|
||||
string getName() { none() }
|
||||
|
||||
/** Gets a callable value for this callable, if any. */
|
||||
CallableValue getCallableValue() { none() }
|
||||
|
||||
/** Gets the underlying library callable, if any. */
|
||||
LibraryCallable asLibraryCallable() { this = TLibraryCallable(result) }
|
||||
|
||||
Location getLocation() { none() }
|
||||
}
|
||||
|
||||
/** A class representing a callable value. */
|
||||
class DataFlowCallableValue extends DataFlowCallable, TCallableValue {
|
||||
CallableValue callable;
|
||||
|
||||
DataFlowCallableValue() { this = TCallableValue(callable) }
|
||||
|
||||
override string toString() { result = callable.toString() }
|
||||
|
||||
override CallNode getACall() { result = callable.getACall() }
|
||||
|
||||
override Scope getScope() { result = callable.getScope() }
|
||||
|
||||
override NameNode getParameter(int n) { result = getParameter(callable, n) }
|
||||
|
||||
override string getName() { result = callable.getName() }
|
||||
|
||||
override CallableValue getCallableValue() { result = callable }
|
||||
}
|
||||
|
||||
/** A class representing a callable lambda. */
|
||||
class DataFlowLambda extends DataFlowCallable, TLambda {
|
||||
Function lambda;
|
||||
|
||||
DataFlowLambda() { this = TLambda(lambda) }
|
||||
|
||||
override string toString() { result = lambda.toString() }
|
||||
|
||||
override CallNode getACall() { result = this.getCallableValue().getACall() }
|
||||
|
||||
override Scope getScope() { result = lambda.getEvaluatingScope() }
|
||||
|
||||
override NameNode getParameter(int n) { result = getParameter(this.getCallableValue(), n) }
|
||||
|
||||
override string getName() { result = "Lambda callable" }
|
||||
|
||||
override FunctionValue getCallableValue() {
|
||||
result.getOrigin().getNode() = lambda.getDefinition()
|
||||
}
|
||||
|
||||
Expr getDefinition() { result = lambda.getDefinition() }
|
||||
}
|
||||
|
||||
/** A class representing the scope in which a `ModuleVariableNode` appears. */
|
||||
class DataFlowModuleScope extends DataFlowCallable, TModule {
|
||||
Module mod;
|
||||
|
||||
DataFlowModuleScope() { this = TModule(mod) }
|
||||
|
||||
override string toString() { result = mod.toString() }
|
||||
|
||||
override CallNode getACall() { none() }
|
||||
|
||||
override Scope getScope() { result = mod }
|
||||
|
||||
override NameNode getParameter(int n) { none() }
|
||||
|
||||
override string getName() { result = mod.getName() }
|
||||
|
||||
override CallableValue getCallableValue() { none() }
|
||||
}
|
||||
|
||||
class LibraryCallableValue extends DataFlowCallable, TLibraryCallable {
|
||||
LibraryCallable callable;
|
||||
|
||||
LibraryCallableValue() { this = TLibraryCallable(callable) }
|
||||
|
||||
override string toString() { result = callable.toString() }
|
||||
|
||||
override CallNode getACall() { result = callable.getACall().getNode() }
|
||||
|
||||
/** Gets a data-flow node, where this library callable is used as a call-back. */
|
||||
ArgumentNode getACallback() { result = callable.getACallback() }
|
||||
|
||||
override Scope getScope() { none() }
|
||||
|
||||
override NameNode getParameter(int n) { none() }
|
||||
|
||||
override string getName() { result = callable }
|
||||
|
||||
override LibraryCallable asLibraryCallable() { result = callable }
|
||||
}
|
||||
|
||||
/**
|
||||
* IPA type for DataFlowCall.
|
||||
*
|
||||
* Calls corresponding to `CallNode`s are either to callable values or to classes.
|
||||
* The latter is directed to the callable corresponding to the `__init__` method of the class.
|
||||
*
|
||||
* An `__init__` method can also be called directly, so that the callable can be targeted by
|
||||
* different types of calls. In that case, the parameter mappings will be different,
|
||||
* as the class call will synthesize an argument node to be mapped to the `self` parameter.
|
||||
*
|
||||
* A call corresponding to a special method call is handled by the corresponding `SpecialMethodCallNode`.
|
||||
*
|
||||
* TODO: Add `TClassMethodCall` mapping `cls` appropriately.
|
||||
*/
|
||||
newtype TDataFlowCall =
|
||||
/**
|
||||
* Includes function calls, method calls, class calls and library calls.
|
||||
* All these will be associated with a `CallNode`.
|
||||
*/
|
||||
TNormalCall(CallNode call) or
|
||||
/**
|
||||
* Includes calls to special methods.
|
||||
* These will be associated with a `SpecialMethodCallNode`.
|
||||
*/
|
||||
TSpecialCall(SpecialMethodCallNode special) or
|
||||
/** A synthesized call inside a summarized callable */
|
||||
TSummaryCall(FlowSummaryImpl::Public::SummarizedCallable c, Node receiver) {
|
||||
FlowSummaryImpl::Private::summaryCallbackRange(c, receiver)
|
||||
}
|
||||
|
||||
/** A call found in the program source (as opposed to a synthesised summary call). */
|
||||
class TExtractedDataFlowCall = TSpecialCall or TNormalCall;
|
||||
|
||||
/** A call that is taken into account by the global data flow computation. */
|
||||
abstract class DataFlowCall extends TDataFlowCall {
|
||||
/** Gets a textual representation of this element. */
|
||||
abstract string toString();
|
||||
|
||||
/** Get the callable to which this call goes, if such exists. */
|
||||
abstract DataFlowCallable getCallable();
|
||||
|
||||
/**
|
||||
* Gets the argument to this call that will be sent
|
||||
* to the `n`th parameter of the callable, if any.
|
||||
*/
|
||||
abstract Node getArg(int n);
|
||||
|
||||
/** Get the control flow node representing this call, if any. */
|
||||
abstract ControlFlowNode getNode();
|
||||
|
||||
/** Gets the enclosing callable of this call. */
|
||||
abstract DataFlowCallable getEnclosingCallable();
|
||||
|
||||
/** Gets the location of this dataflow call. */
|
||||
abstract Location getLocation();
|
||||
|
||||
/**
|
||||
* 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://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
}
|
||||
|
||||
/** A call found in the program source (as opposed to a synthesised call). */
|
||||
abstract class ExtractedDataFlowCall extends DataFlowCall, TExtractedDataFlowCall {
|
||||
final override Location getLocation() { result = this.getNode().getLocation() }
|
||||
|
||||
abstract override DataFlowCallable getCallable();
|
||||
|
||||
abstract override Node getArg(int n);
|
||||
|
||||
abstract override ControlFlowNode getNode();
|
||||
}
|
||||
|
||||
/** A call associated with a `CallNode`. */
|
||||
class NormalCall extends ExtractedDataFlowCall, TNormalCall {
|
||||
CallNode call;
|
||||
|
||||
NormalCall() { this = TNormalCall(call) }
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
|
||||
abstract override Node getArg(int n);
|
||||
|
||||
override CallNode getNode() { result = call }
|
||||
|
||||
abstract override DataFlowCallable getCallable();
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result.getScope() = call.getNode().getScope() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a function.
|
||||
* This excludes calls to bound methods, classes, and special methods.
|
||||
* Bound method calls and class calls insert an argument for the explicit
|
||||
* `self` parameter, and special method calls have special argument passing.
|
||||
*/
|
||||
class FunctionCall extends NormalCall {
|
||||
DataFlowCallableValue callable;
|
||||
|
||||
FunctionCall() {
|
||||
call = any(FunctionValue f).getAFunctionCall() and
|
||||
call = callable.getACall()
|
||||
}
|
||||
|
||||
override Node getArg(int n) { result = getArg(call, TNoShift(), callable.getCallableValue(), n) }
|
||||
|
||||
override DataFlowCallable getCallable() { result = callable }
|
||||
}
|
||||
|
||||
/** A call to a lambda. */
|
||||
class LambdaCall extends NormalCall {
|
||||
DataFlowLambda callable;
|
||||
|
||||
LambdaCall() {
|
||||
call = callable.getACall() and
|
||||
callable = TLambda(any(Function f))
|
||||
}
|
||||
|
||||
override Node getArg(int n) { result = getArg(call, TNoShift(), callable.getCallableValue(), n) }
|
||||
|
||||
override DataFlowCallable getCallable() { result = callable }
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a call to a bound method call.
|
||||
* The node representing the instance is inserted as argument to the `self` parameter.
|
||||
*/
|
||||
class MethodCall extends NormalCall {
|
||||
FunctionValue bm;
|
||||
|
||||
MethodCall() { call = bm.getAMethodCall() }
|
||||
|
||||
private CallableValue getCallableValue() { result = bm }
|
||||
|
||||
override Node getArg(int n) {
|
||||
n > 0 and result = getArg(call, TShiftOneUp(), this.getCallableValue(), n)
|
||||
or
|
||||
n = 0 and result = TCfgNode(call.getFunction().(AttrNode).getObject())
|
||||
}
|
||||
|
||||
override DataFlowCallable getCallable() { result = TCallableValue(this.getCallableValue()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a call to a class.
|
||||
* The pre-update node for the call is inserted as argument to the `self` parameter.
|
||||
* That makes the call node be the post-update node holding the value of the object
|
||||
* after the constructor has run.
|
||||
*/
|
||||
class ClassCall extends NormalCall {
|
||||
ClassValue c;
|
||||
|
||||
ClassCall() {
|
||||
not c.isAbsent() and
|
||||
call = c.getACall()
|
||||
}
|
||||
|
||||
private CallableValue getCallableValue() { c.getScope().getInitMethod() = result.getScope() }
|
||||
|
||||
override Node getArg(int n) {
|
||||
n > 0 and result = getArg(call, TShiftOneUp(), this.getCallableValue(), n)
|
||||
or
|
||||
n = 0 and result = TSyntheticPreUpdateNode(TCfgNode(call))
|
||||
}
|
||||
|
||||
override DataFlowCallable getCallable() { result = TCallableValue(this.getCallableValue()) }
|
||||
}
|
||||
|
||||
/** A call to a special method. */
|
||||
class SpecialCall extends ExtractedDataFlowCall, TSpecialCall {
|
||||
SpecialMethodCallNode special;
|
||||
|
||||
SpecialCall() { this = TSpecialCall(special) }
|
||||
|
||||
override string toString() { result = special.toString() }
|
||||
|
||||
override Node getArg(int n) { result = TCfgNode(special.(SpecialMethod::Potential).getArg(n)) }
|
||||
|
||||
override ControlFlowNode getNode() { result = special }
|
||||
|
||||
override DataFlowCallable getCallable() {
|
||||
result = TCallableValue(special.getResolvedSpecialMethod())
|
||||
}
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() {
|
||||
result.getScope() = special.getNode().getScope()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a summarized callable, a `LibraryCallable`.
|
||||
*
|
||||
* We currently exclude all resolved calls. This means that a call to, say, `map`, which
|
||||
* is a `ClassCall`, cannot currently be given a summary.
|
||||
* We hope to lift this restriction in the future and include all potential calls to summaries
|
||||
* in this class.
|
||||
*/
|
||||
class LibraryCall extends NormalCall {
|
||||
LibraryCall() {
|
||||
// TODO: share this with `resolvedCall`
|
||||
not (
|
||||
call = any(DataFlowCallableValue cv).getACall()
|
||||
or
|
||||
call = any(DataFlowLambda l).getACall()
|
||||
or
|
||||
// TODO: this should be covered by `DataFlowCallableValue`, but a `ClassValue` is not a `CallableValue`.
|
||||
call = any(ClassValue c).getACall()
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Implement Python calling convention?
|
||||
override Node getArg(int n) { result = TCfgNode(call.getArg(n)) }
|
||||
|
||||
// We cannot refer to a `LibraryCallable` here,
|
||||
// as that could in turn refer to type tracking.
|
||||
// This call will be tied to a `LibraryCallable` via
|
||||
// `getViableCallabe` when the global data flow is assembled.
|
||||
override DataFlowCallable getCallable() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A synthesized call inside a callable with a flow summary.
|
||||
*
|
||||
* For example, in
|
||||
* ```python
|
||||
* map(lambda x: x + 1, [1, 2, 3])
|
||||
* ```
|
||||
*
|
||||
* there is a synthesized call to the lambda argument inside `map`.
|
||||
*/
|
||||
class SummaryCall extends DataFlowCall, TSummaryCall {
|
||||
private FlowSummaryImpl::Public::SummarizedCallable c;
|
||||
private Node receiver;
|
||||
|
||||
SummaryCall() { this = TSummaryCall(c, receiver) }
|
||||
|
||||
/** Gets the data flow node that this call targets. */
|
||||
Node getReceiver() { result = receiver }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = c }
|
||||
|
||||
override DataFlowCallable getCallable() { none() }
|
||||
|
||||
override Node getArg(int n) { none() }
|
||||
|
||||
override ControlFlowNode getNode() { none() }
|
||||
|
||||
override string toString() { result = "[summary] call to " + receiver + " in " + c }
|
||||
|
||||
override Location getLocation() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of a parameter at function entry, viewed as a node in a data
|
||||
* flow graph.
|
||||
*/
|
||||
abstract class ParameterNodeImpl extends Node {
|
||||
abstract Parameter getParameter();
|
||||
|
||||
/**
|
||||
* Holds if this node is the parameter of callable `c` at the
|
||||
* (zero-based) index `i`.
|
||||
*/
|
||||
abstract predicate isParameterOf(DataFlowCallable c, int i);
|
||||
}
|
||||
|
||||
/** A parameter for a library callable with a flow summary. */
|
||||
class SummaryParameterNode extends ParameterNodeImpl, TSummaryParameterNode {
|
||||
private FlowSummaryImpl::Public::SummarizedCallable sc;
|
||||
private int pos;
|
||||
|
||||
SummaryParameterNode() { this = TSummaryParameterNode(sc, pos) }
|
||||
|
||||
override Parameter getParameter() { none() }
|
||||
|
||||
override predicate isParameterOf(DataFlowCallable c, int i) {
|
||||
sc = c.asLibraryCallable() and i = pos
|
||||
}
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = sc }
|
||||
|
||||
override string toString() { result = "parameter " + pos + " of " + sc }
|
||||
|
||||
// Hack to return "empty location"
|
||||
override predicate hasLocationInfo(
|
||||
string file, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
file = "" and
|
||||
startline = 0 and
|
||||
startcolumn = 0 and
|
||||
endline = 0 and
|
||||
endcolumn = 0
|
||||
}
|
||||
}
|
||||
|
||||
/** A data-flow node used to model flow summaries. */
|
||||
class SummaryNode extends Node, TSummaryNode {
|
||||
private FlowSummaryImpl::Public::SummarizedCallable c;
|
||||
private FlowSummaryImpl::Private::SummaryNodeState state;
|
||||
|
||||
SummaryNode() { this = TSummaryNode(c, state) }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = c }
|
||||
|
||||
override string toString() { result = "[summary] " + state + " in " + c }
|
||||
|
||||
// Hack to return "empty location"
|
||||
override predicate hasLocationInfo(
|
||||
string file, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
file = "" and
|
||||
startline = 0 and
|
||||
startcolumn = 0 and
|
||||
endline = 0 and
|
||||
endcolumn = 0
|
||||
}
|
||||
}
|
||||
|
||||
private class SummaryReturnNode extends SummaryNode, ReturnNode {
|
||||
private ReturnKind rk;
|
||||
|
||||
SummaryReturnNode() { FlowSummaryImpl::Private::summaryReturnNode(this, rk) }
|
||||
|
||||
override ReturnKind getKind() { result = rk }
|
||||
}
|
||||
|
||||
private class SummaryArgumentNode extends SummaryNode, ArgumentNode {
|
||||
SummaryArgumentNode() { FlowSummaryImpl::Private::summaryArgumentNode(_, this, _) }
|
||||
|
||||
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
FlowSummaryImpl::Private::summaryArgumentNode(call, this, pos)
|
||||
}
|
||||
}
|
||||
|
||||
private class SummaryPostUpdateNode extends SummaryNode, PostUpdateNode {
|
||||
private Node pre;
|
||||
|
||||
SummaryPostUpdateNode() { FlowSummaryImpl::Private::summaryPostUpdateNode(this, pre) }
|
||||
|
||||
override Node getPreUpdateNode() { result = pre }
|
||||
}
|
||||
|
||||
/** Gets a viable run-time target for the call `call`. */
|
||||
DataFlowCallable viableCallable(ExtractedDataFlowCall call) {
|
||||
result = call.getCallable()
|
||||
or
|
||||
// A call to a library callable with a flow summary
|
||||
// In this situation we can not resolve the callable from the call,
|
||||
// as that would make data flow depend on type tracking.
|
||||
// Instead we resolve the call from the summary.
|
||||
exists(LibraryCallable callable |
|
||||
result = TLibraryCallable(callable) and
|
||||
call.getNode() = callable.getACall().getNode()
|
||||
)
|
||||
}
|
||||
|
||||
private newtype TReturnKind = TNormalReturnKind()
|
||||
|
||||
/**
|
||||
* A return kind. A return kind describes how a value can be returned
|
||||
* from a callable. For Python, this is simply a method return.
|
||||
*/
|
||||
class ReturnKind extends TReturnKind {
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { result = "return" }
|
||||
}
|
||||
|
||||
/** A data flow node that represents a value returned by a callable. */
|
||||
abstract class ReturnNode extends Node {
|
||||
/** Gets the kind of this return node. */
|
||||
ReturnKind getKind() { any() }
|
||||
}
|
||||
|
||||
/** A data flow node that represents a value returned by a callable. */
|
||||
class ExtractedReturnNode extends ReturnNode, CfgNode {
|
||||
// See `TaintTrackingImplementation::returnFlowStep`
|
||||
ExtractedReturnNode() { node = any(Return ret).getValue().getAFlowNode() }
|
||||
|
||||
override ReturnKind getKind() { any() }
|
||||
}
|
||||
|
||||
/** A data-flow node that represents the output of a call. */
|
||||
abstract class OutNode extends Node {
|
||||
/** Gets the underlying call, where this node is a corresponding output of kind `kind`. */
|
||||
abstract DataFlowCall getCall(ReturnKind kind);
|
||||
}
|
||||
|
||||
private module OutNodes {
|
||||
/**
|
||||
* A data-flow node that reads a value returned directly by a callable.
|
||||
*/
|
||||
class ExprOutNode extends OutNode, ExprNode {
|
||||
private DataFlowCall call;
|
||||
|
||||
ExprOutNode() { call.(ExtractedDataFlowCall).getNode() = this.getNode() }
|
||||
|
||||
override DataFlowCall getCall(ReturnKind kind) {
|
||||
result = call and
|
||||
kind = kind
|
||||
}
|
||||
}
|
||||
|
||||
private class SummaryOutNode extends SummaryNode, OutNode {
|
||||
SummaryOutNode() { FlowSummaryImpl::Private::summaryOutNode(_, this, _) }
|
||||
|
||||
override DataFlowCall getCall(ReturnKind kind) {
|
||||
FlowSummaryImpl::Private::summaryOutNode(result, this, kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that can read the value returned from `call` with return kind
|
||||
* `kind`.
|
||||
*/
|
||||
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { call = result.getCall(kind) }
|
||||
@@ -16,7 +16,7 @@ private import semmle.python.Frameworks
|
||||
// make it more digestible.
|
||||
import MatchUnpacking
|
||||
import IterableUnpacking
|
||||
import DataFlowDispatchPointsTo
|
||||
import DataFlowDispatch
|
||||
|
||||
/** Gets the callable in which this node occurs. */
|
||||
DataFlowCallable nodeGetEnclosingCallable(Node n) { result = n.getEnclosingCallable() }
|
||||
@@ -39,162 +39,267 @@ predicate isArgumentNode(ArgumentNode arg, DataFlowCall c, ArgumentPosition pos)
|
||||
//--------
|
||||
predicate isExpressionNode(ControlFlowNode node) { node.getNode() instanceof Expr }
|
||||
|
||||
/** DEPRECATED: Alias for `SyntheticPreUpdateNode` */
|
||||
deprecated module syntheticPreUpdateNode = SyntheticPreUpdateNode;
|
||||
// =============================================================================
|
||||
// SyntheticPreUpdateNode
|
||||
// =============================================================================
|
||||
class SyntheticPreUpdateNode extends Node, TSyntheticPreUpdateNode {
|
||||
CallNode node;
|
||||
|
||||
/** A module collecting the different reasons for synthesising a pre-update node. */
|
||||
module SyntheticPreUpdateNode {
|
||||
class SyntheticPreUpdateNode extends Node, TSyntheticPreUpdateNode {
|
||||
NeedsSyntheticPreUpdateNode post;
|
||||
SyntheticPreUpdateNode() { this = TSyntheticPreUpdateNode(node) }
|
||||
|
||||
SyntheticPreUpdateNode() { this = TSyntheticPreUpdateNode(post) }
|
||||
/** Gets the node for which this is a synthetic pre-update node. */
|
||||
CfgNode getPostUpdateNode() { result.getNode() = node }
|
||||
|
||||
/** Gets the node for which this is a synthetic pre-update node. */
|
||||
Node getPostUpdateNode() { result = post }
|
||||
override string toString() { result = "[pre] " + node.toString() }
|
||||
|
||||
override string toString() { result = "[pre " + post.label() + "] " + post.toString() }
|
||||
override Scope getScope() { result = node.getScope() }
|
||||
|
||||
override Scope getScope() { result = post.getScope() }
|
||||
|
||||
override Location getLocation() { result = post.getLocation() }
|
||||
}
|
||||
|
||||
/** A data flow node for which we should synthesise an associated pre-update node. */
|
||||
class NeedsSyntheticPreUpdateNode extends PostUpdateNode {
|
||||
NeedsSyntheticPreUpdateNode() { this = objectCreationNode() }
|
||||
|
||||
override Node getPreUpdateNode() { result.(SyntheticPreUpdateNode).getPostUpdateNode() = this }
|
||||
|
||||
/**
|
||||
* Gets the label for this kind of node. This will figure in the textual representation of the synthesized pre-update node.
|
||||
*
|
||||
* There is currently only one reason for needing a pre-update node, so we always use that as the label.
|
||||
*/
|
||||
string label() { result = "objCreate" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls to constructors are treated as post-update nodes for the synthesized argument
|
||||
* that is mapped to the `self` parameter. That way, constructor calls represent the value of the
|
||||
* object after the constructor (currently only `__init__`) has run.
|
||||
*/
|
||||
CfgNode objectCreationNode() { result.getNode() = any(ClassCall c).getNode() }
|
||||
override Location getLocation() { result = node.getLocation() }
|
||||
}
|
||||
|
||||
import SyntheticPreUpdateNode
|
||||
// =============================================================================
|
||||
// *args (StarArgs) related
|
||||
// =============================================================================
|
||||
/**
|
||||
* A (synthetic) data-flow parameter node to capture all positional arguments that
|
||||
* should be passed to the `*args` parameter.
|
||||
*
|
||||
* To handle
|
||||
* ```py
|
||||
* def func(*args):
|
||||
* for arg in args:
|
||||
* sink(arg)
|
||||
*
|
||||
* func(source1, source2, ...)
|
||||
* ```
|
||||
*
|
||||
* we add a synthetic parameter to `func` that accepts any positional argument at (or
|
||||
* after) the index for the `*args` parameter. We add a store step (at any list index) to the real
|
||||
* `*args` parameter. This means we can handle the code above, but if the code had done `sink(args[0])`
|
||||
* we would (wrongly) add flow for `source2` as well.
|
||||
*
|
||||
* To solve this more precisely, we could add a synthetic argument with position `*args`
|
||||
* that had store steps with the correct index (like we do for mapping keyword arguments to a
|
||||
* `**kwargs` parameter). However, if a single call could go to 2 different
|
||||
* targets with `*args` parameters at different positions, as in the example below, it's unclear what
|
||||
* index to store `2` at. For the `foo` callable it should be 1, for the `bar` callable it should be 0.
|
||||
* So this information would need to be encoded in the arguments of a `ArgumentPosition` branch, and
|
||||
* one of the arguments would be which callable is the target. However, we cannot build `ArgumentPosition`
|
||||
* branches based on the call-graph, so this strategy doesn't work.
|
||||
*
|
||||
* Another approach to solving it precisely is to add multiple synthetic parameters that have store steps
|
||||
* to the real `*args` parameter. So for the example below, `foo` would need to have synthetic parameter
|
||||
* nodes for indexes 1 and 2 (which would have store step for index 0 and 1 of the `*args` parameter),
|
||||
* and `bar` would need it for indexes 1, 2, and 3. The question becomes how many synthetic parameters to
|
||||
* create, which _must_ be `max(Call call, int i | exists(call.getArg(i)))`, since (again) we can't base
|
||||
* this on the call-graph. And each function with a `*args` parameter would need this many extra synthetic
|
||||
* nodes. My gut feeling at that this simple approach will be good enough, but if we need to get it more
|
||||
* precise, it should be possible to do it like this.
|
||||
*
|
||||
* In PR review, @yoff suggested an alternative approach for more precise handling:
|
||||
*
|
||||
* - At the call site, all positional arguments are stored into a synthetic starArgs argument, always tarting at index 0
|
||||
* - This is sent to a synthetic star parameter
|
||||
* - At the receiving end, we know the offset of a potential real star parameter, so we can define read steps accordingly: In foo, we read from the synthetic star parameter at index 1 and store to the real star parameter at index 0.
|
||||
*
|
||||
* ```py
|
||||
* def foo(one, *args): ...
|
||||
* def bar(*args): ...
|
||||
*
|
||||
* func = foo if <cond> else bar
|
||||
* func(1, 2, 3)
|
||||
*/
|
||||
class SynthStarArgsElementParameterNode extends ParameterNodeImpl,
|
||||
TSynthStarArgsElementParameterNode {
|
||||
DataFlowCallable callable;
|
||||
|
||||
/** DEPRECATED: Alias for `SyntheticPostUpdateNode` */
|
||||
deprecated module syntheticPostUpdateNode = SyntheticPostUpdateNode;
|
||||
SynthStarArgsElementParameterNode() { this = TSynthStarArgsElementParameterNode(callable) }
|
||||
|
||||
/** A module collecting the different reasons for synthesising a post-update node. */
|
||||
module SyntheticPostUpdateNode {
|
||||
private import semmle.python.SpecialMethods
|
||||
override string toString() { result = "SynthStarArgsElementParameterNode" }
|
||||
|
||||
/** A post-update node is synthesized for all nodes which satisfy `NeedsSyntheticPostUpdateNode`. */
|
||||
class SyntheticPostUpdateNode extends PostUpdateNode, TSyntheticPostUpdateNode {
|
||||
NeedsSyntheticPostUpdateNode pre;
|
||||
override Scope getScope() { result = callable.getScope() }
|
||||
|
||||
SyntheticPostUpdateNode() { this = TSyntheticPostUpdateNode(pre) }
|
||||
override Location getLocation() { result = callable.getLocation() }
|
||||
|
||||
override Node getPreUpdateNode() { result = pre }
|
||||
|
||||
override string toString() { result = "[post " + pre.label() + "] " + pre.toString() }
|
||||
|
||||
override Scope getScope() { result = pre.getScope() }
|
||||
|
||||
override Location getLocation() { result = pre.getLocation() }
|
||||
}
|
||||
|
||||
/** A data flow node for which we should synthesise an associated post-update node. */
|
||||
class NeedsSyntheticPostUpdateNode extends Node {
|
||||
NeedsSyntheticPostUpdateNode() {
|
||||
this = argumentPreUpdateNode()
|
||||
or
|
||||
this = storePreUpdateNode()
|
||||
or
|
||||
this = readPreUpdateNode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the label for this kind of node. This will figure in the textual representation of the synthesized post-update node.
|
||||
* We favour being an arguments as the reason for the post-update node in case multiple reasons apply.
|
||||
*/
|
||||
string label() {
|
||||
if this = argumentPreUpdateNode()
|
||||
then result = "arg"
|
||||
else
|
||||
if this = storePreUpdateNode()
|
||||
then result = "store"
|
||||
else result = "read"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pre-update node for this node.
|
||||
*
|
||||
* An argument might have its value changed as a result of a call.
|
||||
* Certain arguments, such as implicit self arguments are already post-update nodes
|
||||
* and should not have an extra node synthesised.
|
||||
*/
|
||||
Node argumentPreUpdateNode() {
|
||||
result = any(FunctionCall c).getArg(_)
|
||||
or
|
||||
result = any(LambdaCall c).getArg(_)
|
||||
or
|
||||
// Avoid argument 0 of method calls as those have read post-update nodes.
|
||||
exists(MethodCall c, int n | n > 0 | result = c.getArg(n))
|
||||
or
|
||||
result = any(SpecialCall c).getArg(_)
|
||||
or
|
||||
// Avoid argument 0 of class calls as those have non-synthetic post-update nodes.
|
||||
exists(ClassCall c, int n | n > 0 | result = c.getArg(n))
|
||||
or
|
||||
// any argument of any call that we have not been able to resolve
|
||||
exists(CallNode call | not resolvedCall(call) |
|
||||
result.(CfgNode).getNode() in [call.getArg(_), call.getArgByName(_)]
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `call` can be resolved as a normal call */
|
||||
private predicate resolvedCall(CallNode call) {
|
||||
call = any(DataFlowCallableValue cv).getACall()
|
||||
or
|
||||
call = any(DataFlowLambda l).getACall()
|
||||
}
|
||||
|
||||
/** Gets the pre-update node associated with a store. This is used for when an object might have its value changed after a store. */
|
||||
CfgNode storePreUpdateNode() {
|
||||
exists(Attribute a |
|
||||
result.getNode() = a.getObject().getAFlowNode() and
|
||||
a.getCtx() instanceof Store
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node marking the state change of an object after a read.
|
||||
*
|
||||
* A reverse read happens when the result of a read is modified, e.g. in
|
||||
* ```python
|
||||
* l = [ mutable ]
|
||||
* l[0].mutate()
|
||||
* ```
|
||||
* we may now have changed the content of `l`. To track this, there must be
|
||||
* a postupdate node for `l`.
|
||||
*/
|
||||
CfgNode readPreUpdateNode() {
|
||||
exists(Attribute a |
|
||||
result.getNode() = a.getObject().getAFlowNode() and
|
||||
a.getCtx() instanceof Load
|
||||
)
|
||||
or
|
||||
result.getNode() = any(SubscriptNode s).getObject()
|
||||
or
|
||||
// The dictionary argument is read from if the callable has parameters matching the keys.
|
||||
result.getNode().getNode() = any(Call call).getKwargs()
|
||||
}
|
||||
override Parameter getParameter() { none() }
|
||||
}
|
||||
|
||||
import SyntheticPostUpdateNode
|
||||
predicate synthStarArgsElementParameterNodeStoreStep(
|
||||
SynthStarArgsElementParameterNode nodeFrom, ListElementContent c, ParameterNode nodeTo
|
||||
) {
|
||||
c = c and // suppress warning about unused parameter
|
||||
exists(DataFlowCallable callable, ParameterPosition ppos |
|
||||
nodeFrom = TSynthStarArgsElementParameterNode(callable) and
|
||||
nodeTo = callable.getParameter(ppos) and
|
||||
ppos.isStarArgs(_)
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// **kwargs (DictSplat) related
|
||||
// =============================================================================
|
||||
/**
|
||||
* A (synthetic) data-flow node that represents all keyword arguments, as if they had
|
||||
* been passed in a `**kwargs` argument.
|
||||
*/
|
||||
class SynthDictSplatArgumentNode extends Node, TSynthDictSplatArgumentNode {
|
||||
CallNode node;
|
||||
|
||||
SynthDictSplatArgumentNode() { this = TSynthDictSplatArgumentNode(node) }
|
||||
|
||||
override string toString() { result = "SynthDictSplatArgumentNode" }
|
||||
|
||||
override Scope getScope() { result = node.getScope() }
|
||||
|
||||
override Location getLocation() { result = node.getLocation() }
|
||||
}
|
||||
|
||||
private predicate synthDictSplatArgumentNodeStoreStep(
|
||||
ArgumentNode nodeFrom, DictionaryElementContent c, SynthDictSplatArgumentNode nodeTo
|
||||
) {
|
||||
exists(string name, CallNode call, ArgumentPosition keywordPos |
|
||||
nodeTo = TSynthDictSplatArgumentNode(call) and
|
||||
getCallArg(call, _, _, nodeFrom, keywordPos) and
|
||||
keywordPos.isKeyword(name) and
|
||||
c.getKey() = name
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the a `**kwargs` parameter will not contain elements with names of
|
||||
* keyword parameters.
|
||||
*
|
||||
* For example, for the function below, it's not possible that the `kwargs` dictionary
|
||||
* can contain an element with the name `a`, since that parameter can be given as a
|
||||
* keyword argument.
|
||||
*
|
||||
* ```py
|
||||
* def func(a, **kwargs):
|
||||
* ...
|
||||
* ```
|
||||
*/
|
||||
private predicate dictSplatParameterNodeClearStep(ParameterNode n, DictionaryElementContent c) {
|
||||
exists(DataFlowCallable callable, ParameterPosition dictSplatPos, ParameterPosition keywordPos |
|
||||
dictSplatPos.isDictSplat() and
|
||||
(
|
||||
n.getParameter() = callable.(DataFlowFunction).getScope().getKwarg()
|
||||
or
|
||||
n = TSummaryParameterNode(callable.asLibraryCallable(), dictSplatPos)
|
||||
) and
|
||||
exists(callable.getParameter(keywordPos)) and
|
||||
keywordPos.isKeyword(c.getKey())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A synthetic data-flow node to allow flow to keyword parameters from a `**kwargs` argument.
|
||||
*
|
||||
* Take the code snippet below as an example. Since the call only has a `**kwargs` argument,
|
||||
* with a `**` argument position, we add this synthetic parameter node with `**` parameter position,
|
||||
* and a read step to the `p1` parameter.
|
||||
*
|
||||
* ```py
|
||||
* def foo(p1, p2): ...
|
||||
*
|
||||
* kwargs = {"p1": 42, "p2": 43}
|
||||
* foo(**kwargs)
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Note that this will introduce a bit of redundancy in cases like
|
||||
*
|
||||
* ```py
|
||||
* foo(p1=taint(1), p2=taint(2))
|
||||
* ```
|
||||
*
|
||||
* where direct keyword matching is possible, since we construct a synthesized dict
|
||||
* splat argument (`SynthDictSplatArgumentNode`) at the call site, which means that
|
||||
* `taint(1)` will flow into `p1` both via normal keyword matching and via the synthesized
|
||||
* nodes (and similarly for `p2`). However, this redundancy is OK since
|
||||
* (a) it means that type-tracking through keyword arguments also works in most cases,
|
||||
* (b) read/store steps can be avoided when direct keyword matching is possible, and
|
||||
* hence access path limits are not a concern, and
|
||||
* (c) since the synthesized nodes are hidden, the reported data-flow paths will be
|
||||
* collapsed anyway.
|
||||
*/
|
||||
class SynthDictSplatParameterNode extends ParameterNodeImpl, TSynthDictSplatParameterNode {
|
||||
DataFlowCallable callable;
|
||||
|
||||
SynthDictSplatParameterNode() { this = TSynthDictSplatParameterNode(callable) }
|
||||
|
||||
override string toString() { result = "SynthDictSplatParameterNode" }
|
||||
|
||||
override Scope getScope() { result = callable.getScope() }
|
||||
|
||||
override Location getLocation() { result = callable.getLocation() }
|
||||
|
||||
override Parameter getParameter() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow step from the synthetic `**kwargs` parameter to the real `**kwargs` parameter.
|
||||
* Due to restriction in dataflow library, we can only give one of them as result for
|
||||
* `DataFlowCallable.getParameter`, so this is a workaround to ensure there is flow to
|
||||
* _both_ of them.
|
||||
*/
|
||||
private predicate dictSplatParameterNodeFlowStep(
|
||||
ParameterNodeImpl nodeFrom, ParameterNodeImpl nodeTo
|
||||
) {
|
||||
exists(DataFlowCallable callable |
|
||||
nodeFrom = TSynthDictSplatParameterNode(callable) and
|
||||
(
|
||||
nodeTo.getParameter() = callable.(DataFlowFunction).getScope().getKwarg()
|
||||
or
|
||||
exists(ParameterPosition pos |
|
||||
nodeTo = TSummaryParameterNode(callable.asLibraryCallable(), pos) and
|
||||
pos.isDictSplat()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads from the synthetic **kwargs parameter to each keyword parameter.
|
||||
*/
|
||||
predicate synthDictSplatParameterNodeReadStep(
|
||||
SynthDictSplatParameterNode nodeFrom, DictionaryElementContent c, ParameterNode nodeTo
|
||||
) {
|
||||
exists(DataFlowCallable callable, ParameterPosition ppos |
|
||||
nodeFrom = TSynthDictSplatParameterNode(callable) and
|
||||
nodeTo = callable.getParameter(ppos) and
|
||||
ppos.isKeyword(c.getKey())
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// PostUpdateNode
|
||||
// =============================================================================
|
||||
abstract class PostUpdateNodeImpl extends Node {
|
||||
/** Gets the node before the state update. */
|
||||
abstract Node getPreUpdateNode();
|
||||
}
|
||||
|
||||
class SyntheticPostUpdateNode extends PostUpdateNodeImpl, TSyntheticPostUpdateNode {
|
||||
ControlFlowNode node;
|
||||
|
||||
SyntheticPostUpdateNode() { this = TSyntheticPostUpdateNode(node) }
|
||||
|
||||
override Node getPreUpdateNode() { result.(CfgNode).getNode() = node }
|
||||
|
||||
override string toString() { result = "[post] " + node.toString() }
|
||||
|
||||
override Scope getScope() { result = node.getScope() }
|
||||
|
||||
override Location getLocation() { result = node.getLocation() }
|
||||
}
|
||||
|
||||
class NonSyntheticPostUpdateNode extends PostUpdateNodeImpl, CfgNode {
|
||||
SyntheticPreUpdateNode pre;
|
||||
|
||||
NonSyntheticPostUpdateNode() { this = pre.getPostUpdateNode() }
|
||||
|
||||
override Node getPreUpdateNode() { result = pre }
|
||||
}
|
||||
|
||||
class DataFlowExpr = Expr;
|
||||
|
||||
@@ -274,13 +379,6 @@ module EssaFlow {
|
||||
iterableUnpackingFlowStep(nodeFrom, nodeTo)
|
||||
or
|
||||
matchFlowStep(nodeFrom, nodeTo)
|
||||
or
|
||||
// Overflow keyword argument
|
||||
exists(CallNode call, CallableValue callable |
|
||||
call = callable.getACall() and
|
||||
nodeTo = TKwOverflowNode(call, callable) and
|
||||
nodeFrom.asCfgNode() = call.getNode().getKwargs().getAFlowNode()
|
||||
)
|
||||
}
|
||||
|
||||
predicate useToNextUse(NameNode nodeFrom, NameNode nodeTo) {
|
||||
@@ -305,6 +403,8 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
simpleLocalFlowStepForTypetracking(nodeFrom, nodeTo)
|
||||
or
|
||||
summaryFlowSteps(nodeFrom, nodeTo)
|
||||
or
|
||||
dictSplatParameterNodeFlowStep(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -521,15 +621,15 @@ predicate storeStep(Node nodeFrom, Content c, Node nodeTo) {
|
||||
or
|
||||
attributeStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
posOverflowStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
kwOverflowStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
matchStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
any(Orm::AdditionalOrmSteps es).storeStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
synthStarArgsElementParameterNodeStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
synthDictSplatArgumentNodeStoreStep(nodeFrom, c, nodeTo)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -669,30 +769,6 @@ predicate attributeStoreStep(Node nodeFrom, AttributeContent c, PostUpdateNode n
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` flows into the synthesized positional overflow argument (`nodeTo`)
|
||||
* at the position indicated by `c`.
|
||||
*/
|
||||
predicate posOverflowStoreStep(CfgNode nodeFrom, TupleElementContent c, Node nodeTo) {
|
||||
exists(CallNode call, CallableValue callable, int n |
|
||||
nodeFrom.asCfgNode() = getPositionalOverflowArg(call, callable, n) and
|
||||
nodeTo = TPosOverflowNode(call, callable) and
|
||||
c.getIndex() = n
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` flows into the synthesized keyword overflow argument (`nodeTo`)
|
||||
* at the key indicated by `c`.
|
||||
*/
|
||||
predicate kwOverflowStoreStep(CfgNode nodeFrom, DictionaryElementContent c, Node nodeTo) {
|
||||
exists(CallNode call, CallableValue callable, string key |
|
||||
nodeFrom.asCfgNode() = getKeywordOverflowArg(call, callable, key) and
|
||||
nodeTo = TKwOverflowNode(call, callable) and
|
||||
c.getKey() = key
|
||||
)
|
||||
}
|
||||
|
||||
predicate defaultValueFlowStep(CfgNode nodeFrom, CfgNode nodeTo) {
|
||||
exists(Function f, Parameter p, ParameterDefinition def |
|
||||
// `getArgByName` supports, unlike `getAnArg`, keyword-only parameters
|
||||
@@ -722,9 +798,9 @@ predicate readStep(Node nodeFrom, Content c, Node nodeTo) {
|
||||
or
|
||||
attributeReadStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
kwUnpackReadStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryReadStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
synthDictSplatParameterNodeReadStep(nodeFrom, c, nodeTo)
|
||||
}
|
||||
|
||||
/** Data flows from a sequence to a subscript of the sequence. */
|
||||
@@ -814,43 +890,19 @@ predicate attributeReadStep(Node nodeFrom, AttributeContent c, AttrRead nodeTo)
|
||||
nodeTo.accesses(nodeFrom, c.getAttribute())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` is a dictionary argument being unpacked and `nodeTo` is the
|
||||
* synthesized unpacked argument with the name indicated by `c`.
|
||||
*/
|
||||
predicate kwUnpackReadStep(CfgNode nodeFrom, DictionaryElementContent c, Node nodeTo) {
|
||||
exists(CallNode call, string name |
|
||||
nodeFrom.asCfgNode() = call.getNode().getKwargs().getAFlowNode() and
|
||||
nodeTo = TKwUnpackedNode(call, _, name) and
|
||||
name = c.getKey()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear content at key `name` of the synthesized dictionary `TKwOverflowNode(call, callable)`,
|
||||
* whenever `call` unpacks `name`.
|
||||
*/
|
||||
predicate kwOverflowClearStep(Node n, Content c) {
|
||||
exists(CallNode call, CallableValue callable, string name |
|
||||
call_unpacks(call, _, callable, name, _) and
|
||||
n = TKwOverflowNode(call, callable) and
|
||||
c.(DictionaryElementContent).getKey() = name
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if values stored inside content `c` are cleared at node `n`. For example,
|
||||
* any value stored inside `f` is cleared at the pre-update node associated with `x`
|
||||
* in `x.f = newValue`.
|
||||
*/
|
||||
predicate clearsContent(Node n, Content c) {
|
||||
kwOverflowClearStep(n, c)
|
||||
or
|
||||
matchClearStep(n, c)
|
||||
or
|
||||
attributeClearStep(n, c)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryClearsContent(n, c)
|
||||
or
|
||||
dictSplatParameterNodeClearStep(n, c)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -906,23 +958,24 @@ predicate nodeIsHidden(Node n) {
|
||||
n instanceof SummaryNode
|
||||
or
|
||||
n instanceof SummaryParameterNode
|
||||
or
|
||||
n instanceof SynthStarArgsElementParameterNode
|
||||
or
|
||||
n instanceof SynthDictSplatArgumentNode
|
||||
or
|
||||
n instanceof SynthDictSplatParameterNode
|
||||
}
|
||||
|
||||
class LambdaCallKind = Unit;
|
||||
|
||||
/** Holds if `creation` is an expression that creates a lambda of kind `kind` for `c`. */
|
||||
predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) {
|
||||
// lambda
|
||||
// lambda and plain functions
|
||||
kind = kind and
|
||||
creation.asExpr() = c.(DataFlowLambda).getDefinition()
|
||||
or
|
||||
// normal function
|
||||
exists(FunctionDef def |
|
||||
def.defines(creation.asVar().getSourceVariable()) and
|
||||
def.getDefinedFunction() = c.(DataFlowCallableValue).getCallableValue().getScope()
|
||||
)
|
||||
creation.asExpr() = c.(DataFlowPlainFunction).getScope().getDefinition()
|
||||
or
|
||||
// summarized function
|
||||
exists(kind) and // avoid warning on unused 'kind'
|
||||
exists(Call call |
|
||||
creation.asExpr() = call.getAnArg() and
|
||||
creation = c.(LibraryCallableValue).getACallback()
|
||||
|
||||
@@ -31,10 +31,44 @@ newtype TNode =
|
||||
or
|
||||
node.getNode() instanceof Pattern
|
||||
} 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 synthetic node representing the value of an object before a state change.
|
||||
*
|
||||
* For class calls we pass a synthetic self argument, so attribute writes in
|
||||
* `__init__` is reflected on the resulting object (we need special logic for this
|
||||
* since there is no `return` in `__init__`)
|
||||
*/
|
||||
// NOTE: since we can't rely on the call graph, but we want to have synthetic
|
||||
// pre-update nodes for class calls, we end up getting synthetic pre-update nodes for
|
||||
// ALL calls :|
|
||||
TSyntheticPreUpdateNode(CallNode call) or
|
||||
/**
|
||||
* A synthetic node representing the value of an object after a state change.
|
||||
* See QLDoc for `PostUpdateNode`.
|
||||
*/
|
||||
TSyntheticPostUpdateNode(ControlFlowNode node) {
|
||||
exists(CallNode call |
|
||||
node = call.getArg(_)
|
||||
or
|
||||
node = call.getArgByName(_)
|
||||
or
|
||||
// `self` argument when handling class instance calls (`__call__` special method))
|
||||
node = call.getFunction()
|
||||
)
|
||||
or
|
||||
node = any(AttrNode a).getObject()
|
||||
or
|
||||
node = any(SubscriptNode s).getObject()
|
||||
or
|
||||
// self parameter when used implicitly in `super()`
|
||||
exists(Class cls, Function func, ParameterDefinition def |
|
||||
func = cls.getAMethod() and
|
||||
not isStaticmethod(func) and
|
||||
// this matches what we do in ExtractedParameterNode
|
||||
def.getDefiningNode() = node and
|
||||
def.getParameter() = func.getArg(0)
|
||||
)
|
||||
} or
|
||||
/** A node representing a global (module-level) variable in a specific module. */
|
||||
TModuleVariableNode(Module m, GlobalVariable v) {
|
||||
v.getScope() = m and
|
||||
@@ -45,37 +79,6 @@ newtype TNode =
|
||||
ImportStar::globalNameDefinedInModule(v.getId(), m)
|
||||
)
|
||||
} 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`.
|
||||
*/
|
||||
TKwUnpackedNode(CallNode call, CallableValue callable, string name) {
|
||||
call_unpacks(call, _, callable, name, _)
|
||||
} or
|
||||
/**
|
||||
* A synthetic node representing that an iterable sequence flows to consumer.
|
||||
*/
|
||||
@@ -109,10 +112,18 @@ newtype TNode =
|
||||
} or
|
||||
TSummaryParameterNode(FlowSummaryImpl::Public::SummarizedCallable c, ParameterPosition pos) {
|
||||
FlowSummaryImpl::Private::summaryParameterNodeRange(c, pos)
|
||||
} or
|
||||
/** A synthetic node to capture positional arguments that are passed to a `*args` parameter. */
|
||||
TSynthStarArgsElementParameterNode(DataFlowCallable callable) {
|
||||
exists(ParameterPosition ppos | ppos.isStarArgs(_) | exists(callable.getParameter(ppos)))
|
||||
} or
|
||||
/** A synthetic node to capture keyword arguments that are passed to a `**kwargs` parameter. */
|
||||
TSynthDictSplatArgumentNode(CallNode call) { exists(call.getArgByName(_)) } or
|
||||
/** A synthetic node to allow flow to keyword parameters from a `**kwargs` argument. */
|
||||
TSynthDictSplatParameterNode(DataFlowCallable callable) {
|
||||
exists(ParameterPosition ppos | ppos.isKeyword(_) | exists(callable.getParameter(ppos)))
|
||||
}
|
||||
|
||||
class TParameterNode = TCfgNode or TSummaryParameterNode;
|
||||
|
||||
/** Helper for `Node::getEnclosingCallable`. */
|
||||
private DataFlowCallable getCallableScope(Scope s) {
|
||||
result.getScope() = s
|
||||
@@ -288,7 +299,7 @@ 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 Node, TParameterNode instanceof ParameterNodeImpl {
|
||||
class ParameterNode extends Node instanceof ParameterNodeImpl {
|
||||
/** Gets the parameter corresponding to this node, if any. */
|
||||
final Parameter getParameter() { result = super.getParameter() }
|
||||
}
|
||||
@@ -298,18 +309,8 @@ class ExtractedParameterNode extends ParameterNodeImpl, CfgNode {
|
||||
//, LocalSourceNode {
|
||||
ParameterDefinition def;
|
||||
|
||||
ExtractedParameterNode() {
|
||||
node = def.getDefiningNode() and
|
||||
// Disregard parameters that we cannot resolve
|
||||
// TODO: Make this unnecessary
|
||||
exists(DataFlowCallable c | node = c.getParameter(_))
|
||||
}
|
||||
ExtractedParameterNode() { node = def.getDefiningNode() }
|
||||
|
||||
override predicate isParameterOf(DataFlowCallable c, int i) { node = c.getParameter(i) }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { this.isParameterOf(result, _) }
|
||||
|
||||
/** Gets the `Parameter` this `ParameterNode` represents. */
|
||||
override Parameter getParameter() { result = def.getParameter() }
|
||||
}
|
||||
|
||||
@@ -327,16 +328,24 @@ abstract class ArgumentNode extends Node {
|
||||
final ExtractedDataFlowCall getCall() { this.argumentOf(result, _) }
|
||||
}
|
||||
|
||||
/** A data flow node that represents a call argument found in the source code. */
|
||||
/**
|
||||
* A data flow node that represents a call argument found in the source code.
|
||||
*/
|
||||
class ExtractedArgumentNode extends ArgumentNode {
|
||||
ExtractedArgumentNode() { this = any(ExtractedDataFlowCall c).getArg(_) }
|
||||
|
||||
final override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
this.extractedArgumentOf(call, pos)
|
||||
ExtractedArgumentNode() {
|
||||
// for resolved calls, we need to allow all argument nodes
|
||||
getCallArg(_, _, _, this, _)
|
||||
or
|
||||
// for potential summaries we allow all normal call arguments
|
||||
normalCallArg(_, this, _)
|
||||
or
|
||||
// and self arguments
|
||||
this.asCfgNode() = any(CallNode c).getFunction().(AttrNode).getObject()
|
||||
}
|
||||
|
||||
predicate extractedArgumentOf(ExtractedDataFlowCall call, ArgumentPosition pos) {
|
||||
this = call.getArg(pos)
|
||||
final override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
this = call.getArgument(pos) and
|
||||
call instanceof ExtractedDataFlowCall
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,16 +354,17 @@ class ExtractedArgumentNode extends ArgumentNode {
|
||||
* 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.
|
||||
* (which might have mutated the argument), the qualifier of a field after
|
||||
* an update to the field, or a container such as a list/dictionary after an element
|
||||
* update.
|
||||
*
|
||||
* Nodes corresponding to AST elements, for example `ExprNode`s, usually refer
|
||||
* to the value before the update with the exception of `ObjectCreationNode`s,
|
||||
* to the value before the update with the exception of class calls,
|
||||
* which represents the value _after_ the constructor has run.
|
||||
*/
|
||||
abstract class PostUpdateNode extends Node {
|
||||
class PostUpdateNode extends Node instanceof PostUpdateNodeImpl {
|
||||
/** Gets the node before the state update. */
|
||||
abstract Node getPreUpdateNode();
|
||||
Node getPreUpdateNode() { result = super.getPreUpdateNode() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -448,70 +458,6 @@ private predicate resolved_import_star_module(Module m, string name, Node n) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 DataFlowCallable getEnclosingCallable() {
|
||||
exists(Node node |
|
||||
node = TCfgNode(call) and
|
||||
result = node.getEnclosingCallable()
|
||||
)
|
||||
}
|
||||
|
||||
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 DataFlowCallable getEnclosingCallable() {
|
||||
exists(Node node |
|
||||
node = TCfgNode(call) and
|
||||
result = node.getEnclosingCallable()
|
||||
)
|
||||
}
|
||||
|
||||
override Location getLocation() { result = call.getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The node representing the synthetic argument of a call that is unpacked from a dictionary
|
||||
* argument.
|
||||
*/
|
||||
class KwUnpackedNode extends Node, TKwUnpackedNode {
|
||||
CallNode call;
|
||||
string name;
|
||||
|
||||
KwUnpackedNode() { this = TKwUnpackedNode(call, _, name) }
|
||||
|
||||
override string toString() { result = "KwUnpacked " + name }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() {
|
||||
exists(Node node |
|
||||
node = TCfgNode(call) and
|
||||
result = node.getEnclosingCallable()
|
||||
)
|
||||
}
|
||||
|
||||
override Location getLocation() { result = call.getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A synthetic node representing an iterable sequence. Used for changing content type
|
||||
* for instance from a `ListElement` to a `TupleElement`, especially if the content is
|
||||
|
||||
@@ -61,11 +61,11 @@ bindingset[c, rk]
|
||||
DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) { any() }
|
||||
|
||||
/**
|
||||
* Gets the type of the `i`th parameter in a synthesized call that targets a
|
||||
* callback of type `t`.
|
||||
* Gets the type of the parameter matching arguments at position `pos` in a
|
||||
* synthesized call that targets a callback of type `t`.
|
||||
*/
|
||||
bindingset[t, i]
|
||||
DataFlowType getCallbackParameterType(DataFlowType t, int i) { any() }
|
||||
bindingset[t, pos]
|
||||
DataFlowType getCallbackParameterType(DataFlowType t, ArgumentPosition pos) { any() }
|
||||
|
||||
/**
|
||||
* Gets the return type of kind `rk` in a synthesized call that targets a
|
||||
@@ -114,10 +114,34 @@ string getComponentSpecific(SummaryComponent sc) {
|
||||
}
|
||||
|
||||
/** Gets the textual representation of a parameter position in the format used for flow summaries. */
|
||||
string getParameterPosition(ParameterPosition pos) { result = pos.toString() }
|
||||
string getParameterPosition(ParameterPosition pos) {
|
||||
pos.isSelf() and result = "self"
|
||||
or
|
||||
exists(int i |
|
||||
pos.isPositional(i) and
|
||||
result = i.toString()
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
pos.isKeyword(name) and
|
||||
result = name + ":"
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the textual representation of an argument position in the format used for flow summaries. */
|
||||
string getArgumentPosition(ArgumentPosition pos) { result = pos.toString() }
|
||||
string getArgumentPosition(ArgumentPosition pos) {
|
||||
pos.isSelf() and result = "self"
|
||||
or
|
||||
exists(int i |
|
||||
pos.isPositional(i) and
|
||||
result = i.toString()
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
pos.isKeyword(name) and
|
||||
result = name + ":"
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if input specification component `c` needs a reference. */
|
||||
predicate inputNeedsReferenceSpecific(string c) { none() }
|
||||
@@ -197,29 +221,55 @@ module ParsePositions {
|
||||
)
|
||||
}
|
||||
|
||||
predicate isParsedParameterPosition(string c, int i) {
|
||||
predicate isParsedPositionalParameterPosition(string c, int i) {
|
||||
isParamBody(c) and
|
||||
i = AccessPath::parseInt(c)
|
||||
}
|
||||
|
||||
predicate isParsedArgumentPosition(string c, int i) {
|
||||
predicate isParsedKeywordParameterPosition(string c, string paramName) {
|
||||
isParamBody(c) and
|
||||
c = paramName + ":"
|
||||
}
|
||||
|
||||
predicate isParsedPositionalArgumentPosition(string c, int i) {
|
||||
isArgBody(c) and
|
||||
i = AccessPath::parseInt(c)
|
||||
}
|
||||
|
||||
predicate isParsedKeywordArgumentPosition(string c, string argName) {
|
||||
isArgBody(c) and
|
||||
c = argName + ":"
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the argument position obtained by parsing `X` in `Parameter[X]`. */
|
||||
ArgumentPosition parseParamBody(string s) {
|
||||
exists(int i |
|
||||
ParsePositions::isParsedParameterPosition(s, i) and
|
||||
ParsePositions::isParsedPositionalParameterPosition(s, i) and
|
||||
result.isPositional(i)
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
ParsePositions::isParsedKeywordParameterPosition(s, name) and
|
||||
result.isKeyword(name)
|
||||
)
|
||||
or
|
||||
s = "self" and
|
||||
result.isSelf()
|
||||
}
|
||||
|
||||
/** Gets the parameter position obtained by parsing `X` in `Argument[X]`. */
|
||||
ParameterPosition parseArgBody(string s) {
|
||||
exists(int i |
|
||||
ParsePositions::isParsedArgumentPosition(s, i) and
|
||||
ParsePositions::isParsedPositionalArgumentPosition(s, i) and
|
||||
result.isPositional(i)
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
ParsePositions::isParsedKeywordArgumentPosition(s, name) and
|
||||
result.isKeyword(name)
|
||||
)
|
||||
or
|
||||
s = "self" and
|
||||
result.isSelf()
|
||||
}
|
||||
|
||||
@@ -60,22 +60,6 @@ string getPossibleContentName() {
|
||||
result = any(DataFlowPublic::AttrRef a).getAttributeName()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a callable for the call where `nodeFrom` is used as the `i`'th argument.
|
||||
*
|
||||
* Helper predicate to avoid bad join order experienced in `callStep`.
|
||||
* This happened when `isParameterOf` was joined _before_ `getCallable`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private DataFlowPrivate::DataFlowCallable getCallableForArgument(
|
||||
DataFlowPublic::ExtractedArgumentNode nodeFrom, int i
|
||||
) {
|
||||
exists(DataFlowPrivate::ExtractedDataFlowCall call |
|
||||
nodeFrom.extractedArgumentOf(call, i) and
|
||||
result = call.getCallable()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` steps to `nodeTo` by being passed as a parameter in a call.
|
||||
*
|
||||
@@ -83,11 +67,15 @@ private DataFlowPrivate::DataFlowCallable getCallableForArgument(
|
||||
* recursion (or, at best, terrible performance), since identifying calls to library
|
||||
* methods is done using API graphs (which uses type tracking).
|
||||
*/
|
||||
predicate callStep(DataFlowPublic::ArgumentNode nodeFrom, DataFlowPrivate::ParameterNodeImpl nodeTo) {
|
||||
// TODO: Support special methods?
|
||||
exists(DataFlowPrivate::DataFlowCallable callable, int i |
|
||||
callable = getCallableForArgument(nodeFrom, i) and
|
||||
nodeTo.isParameterOf(callable, i)
|
||||
predicate callStep(DataFlowPublic::ArgumentNode nodeFrom, DataFlowPublic::ParameterNode nodeTo) {
|
||||
exists(
|
||||
DataFlowPrivate::DataFlowCall call, DataFlowPrivate::DataFlowCallable callable,
|
||||
DataFlowPrivate::ArgumentPosition apos, DataFlowPrivate::ParameterPosition ppos
|
||||
|
|
||||
nodeFrom = call.getArgument(apos) and
|
||||
nodeTo = callable.getParameter(ppos) and
|
||||
DataFlowPrivate::parameterMatch(ppos, apos) and
|
||||
callable = call.getCallable()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1465,7 +1465,19 @@ private module StdlibPrivate {
|
||||
t.start() and
|
||||
result = openCall and
|
||||
(
|
||||
openCall instanceof OpenCall
|
||||
openCall instanceof OpenCall and
|
||||
// don't include the open call inside of Path.open in pathlib.py since
|
||||
// the call to `path_obj.open` is covered by `PathLibOpenCall`.
|
||||
not exists(Module mod, Class cls, Function func |
|
||||
openCall.(OpenCall).asCfgNode().getScope() = func and
|
||||
func.getName() = "open" and
|
||||
func.getScope() = cls and
|
||||
cls.getName() = "Path" and
|
||||
cls.getScope() = mod and
|
||||
mod.getName() = "pathlib" and
|
||||
// do allow this call if we're analyzing pathlib.py as part of CPython though
|
||||
not exists(mod.getFile().getRelativePath())
|
||||
)
|
||||
or
|
||||
openCall instanceof PathLibOpenCall
|
||||
)
|
||||
|
||||
@@ -93,6 +93,8 @@ module Stages {
|
||||
exists(PyFlow::DefinitionNode b)
|
||||
or
|
||||
exists(any(PyFlow::SequenceNode n).getElement(_))
|
||||
or
|
||||
exists(any(PyFlow::ControlFlowNode c).toString())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +127,45 @@ module Stages {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The points-to stage.
|
||||
*/
|
||||
cached
|
||||
module PointsTo {
|
||||
/**
|
||||
* Always holds.
|
||||
* Ensures that a predicate is evaluated as part of the points-to stage.
|
||||
*/
|
||||
cached
|
||||
predicate ref() { 1 = 1 }
|
||||
|
||||
private import semmle.python.pointsto.Base as PointsToBase
|
||||
private import semmle.python.types.Object as TypeObject
|
||||
private import semmle.python.objects.TObject as TObject
|
||||
private import semmle.python.objects.ObjectInternal as ObjectInternal
|
||||
// have to alias since this module is also called PointsTo
|
||||
private import semmle.python.pointsto.PointsTo as RealPointsTo
|
||||
|
||||
/**
|
||||
* DONT USE!
|
||||
* Contains references to each predicate that use the above `ref` predicate.
|
||||
*/
|
||||
cached
|
||||
predicate backref() {
|
||||
1 = 1
|
||||
or
|
||||
PointsToBase::BaseFlow::scope_entry_value_transfer_from_earlier(_, _, _, _)
|
||||
or
|
||||
exists(TypeObject::Object a)
|
||||
or
|
||||
exists(TObject::TObject f)
|
||||
or
|
||||
exists(any(ObjectInternal::ObjectInternal o).toString())
|
||||
or
|
||||
RealPointsTo::AttributePointsTo::variableAttributePointsTo(_, _, _, _, _)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `dataflow` stage.
|
||||
*/
|
||||
@@ -138,14 +179,9 @@ module Stages {
|
||||
predicate ref() { 1 = 1 }
|
||||
|
||||
private import semmle.python.dataflow.new.internal.DataFlowPublic as DataFlowPublic
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch as DataFlowDispatch
|
||||
private import semmle.python.dataflow.new.internal.LocalSources as LocalSources
|
||||
private import semmle.python.internal.Awaited as Awaited
|
||||
private import semmle.python.pointsto.Base as PointsToBase
|
||||
private import semmle.python.types.Object as TypeObject
|
||||
private import semmle.python.objects.TObject as TObject
|
||||
private import semmle.python.Flow as Flow
|
||||
private import semmle.python.objects.ObjectInternal as ObjectInternal
|
||||
private import semmle.python.pointsto.PointsTo as PointsTo
|
||||
|
||||
/**
|
||||
* DONT USE!
|
||||
@@ -159,21 +195,13 @@ module Stages {
|
||||
or
|
||||
any(DataFlowPublic::Node node).hasLocationInfo(_, _, _, _, _)
|
||||
or
|
||||
DataFlowDispatch::resolveCall(_, _, _)
|
||||
or
|
||||
DataFlowDispatch::getCallArg(_, _, _, _, _)
|
||||
or
|
||||
any(LocalSources::LocalSourceNode n).flowsTo(_)
|
||||
or
|
||||
exists(Awaited::awaited(_))
|
||||
or
|
||||
PointsToBase::BaseFlow::scope_entry_value_transfer_from_earlier(_, _, _, _)
|
||||
or
|
||||
exists(TypeObject::Object a)
|
||||
or
|
||||
exists(TObject::TObject f)
|
||||
or
|
||||
exists(any(Flow::ControlFlowNode c).toString())
|
||||
or
|
||||
exists(any(ObjectInternal::ObjectInternal o).toString())
|
||||
or
|
||||
PointsTo::AttributePointsTo::variableAttributePointsTo(_, _, _, _, _)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ class BuiltinOpaqueObjectInternal extends ObjectInternal, TBuiltinOpaqueObject {
|
||||
override Builtin getBuiltin() { this = TBuiltinOpaqueObject(result) }
|
||||
|
||||
override string toString() {
|
||||
Stages::DataFlow::ref() and
|
||||
Stages::PointsTo::ref() and
|
||||
result = this.getBuiltin().getClass().getName() + " object"
|
||||
}
|
||||
|
||||
|
||||
@@ -318,7 +318,7 @@ module BaseFlow {
|
||||
predicate scope_entry_value_transfer_from_earlier(
|
||||
EssaVariable pred_var, Scope pred_scope, ScopeEntryDefinition succ_def, Scope succ_scope
|
||||
) {
|
||||
Stages::DataFlow::ref() and
|
||||
Stages::PointsTo::ref() and
|
||||
exists(SsaSourceVariable var |
|
||||
essa_var_scope(var, pred_scope, pred_var) and
|
||||
scope_entry_def_scope(var, succ_scope, succ_def)
|
||||
|
||||
@@ -2566,7 +2566,7 @@ module AttributePointsTo {
|
||||
predicate variableAttributePointsTo(
|
||||
EssaVariable var, Context context, string name, ObjectInternal value, CfgOrigin origin
|
||||
) {
|
||||
Stages::DataFlow::ref() and
|
||||
Stages::PointsTo::ref() and
|
||||
definitionAttributePointsTo(var.getDefinition(), context, name, value, origin)
|
||||
or
|
||||
exists(EssaVariable prev |
|
||||
|
||||
@@ -57,16 +57,43 @@ module CleartextLogging {
|
||||
/** A piece of data printed, considered as a flow sink. */
|
||||
class PrintedDataAsSink extends Sink {
|
||||
PrintedDataAsSink() {
|
||||
this = API::builtin("print").getACall().getArg(_)
|
||||
or
|
||||
// special handling of writing to `sys.stdout` and `sys.stderr`, which is
|
||||
// essentially the same as printing
|
||||
this =
|
||||
API::moduleImport("sys")
|
||||
.getMember(["stdout", "stderr"])
|
||||
.getMember("write")
|
||||
.getACall()
|
||||
.getArg(0)
|
||||
(
|
||||
this = API::builtin("print").getACall().getArg(_)
|
||||
or
|
||||
// special handling of writing to `sys.stdout` and `sys.stderr`, which is
|
||||
// essentially the same as printing
|
||||
this =
|
||||
API::moduleImport("sys")
|
||||
.getMember(["stdout", "stderr"])
|
||||
.getMember("write")
|
||||
.getACall()
|
||||
.getArg(0)
|
||||
) and
|
||||
// since some of the inner error handling implementation of the logging module is
|
||||
// ```py
|
||||
// sys.stderr.write('Message: %r\n'
|
||||
// 'Arguments: %s\n' % (record.msg,
|
||||
// record.args))
|
||||
// ```
|
||||
// any time we would report flow to such a logging sink, we can ALSO report
|
||||
// the flow to the `record.msg`/`record.args` sinks -- obviously we
|
||||
// don't want that.
|
||||
//
|
||||
// However, simply removing taint edges out of a sink is not a good enough solution,
|
||||
// since we would only flag one of the `logging.info` calls in the following example
|
||||
// due to use-use flow
|
||||
// ```py
|
||||
// logging.info(user_controlled)
|
||||
// logging.info(user_controlled)
|
||||
// ```
|
||||
//
|
||||
// The same approach is used in the command injection query.
|
||||
not exists(Module loggingInit |
|
||||
loggingInit.getName() = "logging.__init__" and
|
||||
this.getScope().getEnclosingModule() = loggingInit and
|
||||
// do allow this call if we're analyzing logging/__init__.py as part of CPython though
|
||||
not exists(loggingInit.getFile().getRelativePath())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,34 @@ module CleartextStorage {
|
||||
|
||||
/** The data written to a file, considered as a flow sink. */
|
||||
class FileWriteDataAsSink extends Sink {
|
||||
FileWriteDataAsSink() { this = any(FileSystemWriteAccess write).getADataNode() }
|
||||
FileWriteDataAsSink() {
|
||||
this = any(FileSystemWriteAccess write).getADataNode() and
|
||||
// since implementation of Path.write_bytes in pathlib.py is like
|
||||
// ```py
|
||||
// def write_bytes(self, data):
|
||||
// with self.open(mode='wb') as f:
|
||||
// return f.write(data)
|
||||
// ```
|
||||
// any time we would report flow to the `Path.write_bytes` sink, we can ALSO report
|
||||
// the flow from the `data` parameter to the `f.write` sink -- obviously we
|
||||
// don't want that.
|
||||
//
|
||||
// However, simply removing taint edges out of a sink is not a good enough solution,
|
||||
// since we would only flag one of the `p.write` calls in the following example
|
||||
// due to use-use flow
|
||||
// ```py
|
||||
// p.write(user_controlled)
|
||||
// p.write(user_controlled)
|
||||
// ```
|
||||
//
|
||||
// The same approach is used in the command injection query.
|
||||
not exists(Module pathlib |
|
||||
pathlib.getName() = "pathlib" and
|
||||
this.getScope().getEnclosingModule() = pathlib and
|
||||
// do allow this call if we're analyzing pathlib.py as part of CPython though
|
||||
not exists(pathlib.getFile().getRelativePath())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** The data written to a cookie on a HTTP response, considered as a flow sink. */
|
||||
|
||||
@@ -76,6 +76,9 @@ module CommandInjection {
|
||||
// `subprocess`. See:
|
||||
// https://github.com/python/cpython/blob/fa7ce080175f65d678a7d5756c94f82887fc9803/Lib/os.py#L974
|
||||
// https://github.com/python/cpython/blob/fa7ce080175f65d678a7d5756c94f82887fc9803/Lib/subprocess.py#L341
|
||||
//
|
||||
// The same approach is used in the path-injection, cleartext-storage, and
|
||||
// cleartext-logging queries.
|
||||
not this.getScope().getEnclosingModule().getName() in [
|
||||
"os", "subprocess", "platform", "popen2"
|
||||
]
|
||||
|
||||
@@ -58,7 +58,33 @@ module PathInjection {
|
||||
* A file system access, considered as a flow sink.
|
||||
*/
|
||||
class FileSystemAccessAsSink extends Sink {
|
||||
FileSystemAccessAsSink() { this = any(FileSystemAccess e).getAPathArgument() }
|
||||
FileSystemAccessAsSink() {
|
||||
this = any(FileSystemAccess e).getAPathArgument() and
|
||||
// since implementation of Path.open in pathlib.py is like
|
||||
// ```py
|
||||
// def open(self, ...):
|
||||
// return io.open(self, ...)
|
||||
// ```
|
||||
// any time we would report flow to the `path_obj.open` sink, we can ALSO report
|
||||
// the flow from the `self` parameter to the `io.open` sink -- obviously we
|
||||
// don't want that.
|
||||
//
|
||||
// However, simply removing taint edges out of a sink is not a good enough solution,
|
||||
// since we would only flag one of the `p.open` calls in the following example
|
||||
// due to use-use flow
|
||||
// ```py
|
||||
// p.open()
|
||||
// p.open()
|
||||
// ```
|
||||
//
|
||||
// The same approach is used in the command injection query.
|
||||
not exists(Module pathlib |
|
||||
pathlib.getName() = "pathlib" and
|
||||
this.getScope().getEnclosingModule() = pathlib and
|
||||
// do allow this call if we're analyzing pathlib.py as part of CPython though
|
||||
not exists(pathlib.getFile().getRelativePath())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
@@ -41,7 +41,32 @@ module StackTraceExposure {
|
||||
/**
|
||||
* A source of exception info, considered as a flow source.
|
||||
*/
|
||||
class ExceptionInfoAsSource extends Source instanceof ExceptionInfo { }
|
||||
class ExceptionInfoAsSource extends Source instanceof ExceptionInfo {
|
||||
ExceptionInfoAsSource() {
|
||||
// since `traceback.format_exc()` in Python 2 is internally implemented as
|
||||
// ```py
|
||||
// def format_exc(limit=None):
|
||||
// """Like print_exc() but return a string."""
|
||||
// try:
|
||||
// etype, value, tb = sys.exc_info()
|
||||
// return ''.join(format_exception(etype, value, tb, limit))
|
||||
// finally:
|
||||
// etype = value = tb = None
|
||||
// ```
|
||||
// any time we would report flow to such from a call to format_exc, we can ALSO report
|
||||
// the flow from the `sys.exc_info()` source -- obviously we don't want that.
|
||||
//
|
||||
//
|
||||
// To avoid this, we use the same approach as for sinks in the command injection
|
||||
// query (and others).
|
||||
not exists(Module traceback |
|
||||
traceback.getName() = "traceback" and
|
||||
this.getScope().getEnclosingModule() = traceback and
|
||||
// do allow this call if we're analyzing traceback.py as part of CPython though
|
||||
not exists(traceback.getFile().getRelativePath())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The body of a HTTP response that will be returned from a server, considered as a flow sink.
|
||||
|
||||
@@ -5,7 +5,7 @@ private import semmle.python.internal.CachedStages
|
||||
|
||||
cached
|
||||
private predicate is_an_object(@py_object obj) {
|
||||
Stages::DataFlow::ref() and
|
||||
Stages::PointsTo::ref() and
|
||||
/* CFG nodes for numeric literals, all of which have a @py_cobject for the value of that literal */
|
||||
obj instanceof ControlFlowNode and
|
||||
not obj.(ControlFlowNode).getNode() instanceof IntegerLiteral and
|
||||
@@ -78,7 +78,7 @@ class Object extends @py_object {
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
Stages::DataFlow::ref() and
|
||||
Stages::PointsTo::ref() and
|
||||
this.hasOrigin() and
|
||||
this.getOrigin()
|
||||
.getLocation()
|
||||
@@ -98,7 +98,7 @@ class Object extends @py_object {
|
||||
/** Gets a textual representation of this element. */
|
||||
cached
|
||||
string toString() {
|
||||
Stages::DataFlow::ref() and
|
||||
Stages::PointsTo::ref() and
|
||||
not this = undefinedVariable() and
|
||||
not this = unknownValue() and
|
||||
exists(ClassObject type | type.asBuiltin() = this.asBuiltin().getClass() |
|
||||
|
||||
Reference in New Issue
Block a user