diff --git a/python/ql/consistency-queries/CfgConsistency.ql b/python/ql/consistency-queries/CfgConsistency.ql new file mode 100644 index 000000000000..ab13eddf190c --- /dev/null +++ b/python/ql/consistency-queries/CfgConsistency.ql @@ -0,0 +1,2 @@ +import semmle.python.controlflow.internal.AstNodeImpl +import ControlFlow::Consistency diff --git a/python/ql/consistency-queries/DataFlowConsistency.ql b/python/ql/consistency-queries/DataFlowConsistency.ql index 829aa6debef2..2eec17c78fe1 100644 --- a/python/ql/consistency-queries/DataFlowConsistency.ql +++ b/python/ql/consistency-queries/DataFlowConsistency.ql @@ -9,6 +9,7 @@ private import semmle.python.dataflow.new.internal.DataFlowImplSpecific private import semmle.python.dataflow.new.internal.DataFlowDispatch private import semmle.python.dataflow.new.internal.TaintTrackingImplSpecific private import codeql.dataflow.internal.DataFlowImplConsistency +private import semmle.python.controlflow.internal.Cfg as Cfg private module Input implements InputSig { private import Private @@ -72,7 +73,7 @@ private module Input implements InputSig { // resolve to multiple functions), but we only make _one_ ArgumentNode for each // argument in the CallNode, we end up violating this consistency check in those // cases. (see `getCallArg` in DataFlowDispatch.qll) - exists(DataFlowCall other, CallNode cfgCall | other != call | + exists(DataFlowCall other, Cfg::CallNode cfgCall | other != call | call.getNode() = cfgCall and other.getNode() = cfgCall and isArgumentNode(arg, call, _) and @@ -88,16 +89,16 @@ private module Input implements InputSig { // allow it instead. ( call.getScope() = attr.getScope() and - any(CfgNode n | n.asCfgNode() = call.getNode().(CallNode).getFunction()).getALocalSource() = - attr + any(CfgNode n | n.asCfgNode() = call.getNode().(Cfg::CallNode).getFunction()) + .getALocalSource() = attr or not exists(call.getScope().(Function).getDefinition()) and call.getScope().getScope+() = attr.getScope() ) and ( other.getScope() = attr.getScope() and - any(CfgNode n | n.asCfgNode() = other.getNode().(CallNode).getFunction()).getALocalSource() = - attr + any(CfgNode n | n.asCfgNode() = other.getNode().(Cfg::CallNode).getFunction()) + .getALocalSource() = attr or not exists(other.getScope().(Function).getDefinition()) and other.getScope().getScope+() = attr.getScope() diff --git a/python/ql/lib/LegacyPointsTo.qll b/python/ql/lib/LegacyPointsTo.qll index ffea2d93b66c..f5ad67a3c555 100644 --- a/python/ql/lib/LegacyPointsTo.qll +++ b/python/ql/lib/LegacyPointsTo.qll @@ -213,9 +213,11 @@ class ExprWithPointsTo extends Expr { * Gets what this expression might "refer-to" in the given `context`. */ predicate refersTo(Context context, Object obj, ClassObject cls, AstNode origin) { - this.getAFlowNode() - .(ControlFlowNodeWithPointsTo) - .refersTo(context, obj, cls, origin.getAFlowNode()) + exists(ControlFlowNode this_, ControlFlowNode origin_ | + this_.getNode() = this and origin_.getNode() = origin + | + this_.(ControlFlowNodeWithPointsTo).refersTo(context, obj, cls, origin_) + ) } /** @@ -226,7 +228,11 @@ class ExprWithPointsTo extends Expr { */ pragma[nomagic] predicate refersTo(Object obj, AstNode origin) { - this.getAFlowNode().(ControlFlowNodeWithPointsTo).refersTo(obj, origin.getAFlowNode()) + exists(ControlFlowNode this_, ControlFlowNode origin_ | + this_.getNode() = this and origin_.getNode() = origin + | + this_.(ControlFlowNodeWithPointsTo).refersTo(obj, origin_) + ) } /** @@ -240,16 +246,22 @@ class ExprWithPointsTo extends Expr { * in the given `context`. */ predicate pointsTo(Context context, Value value, AstNode origin) { - this.getAFlowNode() - .(ControlFlowNodeWithPointsTo) - .pointsTo(context, value, origin.getAFlowNode()) + exists(ControlFlowNode this_, ControlFlowNode origin_ | + this_.getNode() = this and origin_.getNode() = origin + | + this_.(ControlFlowNodeWithPointsTo).pointsTo(context, value, origin_) + ) } /** * Holds if this expression might "point-to" to `value` which is from `origin`. */ predicate pointsTo(Value value, AstNode origin) { - this.getAFlowNode().(ControlFlowNodeWithPointsTo).pointsTo(value, origin.getAFlowNode()) + exists(ControlFlowNode this_, ControlFlowNode origin_ | + this_.getNode() = this and origin_.getNode() = origin + | + this_.(ControlFlowNodeWithPointsTo).pointsTo(value, origin_) + ) } /** @@ -475,7 +487,10 @@ class FunctionMetricsWithPointsTo extends FunctionMetrics { not non_coupling_method(result) and exists(Call call | call.getScope() = this | exists(FunctionObject callee | callee.getFunction() = result | - call.getAFlowNode().getFunction().(ControlFlowNodeWithPointsTo).refersTo(callee) + exists(CallNode call_ | + call_.getNode() = call and + call_.getFunction().(ControlFlowNodeWithPointsTo).refersTo(callee) + ) ) or exists(Attribute a | call.getFunc() = a | diff --git a/python/ql/lib/analysis/DefinitionTracking.qll b/python/ql/lib/analysis/DefinitionTracking.qll index 21155970375b..583a7807ff27 100644 --- a/python/ql/lib/analysis/DefinitionTracking.qll +++ b/python/ql/lib/analysis/DefinitionTracking.qll @@ -64,7 +64,7 @@ private predicate jump_to_defn(ControlFlowNode use, Definition defn) { private predicate preferred_jump_to_defn(Expr use, Definition def) { not use instanceof ClassExpr and not use instanceof FunctionExpr and - jump_to_defn(use.getAFlowNode(), def) + exists(ControlFlowNode useNode | useNode.getNode() = use | jump_to_defn(useNode, def)) } private predicate unique_jump_to_defn(Expr use, Definition def) { @@ -452,7 +452,7 @@ private predicate self_parameter_jump_to_defn_attribute( * This exists primarily for testing use `getPreferredDefinition()` instead. */ Definition getADefinition(Expr use) { - jump_to_defn(use.getAFlowNode(), result) and + exists(ControlFlowNode useNode | useNode.getNode() = use | jump_to_defn(useNode, result)) and not use instanceof Call and not use.isArtificial() and // Not the use itself diff --git a/python/ql/lib/change-notes/2026-05-26-shared-cfg-and-ssa.md b/python/ql/lib/change-notes/2026-05-26-shared-cfg-and-ssa.md new file mode 100644 index 000000000000..39bdadbbaa43 --- /dev/null +++ b/python/ql/lib/change-notes/2026-05-26-shared-cfg-and-ssa.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The Python dataflow library is now built on the shared CFG and SSA libraries (`shared/controlflow` and `shared/ssa`), bringing Python in line with the other CodeQL languages. The legacy CFG in `semmle/python/Flow.qll` and the legacy ESSA SSA in `semmle/python/essa/*` remain available for downstream queries but are no longer used by the new dataflow library, type tracking, or API graphs. Most queries should be unaffected; a small number may produce slightly different results because of differences in CFG granularity (e.g. separate pre/post nodes per expression) and in how attribute and tuple-unpacking writes are modelled. diff --git a/python/ql/lib/printCfgNew.ql b/python/ql/lib/printCfgNew.ql new file mode 100644 index 000000000000..ba336de562a7 --- /dev/null +++ b/python/ql/lib/printCfgNew.ql @@ -0,0 +1,45 @@ +/** + * @name Print CFG (New) + * @description Produces a representation of a file's Control Flow Graph + * using the new shared control flow library. + * This query is used by the VS Code extension. + * @id python/print-cfg + * @kind graph + * @tags ide-contextual-queries/print-cfg + */ + +private import python as Py +import semmle.python.controlflow.internal.AstNodeImpl + +external string selectedSourceFile(); + +private predicate selectedSourceFileAlias = selectedSourceFile/0; + +external int selectedSourceLine(); + +private predicate selectedSourceLineAlias = selectedSourceLine/0; + +external int selectedSourceColumn(); + +private predicate selectedSourceColumnAlias = selectedSourceColumn/0; + +module ViewCfgQueryInput implements ControlFlow::ViewCfgQueryInputSig { + predicate selectedSourceFile = selectedSourceFileAlias/0; + + predicate selectedSourceLine = selectedSourceLineAlias/0; + + predicate selectedSourceColumn = selectedSourceColumnAlias/0; + + predicate cfgScopeSpan( + Ast::Callable callable, Py::File file, int startLine, int startColumn, int endLine, + int endColumn + ) { + exists(Py::Scope scope | + scope = callable.asScope() and + file = scope.getLocation().getFile() and + scope.getLocation().hasLocationInfo(_, startLine, startColumn, endLine, endColumn) + ) + } +} + +import ControlFlow::ViewCfgQuery diff --git a/python/ql/lib/qlpack.yml b/python/ql/lib/qlpack.yml index 981ab78ff33e..c8b592b95f01 100644 --- a/python/ql/lib/qlpack.yml +++ b/python/ql/lib/qlpack.yml @@ -7,6 +7,7 @@ library: true upgrades: upgrades dependencies: codeql/concepts: ${workspace} + codeql/controlflow: ${workspace} codeql/dataflow: ${workspace} codeql/mad: ${workspace} codeql/regex: ${workspace} diff --git a/python/ql/lib/semmle/python/ApiGraphs.qll b/python/ql/lib/semmle/python/ApiGraphs.qll index efd8141efc6e..eaa31ed3d6c5 100644 --- a/python/ql/lib/semmle/python/ApiGraphs.qll +++ b/python/ql/lib/semmle/python/ApiGraphs.qll @@ -6,8 +6,9 @@ * directed and labeled; they specify how the components represented by nodes relate to each other. */ -// Importing python under the `py` namespace to avoid importing `CallNode` from `Flow.qll` and thereby having a naming conflict with `API::CallNode`. +// Importing python under the `py` namespace to avoid importing `Cfg::CallNode` from `Flow.qll` and thereby having a naming conflict with `API::CallNode`. private import python as PY +private import semmle.python.controlflow.internal.Cfg as Cfg import semmle.python.dataflow.new.DataFlow private import semmle.python.internal.CachedStages @@ -282,7 +283,7 @@ module API { index = this.getIndex() and ( // subscripting - exists(PY::SubscriptNode subscript | + exists(Cfg::SubscriptNode subscript | subscript.getObject() = this.getAValueReachableFromSource().asCfgNode() and subscript.getIndex() = index.asSink().asCfgNode() | @@ -290,7 +291,7 @@ module API { subscript = result.asSource().asCfgNode() or // writing - subscript.(PY::DefinitionNode).getValue() = result.asSink().asCfgNode() + subscript.(Cfg::DefinitionNode).getValue() = result.asSink().asCfgNode() ) or // dictionary literals @@ -684,7 +685,7 @@ module API { * Ignores relative imports, such as `from ..foo.bar import baz`. */ private predicate imports(DataFlow::CfgNode imp, string name) { - exists(PY::ImportExprNode iexpr | + exists(Cfg::ImportExprNode iexpr | imp.getNode() = iexpr and not iexpr.getNode().isRelative() and name = iexpr.getNode().getImportedModuleName() @@ -775,7 +776,7 @@ module API { // list literals, from `x` to `[x]` // TODO: once convenient, this should be done at a higher level than the AST, // at least at the CFG layer, to take splitting into account. - // Also consider `SequenceNode for generality. + // Also consider `Cfg::SequenceNode for generality. exists(PY::List list | list = pred.(DataFlow::ExprNode).getNode().getNode() | rhs.(DataFlow::ExprNode).getNode().getNode() = list.getAnElt() and lbl = Label::subscript() @@ -805,7 +806,7 @@ module API { subscript = trackUseNode(src).getSubscript(index) | // from `x` to a definition of `x[...]` - rhs.asCfgNode() = subscript.asCfgNode().(PY::DefinitionNode).getValue() and + rhs.asCfgNode() = subscript.asCfgNode().(Cfg::DefinitionNode).getValue() and lbl = Label::subscript() or // from `x` to `"key"` in `x["key"]` diff --git a/python/ql/lib/semmle/python/AstExtended.qll b/python/ql/lib/semmle/python/AstExtended.qll index 13da4e899a71..32b9ce6eee7c 100644 --- a/python/ql/lib/semmle/python/AstExtended.qll +++ b/python/ql/lib/semmle/python/AstExtended.qll @@ -16,17 +16,6 @@ abstract class AstNode extends AstNode_ { /** Gets the scope that this node occurs in */ abstract Scope getScope(); - /** - * Gets a flow node corresponding directly to this node. - * NOTE: For some statements and other purely syntactic elements, - * there may not be a `ControlFlowNode` - */ - cached - ControlFlowNode getAFlowNode() { - Stages::AST::ref() and - py_flow_bb_node(result, this, _, _) - } - /** Gets the location for this AST node */ cached Location getLocation() { none() } diff --git a/python/ql/lib/semmle/python/Concepts.qll b/python/ql/lib/semmle/python/Concepts.qll index 76e9f4bd13f9..6eb74b52121d 100644 --- a/python/ql/lib/semmle/python/Concepts.qll +++ b/python/ql/lib/semmle/python/Concepts.qll @@ -5,6 +5,7 @@ */ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.internal.DataFlowImplSpecific private import semmle.python.dataflow.new.RemoteFlowSources @@ -214,7 +215,7 @@ module Path { SafeAccessCheck() { this = DataFlow::BarrierGuard::getABarrierNode() } } - private predicate safeAccessCheck(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) { + private predicate safeAccessCheck(DataFlow::GuardNode g, Cfg::ControlFlowNode node, boolean branch) { g.(SafeAccessCheck::Range).checks(node, branch) } @@ -223,7 +224,7 @@ module Path { /** A data-flow node that checks that a path is safe to access in some way, for example by having a controlled prefix. */ abstract class Range extends DataFlow::GuardNode { /** Holds if this guard validates `node` upon evaluating to `branch`. */ - abstract predicate checks(ControlFlowNode node, boolean branch); + abstract predicate checks(Cfg::ControlFlowNode node, boolean branch); } } } diff --git a/python/ql/lib/semmle/python/Exprs.qll b/python/ql/lib/semmle/python/Exprs.qll index 6ab9f8d8340d..9ce6f9e6680a 100644 --- a/python/ql/lib/semmle/python/Exprs.qll +++ b/python/ql/lib/semmle/python/Exprs.qll @@ -28,7 +28,9 @@ class Expr extends Expr_, AstNode { /** Whether this expression may have a side effect (as determined purely from its syntax) */ predicate hasSideEffects() { /* If an exception raised by this expression handled, count that as a side effect */ - this.getAFlowNode().getASuccessor().getNode() instanceof ExceptStmt + exists(ControlFlowNode n | n.getNode() = this | + n.getASuccessor().getNode() instanceof ExceptStmt + ) or this.getASubExpression().hasSideEffects() } @@ -68,8 +70,6 @@ class Attribute extends Attribute_ { /* syntax: Expr.name */ override Expr getASubExpression() { result = this.getObject() } - override AttrNode getAFlowNode() { result = super.getAFlowNode() } - /** Gets the name of this attribute. That is the `name` in `obj.name` */ string getName() { result = Attribute_.super.getAttr() } @@ -96,8 +96,6 @@ class Subscript extends Subscript_ { } Expr getObject() { result = Subscript_.super.getValue() } - - override SubscriptNode getAFlowNode() { result = super.getAFlowNode() } } /** A call expression, such as `func(...)` */ @@ -113,8 +111,6 @@ class Call extends Call_ { override string toString() { result = this.getFunc().toString() + "()" } - override CallNode getAFlowNode() { result = super.getAFlowNode() } - /** Gets a tuple (*) argument of this call. */ Expr getStarargs() { result = this.getAPositionalArg().(Starred).getValue() } @@ -200,8 +196,6 @@ class IfExp extends IfExp_ { override Expr getASubExpression() { result = this.getTest() or result = this.getBody() or result = this.getOrelse() } - - override IfExprNode getAFlowNode() { result = super.getAFlowNode() } } /** A starred expression, such as the `*rest` in the assignment `first, *rest = seq` */ @@ -410,8 +404,6 @@ class PlaceHolder extends PlaceHolder_ { override Expr getASubExpression() { none() } override string toString() { result = "$" + this.getId() } - - override NameNode getAFlowNode() { result = super.getAFlowNode() } } /** A tuple expression such as `( 1, 3, 5, 7, 9 )` */ @@ -478,8 +470,6 @@ class Name extends Name_ { override string toString() { result = this.getId() } - override NameNode getAFlowNode() { result = super.getAFlowNode() } - override predicate isArtificial() { /* Artificial variable names in comprehensions all start with "." */ this.getId().charAt(0) = "." @@ -585,8 +575,6 @@ abstract class NameConstant extends Name, ImmutableLiteral { override predicate isConstant() { any() } - override NameConstantNode getAFlowNode() { result = Name.super.getAFlowNode() } - override predicate isArtificial() { none() } } diff --git a/python/ql/lib/semmle/python/Flow.qll b/python/ql/lib/semmle/python/Flow.qll index 94caf513aa98..0f84f3f367ad 100644 --- a/python/ql/lib/semmle/python/Flow.qll +++ b/python/ql/lib/semmle/python/Flow.qll @@ -1,7 +1,7 @@ overlay[local] module; -import python +import python as Py private import semmle.python.internal.CachedStages private import codeql.controlflow.BasicBlock as BB @@ -17,7 +17,7 @@ private import codeql.controlflow.BasicBlock as BB */ private predicate augstore(ControlFlowNode load, ControlFlowNode store) { - exists(Expr load_store | exists(AugAssign aa | aa.getTarget() = load_store) | + exists(Py::Expr load_store | exists(Py::AugAssign aa | aa.getTarget() = load_store) | toAst(load) = load_store and toAst(store) = load_store and load.strictlyDominates(store) @@ -25,7 +25,7 @@ private predicate augstore(ControlFlowNode load, ControlFlowNode store) { } /** A non-dispatched getNode() to avoid negative recursion issues */ -private AstNode toAst(ControlFlowNode n) { py_flow_bb_node(n, result, _, _) } +private Py::AstNode toAst(ControlFlowNode n) { py_flow_bb_node(n, result, _, _) } /** * A control flow node. Control flow nodes have a many-to-one relation with syntactic nodes, @@ -35,19 +35,19 @@ private AstNode toAst(ControlFlowNode n) { py_flow_bb_node(n, result, _, _) } class ControlFlowNode extends @py_flow_node { /** Whether this control flow node is a load (including those in augmented assignments) */ predicate isLoad() { - exists(Expr e | e = toAst(this) | py_expr_contexts(_, 3, e) and not augstore(_, this)) + exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 3, e) and not augstore(_, this)) } /** Whether this control flow node is a store (including those in augmented assignments) */ predicate isStore() { - exists(Expr e | e = toAst(this) | py_expr_contexts(_, 5, e) or augstore(_, this)) + exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 5, e) or augstore(_, this)) } /** Whether this control flow node is a delete */ - predicate isDelete() { exists(Expr e | e = toAst(this) | py_expr_contexts(_, 2, e)) } + predicate isDelete() { exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 2, e)) } /** Whether this control flow node is a parameter */ - predicate isParameter() { exists(Expr e | e = toAst(this) | py_expr_contexts(_, 4, e)) } + predicate isParameter() { exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 4, e)) } /** Whether this control flow node is a store in an augmented assignment */ predicate isAugStore() { augstore(_, this) } @@ -57,61 +57,61 @@ class ControlFlowNode extends @py_flow_node { /** Whether this flow node corresponds to a literal */ predicate isLiteral() { - toAst(this) instanceof Bytes + toAst(this) instanceof Py::Bytes or - toAst(this) instanceof Dict + toAst(this) instanceof Py::Dict or - toAst(this) instanceof DictComp + toAst(this) instanceof Py::DictComp or - toAst(this) instanceof Set + toAst(this) instanceof Py::Set or - toAst(this) instanceof SetComp + toAst(this) instanceof Py::SetComp or - toAst(this) instanceof Ellipsis + toAst(this) instanceof Py::Ellipsis or - toAst(this) instanceof GeneratorExp + toAst(this) instanceof Py::GeneratorExp or - toAst(this) instanceof Lambda + toAst(this) instanceof Py::Lambda or - toAst(this) instanceof ListComp + toAst(this) instanceof Py::ListComp or - toAst(this) instanceof List + toAst(this) instanceof Py::List or - toAst(this) instanceof Num + toAst(this) instanceof Py::Num or - toAst(this) instanceof Tuple + toAst(this) instanceof Py::Tuple or - toAst(this) instanceof Unicode + toAst(this) instanceof Py::Unicode or - toAst(this) instanceof NameConstant + toAst(this) instanceof Py::NameConstant } /** Whether this flow node corresponds to an attribute expression */ - predicate isAttribute() { toAst(this) instanceof Attribute } + predicate isAttribute() { toAst(this) instanceof Py::Attribute } /** Whether this flow node corresponds to an subscript expression */ - predicate isSubscript() { toAst(this) instanceof Subscript } + predicate isSubscript() { toAst(this) instanceof Py::Subscript } /** Whether this flow node corresponds to an import member */ - predicate isImportMember() { toAst(this) instanceof ImportMember } + predicate isImportMember() { toAst(this) instanceof Py::ImportMember } /** Whether this flow node corresponds to a call */ - predicate isCall() { toAst(this) instanceof Call } + predicate isCall() { toAst(this) instanceof Py::Call } /** Whether this flow node is the first in a module */ - predicate isModuleEntry() { this.isEntryNode() and toAst(this) instanceof Module } + predicate isModuleEntry() { this.isEntryNode() and toAst(this) instanceof Py::Module } /** Whether this flow node corresponds to an import */ - predicate isImport() { toAst(this) instanceof ImportExpr } + predicate isImport() { toAst(this) instanceof Py::ImportExpr } /** Whether this flow node corresponds to a conditional expression */ - predicate isIfExp() { toAst(this) instanceof IfExp } + predicate isIfExp() { toAst(this) instanceof Py::IfExp } /** Whether this flow node corresponds to a function definition expression */ - predicate isFunction() { toAst(this) instanceof FunctionExpr } + predicate isFunction() { toAst(this) instanceof Py::FunctionExpr } /** Whether this flow node corresponds to a class definition expression */ - predicate isClass() { toAst(this) instanceof ClassExpr } + predicate isClass() { toAst(this) instanceof Py::ClassExpr } /** Gets a predecessor of this flow node */ ControlFlowNode getAPredecessor() { this = result.getASuccessor() } @@ -123,25 +123,25 @@ class ControlFlowNode extends @py_flow_node { ControlFlowNode getImmediateDominator() { py_idoms(this, result) } /** Gets the syntactic element corresponding to this flow node */ - AstNode getNode() { py_flow_bb_node(this, result, _, _) } + Py::AstNode getNode() { py_flow_bb_node(this, result, _, _) } /** Gets a textual representation of this element. */ cached string toString() { Stages::AST::ref() and // Since modules can have ambigous names, entry nodes can too, if we do not collate them. - exists(Scope s | s.getEntryNode() = this | + exists(Py::Scope s | s.getEntryNode() = this | result = "Entry node for " + concat( | | s.toString(), ",") ) or - exists(Scope s | s.getANormalExit() = this | result = "Exit node for " + s.toString()) + exists(Py::Scope s | s.getANormalExit() = this | result = "Exit node for " + s.toString()) or - not exists(Scope s | s.getEntryNode() = this or s.getANormalExit() = this) and + not exists(Py::Scope s | s.getEntryNode() = this or s.getANormalExit() = this) and result = "ControlFlowNode for " + this.getNode().toString() } /** Gets the location of this ControlFlowNode */ - Location getLocation() { result = this.getNode().getLocation() } + Py::Location getLocation() { result = this.getNode().getLocation() } /** Whether this flow node is the first in its scope */ predicate isEntryNode() { py_scope_flow(this, _, -1) } @@ -151,9 +151,9 @@ class ControlFlowNode extends @py_flow_node { /** Gets the scope containing this flow node */ cached - Scope getScope() { + Py::Scope getScope() { Stages::AST::ref() and - if this.getNode() instanceof Scope + if this.getNode() instanceof Py::Scope then /* Entry or exit node */ result = this.getNode() @@ -161,7 +161,7 @@ class ControlFlowNode extends @py_flow_node { } /** Gets the enclosing module */ - Module getEnclosingModule() { result = this.getScope().getEnclosingModule() } + Py::Module getEnclosingModule() { result = this.getScope().getEnclosingModule() } /** Gets a successor for this node if the relevant condition is True. */ ControlFlowNode getATrueSuccessor() { @@ -188,7 +188,7 @@ class ControlFlowNode extends @py_flow_node { } /** Whether the scope may be exited as a result of this node raising an exception */ - predicate isExceptionalExit(Scope s) { py_scope_flow(this, s, 1) } + predicate isExceptionalExit(Py::Scope s) { py_scope_flow(this, s, 1) } /** Whether this node is a normal (non-exceptional) exit */ predicate isNormalExit() { py_scope_flow(this, _, 0) or py_scope_flow(this, _, 2) } @@ -198,7 +198,7 @@ class ControlFlowNode extends @py_flow_node { pragma[inline] predicate strictlyDominates(ControlFlowNode other) { // This predicate is gigantic, so it must be inlined. - // About 1.4 billion tuples for OpenStack Cinder. + // About 1.4 billion tuples for OpenStack Py::Cinder. this.getBasicBlock().strictlyDominates(other.getBasicBlock()) or exists(BasicBlock b, int i, int j | this = b.getNode(i) and other = b.getNode(j) and i < j) @@ -236,7 +236,7 @@ class ControlFlowNode extends @py_flow_node { /* join-ordering helper for `getAChild() */ pragma[noinline] private ControlFlowNode getExprChild(BasicBlock dom) { - this.getNode().(Expr).getAChildNode() = result.getNode() and + this.getNode().(Py::Expr).getAChildNode() = result.getNode() and result.getBasicBlock().dominates(dom) and not this instanceof UnaryExprNode } @@ -249,16 +249,16 @@ class ControlFlowNode extends @py_flow_node { */ private class AnyNode extends ControlFlowNode { - override AstNode getNode() { result = super.getNode() } + override Py::AstNode getNode() { result = super.getNode() } } /** A control flow node corresponding to a call expression, such as `func(...)` */ class CallNode extends ControlFlowNode { - CallNode() { toAst(this) instanceof Call } + CallNode() { toAst(this) instanceof Py::Call } /** Gets the flow node corresponding to the function expression for the call corresponding to this flow node */ ControlFlowNode getFunction() { - exists(Call c | + exists(Py::Call c | this.getNode() = c and c.getFunc() = result.getNode() and result.getBasicBlock().dominates(this.getBasicBlock()) @@ -267,7 +267,7 @@ class CallNode extends ControlFlowNode { /** Gets the flow node corresponding to the n'th positional argument of the call corresponding to this flow node */ ControlFlowNode getArg(int n) { - exists(Call c | + exists(Py::Call c | this.getNode() = c and c.getArg(n) = result.getNode() and result.getBasicBlock().dominates(this.getBasicBlock()) @@ -276,7 +276,7 @@ class CallNode extends ControlFlowNode { /** Gets the flow node corresponding to the named argument of the call corresponding to this flow node */ ControlFlowNode getArgByName(string name) { - exists(Call c, Keyword k | + exists(Py::Call c, Py::Keyword k | this.getNode() = c and k = c.getANamedArg() and k.getValue() = result.getNode() and @@ -292,7 +292,7 @@ class CallNode extends ControlFlowNode { result = this.getArgByName(_) } - override Call getNode() { result = super.getNode() } + override Py::Call getNode() { result = super.getNode() } predicate isDecoratorCall() { this.isClassDecoratorCall() @@ -301,11 +301,11 @@ class CallNode extends ControlFlowNode { } predicate isClassDecoratorCall() { - exists(ClassExpr cls | this.getNode() = cls.getADecoratorCall()) + exists(Py::ClassExpr cls | this.getNode() = cls.getADecoratorCall()) } predicate isFunctionDecoratorCall() { - exists(FunctionExpr func | this.getNode() = func.getADecoratorCall()) + exists(Py::FunctionExpr func | this.getNode() = func.getADecoratorCall()) } /** Gets the first tuple (*) argument of this call, if any. */ @@ -323,11 +323,11 @@ class CallNode extends ControlFlowNode { /** A control flow corresponding to an attribute expression, such as `value.attr` */ class AttrNode extends ControlFlowNode { - AttrNode() { toAst(this) instanceof Attribute } + AttrNode() { toAst(this) instanceof Py::Attribute } /** Gets the flow node corresponding to the object of the attribute expression corresponding to this flow node */ ControlFlowNode getObject() { - exists(Attribute a | + exists(Py::Attribute a | this.getNode() = a and a.getObject() = result.getNode() and result.getBasicBlock().dominates(this.getBasicBlock()) @@ -339,7 +339,7 @@ class AttrNode extends ControlFlowNode { * with the matching name */ ControlFlowNode getObject(string name) { - exists(Attribute a | + exists(Py::Attribute a | this.getNode() = a and a.getObject() = result.getNode() and a.getName() = name and @@ -348,57 +348,57 @@ class AttrNode extends ControlFlowNode { } /** Gets the attribute name of the attribute expression corresponding to this flow node */ - string getName() { exists(Attribute a | this.getNode() = a and a.getName() = result) } + string getName() { exists(Py::Attribute a | this.getNode() = a and a.getName() = result) } - override Attribute getNode() { result = super.getNode() } + override Py::Attribute getNode() { result = super.getNode() } } /** A control flow node corresponding to a `from ... import ...` expression */ class ImportMemberNode extends ControlFlowNode { - ImportMemberNode() { toAst(this) instanceof ImportMember } + ImportMemberNode() { toAst(this) instanceof Py::ImportMember } /** * Gets the flow node corresponding to the module in the import-member expression corresponding to this flow node, * with the matching name */ ControlFlowNode getModule(string name) { - exists(ImportMember i | this.getNode() = i and i.getModule() = result.getNode() | + exists(Py::ImportMember i | this.getNode() = i and i.getModule() = result.getNode() | i.getName() = name and result.getBasicBlock().dominates(this.getBasicBlock()) ) } - override ImportMember getNode() { result = super.getNode() } + override Py::ImportMember getNode() { result = super.getNode() } } /** A control flow node corresponding to an artificial expression representing an import */ class ImportExprNode extends ControlFlowNode { - ImportExprNode() { toAst(this) instanceof ImportExpr } + ImportExprNode() { toAst(this) instanceof Py::ImportExpr } - override ImportExpr getNode() { result = super.getNode() } + override Py::ImportExpr getNode() { result = super.getNode() } } /** A control flow node corresponding to a `from ... import *` statement */ class ImportStarNode extends ControlFlowNode { - ImportStarNode() { toAst(this) instanceof ImportStar } + ImportStarNode() { toAst(this) instanceof Py::ImportStar } /** Gets the flow node corresponding to the module in the import-star corresponding to this flow node */ ControlFlowNode getModule() { - exists(ImportStar i | this.getNode() = i and i.getModuleExpr() = result.getNode() | + exists(Py::ImportStar i | this.getNode() = i and i.getModuleExpr() = result.getNode() | result.getBasicBlock().dominates(this.getBasicBlock()) ) } - override ImportStar getNode() { result = super.getNode() } + override Py::ImportStar getNode() { result = super.getNode() } } /** A control flow node corresponding to a subscript expression, such as `value[slice]` */ class SubscriptNode extends ControlFlowNode { - SubscriptNode() { toAst(this) instanceof Subscript } + SubscriptNode() { toAst(this) instanceof Py::Subscript } /** flow node corresponding to the value of the sequence in a subscript operation */ ControlFlowNode getObject() { - exists(Subscript s | + exists(Py::Subscript s | this.getNode() = s and s.getObject() = result.getNode() and result.getBasicBlock().dominates(this.getBasicBlock()) @@ -407,23 +407,23 @@ class SubscriptNode extends ControlFlowNode { /** flow node corresponding to the index in a subscript operation */ ControlFlowNode getIndex() { - exists(Subscript s | + exists(Py::Subscript s | this.getNode() = s and s.getIndex() = result.getNode() and result.getBasicBlock().dominates(this.getBasicBlock()) ) } - override Subscript getNode() { result = super.getNode() } + override Py::Subscript getNode() { result = super.getNode() } } /** A control flow node corresponding to a comparison operation, such as `x DeletionNode -> NameNode('b') -> AttrNode('y') -> DeletionNode`. */ class DeletionNode extends ControlFlowNode { - DeletionNode() { toAst(this) instanceof Delete } + DeletionNode() { toAst(this) instanceof Py::Delete } /** Gets the unique target of this deletion node. */ ControlFlowNode getTarget() { result.getASuccessor() = this } @@ -617,9 +617,9 @@ class DeletionNode extends ControlFlowNode { /** A control flow node corresponding to a sequence (tuple or list) literal */ abstract class SequenceNode extends ControlFlowNode { SequenceNode() { - toAst(this) instanceof Tuple + toAst(this) instanceof Py::Tuple or - toAst(this) instanceof List + toAst(this) instanceof Py::List } /** Gets the control flow node for an element of this sequence */ @@ -632,11 +632,11 @@ abstract class SequenceNode extends ControlFlowNode { /** A control flow node corresponding to a tuple expression such as `( 1, 3, 5, 7, 9 )` */ class TupleNode extends SequenceNode { - TupleNode() { toAst(this) instanceof Tuple } + TupleNode() { toAst(this) instanceof Py::Tuple } override ControlFlowNode getElement(int n) { Stages::AST::ref() and - exists(Tuple t | this.getNode() = t and result.getNode() = t.getElt(n)) and + exists(Py::Tuple t | this.getNode() = t and result.getNode() = t.getElt(n)) and ( result.getBasicBlock().dominates(this.getBasicBlock()) or @@ -647,10 +647,10 @@ class TupleNode extends SequenceNode { /** A control flow node corresponding to a list expression, such as `[ 1, 3, 5, 7, 9 ]` */ class ListNode extends SequenceNode { - ListNode() { toAst(this) instanceof List } + ListNode() { toAst(this) instanceof Py::List } override ControlFlowNode getElement(int n) { - exists(List l | this.getNode() = l and result.getNode() = l.getElt(n)) and + exists(Py::List l | this.getNode() = l and result.getNode() = l.getElt(n)) and ( result.getBasicBlock().dominates(this.getBasicBlock()) or @@ -661,10 +661,10 @@ class ListNode extends SequenceNode { /** A control flow node corresponding to a set expression, such as `{ 1, 3, 5, 7, 9 }` */ class SetNode extends ControlFlowNode { - SetNode() { toAst(this) instanceof Set } + SetNode() { toAst(this) instanceof Py::Set } ControlFlowNode getAnElement() { - exists(Set s | this.getNode() = s and result.getNode() = s.getElt(_)) and + exists(Py::Set s | this.getNode() = s and result.getNode() = s.getElt(_)) and ( result.getBasicBlock().dominates(this.getBasicBlock()) or @@ -675,20 +675,20 @@ class SetNode extends ControlFlowNode { /** A control flow node corresponding to a dictionary literal, such as `{ 'a': 1, 'b': 2 }` */ class DictNode extends ControlFlowNode { - DictNode() { toAst(this) instanceof Dict } + DictNode() { toAst(this) instanceof Py::Dict } /** * Gets a key of this dictionary literal node, for those items that have keys * E.g, in {'a':1, **b} this returns only 'a' */ ControlFlowNode getAKey() { - exists(Dict d | this.getNode() = d and result.getNode() = d.getAKey()) and + exists(Py::Dict d | this.getNode() = d and result.getNode() = d.getAKey()) and result.getBasicBlock().dominates(this.getBasicBlock()) } /** Gets a value of this dictionary literal node */ ControlFlowNode getAValue() { - exists(Dict d | this.getNode() = d and result.getNode() = d.getAValue()) and + exists(Py::Dict d | this.getNode() = d and result.getNode() = d.getAValue()) and result.getBasicBlock().dominates(this.getBasicBlock()) } } @@ -712,21 +712,23 @@ class IterableNode extends ControlFlowNode { } } -private AstNode assigned_value(Expr lhs) { +private Py::AstNode assigned_value(Py::Expr lhs) { /* lhs = result */ - exists(Assign a | a.getATarget() = lhs and result = a.getValue()) + exists(Py::Assign a | a.getATarget() = lhs and result = a.getValue()) or /* lhs := result */ - exists(AssignExpr a | a.getTarget() = lhs and result = a.getValue()) + exists(Py::AssignExpr a | a.getTarget() = lhs and result = a.getValue()) or /* lhs : annotation = result */ - exists(AnnAssign a | a.getTarget() = lhs and result = a.getValue()) + exists(Py::AnnAssign a | a.getTarget() = lhs and result = a.getValue()) or /* import result as lhs */ - exists(Alias a | a.getAsname() = lhs and result = a.getValue()) + exists(Py::Alias a | a.getAsname() = lhs and result = a.getValue()) or /* lhs += x => result = (lhs + x) */ - exists(AugAssign a, BinaryExpr b | b = a.getOperation() and result = b and lhs = b.getLeft()) + exists(Py::AugAssign a, Py::BinaryExpr b | + b = a.getOperation() and result = b and lhs = b.getLeft() + ) or /* * ..., lhs, ... = ..., result, ... @@ -734,31 +736,31 @@ private AstNode assigned_value(Expr lhs) { * ..., (..., lhs, ...), ... = ..., (..., result, ...), ... */ - exists(Assign a | nested_sequence_assign(a.getATarget(), a.getValue(), lhs, result)) + exists(Py::Assign a | nested_sequence_assign(a.getATarget(), a.getValue(), lhs, result)) or /* for lhs in seq: => `result` is the `for` node, representing the `iter(next(seq))` operation. */ - result.(For).getTarget() = lhs + result.(Py::For).getTarget() = lhs or - exists(Parameter param | lhs = param.asName() and result = param.getDefault()) + exists(Py::Parameter param | lhs = param.asName() and result = param.getDefault()) } predicate nested_sequence_assign( - Expr left_parent, Expr right_parent, Expr left_result, Expr right_result + Py::Expr left_parent, Py::Expr right_parent, Py::Expr left_result, Py::Expr right_result ) { - exists(Assign a | + exists(Py::Assign a | a.getATarget().getASubExpression*() = left_parent and a.getValue().getASubExpression*() = right_parent ) and - exists(int i, Expr left_elem, Expr right_elem | + exists(int i, Py::Expr left_elem, Py::Expr right_elem | ( - left_elem = left_parent.(Tuple).getElt(i) + left_elem = left_parent.(Py::Tuple).getElt(i) or - left_elem = left_parent.(List).getElt(i) + left_elem = left_parent.(Py::List).getElt(i) ) and ( - right_elem = right_parent.(Tuple).getElt(i) + right_elem = right_parent.(Py::Tuple).getElt(i) or - right_elem = right_parent.(List).getElt(i) + right_elem = right_parent.(Py::List).getElt(i) ) | left_result = left_elem and right_result = right_elem @@ -769,9 +771,9 @@ predicate nested_sequence_assign( /** A flow node for a `for` statement. */ class ForNode extends ControlFlowNode { - ForNode() { toAst(this) instanceof For } + ForNode() { toAst(this) instanceof Py::For } - override For getNode() { result = super.getNode() } + override Py::For getNode() { result = super.getNode() } /** Holds if this `for` statement causes iteration over `sequence` storing each step of the iteration in `target` */ predicate iterates(ControlFlowNode target, ControlFlowNode sequence) { @@ -782,7 +784,7 @@ class ForNode extends ControlFlowNode { /** Gets the sequence node for this `for` statement. */ ControlFlowNode getSequence() { - exists(For for | + exists(Py::For for | toAst(this) = for and for.getIter() = result.getNode() | @@ -792,7 +794,7 @@ class ForNode extends ControlFlowNode { /** A possible `target` for this `for` statement, not accounting for loop unrolling */ private ControlFlowNode possibleTarget() { - exists(For for | + exists(Py::For for | toAst(this) = for and for.getTarget() = result.getNode() and this.getBasicBlock().dominates(result.getBasicBlock()) @@ -809,11 +811,11 @@ class ForNode extends ControlFlowNode { /** A flow node for a `raise` statement */ class RaiseStmtNode extends ControlFlowNode { - RaiseStmtNode() { toAst(this) instanceof Raise } + RaiseStmtNode() { toAst(this) instanceof Py::Raise } /** Gets the control flow node for the exception raised by this raise statement */ ControlFlowNode getException() { - exists(Raise r | + exists(Py::Raise r | r = toAst(this) and r.getException() = toAst(result) and result.getBasicBlock().dominates(this.getBasicBlock()) @@ -827,36 +829,36 @@ class RaiseStmtNode extends ControlFlowNode { */ class NameNode extends ControlFlowNode { NameNode() { - exists(Name n | py_flow_bb_node(this, n, _, _)) + exists(Py::Name n | py_flow_bb_node(this, n, _, _)) or - exists(PlaceHolder p | py_flow_bb_node(this, p, _, _)) + exists(Py::PlaceHolder p | py_flow_bb_node(this, p, _, _)) } /** Whether this flow node defines the variable `v`. */ - predicate defines(Variable v) { - exists(Name d | this.getNode() = d and d.defines(v)) and + predicate defines(Py::Variable v) { + exists(Py::Name d | this.getNode() = d and d.defines(v)) and not this.isLoad() } /** Whether this flow node deletes the variable `v`. */ - predicate deletes(Variable v) { exists(Name d | this.getNode() = d and d.deletes(v)) } + predicate deletes(Py::Variable v) { exists(Py::Name d | this.getNode() = d and d.deletes(v)) } /** Whether this flow node uses the variable `v`. */ - predicate uses(Variable v) { + predicate uses(Py::Variable v) { this.isLoad() and - exists(Name u | this.getNode() = u and u.uses(v)) + exists(Py::Name u | this.getNode() = u and u.uses(v)) or - exists(PlaceHolder u | - this.getNode() = u and u.getVariable() = v and u.getCtx() instanceof Load + exists(Py::PlaceHolder u | + this.getNode() = u and u.getVariable() = v and u.getCtx() instanceof Py::Load ) or Scopes::use_of_global_variable(this, v.getScope(), v.getId()) } string getId() { - result = this.getNode().(Name).getId() + result = this.getNode().(Py::Name).getId() or - result = this.getNode().(PlaceHolder).getId() + result = this.getNode().(Py::PlaceHolder).getId() } /** Whether this is a use of a local variable. */ @@ -868,82 +870,84 @@ class NameNode extends ControlFlowNode { /** Whether this is a use of a global (including builtin) variable. */ predicate isGlobal() { Scopes::use_of_global_variable(this, _, _) } - predicate isSelf() { exists(SsaVariable selfvar | selfvar.isSelf() and selfvar.getAUse() = this) } + predicate isSelf() { + exists(Py::SsaVariable selfvar | selfvar.isSelf() and selfvar.getAUse() = this) + } } /** A control flow node corresponding to a named constant, one of `None`, `True` or `False`. */ class NameConstantNode extends NameNode { - NameConstantNode() { exists(NameConstant n | py_flow_bb_node(this, n, _, _)) } + NameConstantNode() { exists(Py::NameConstant n | py_flow_bb_node(this, n, _, _)) } /* * We ought to override uses as well, but that has * a serious performance impact. - * deprecated predicate uses(Variable v) { none() } + * deprecated predicate uses(Py::Variable v) { none() } */ } /** A control flow node corresponding to a starred expression, `*a`. */ class StarredNode extends ControlFlowNode { - StarredNode() { toAst(this) instanceof Starred } + StarredNode() { toAst(this) instanceof Py::Starred } - ControlFlowNode getValue() { toAst(result) = toAst(this).(Starred).getValue() } + ControlFlowNode getValue() { toAst(result) = toAst(this).(Py::Starred).getValue() } } /** The ControlFlowNode for an 'except' statement. */ class ExceptFlowNode extends ControlFlowNode { - ExceptFlowNode() { this.getNode() instanceof ExceptStmt } + ExceptFlowNode() { this.getNode() instanceof Py::ExceptStmt } /** * Gets the type handled by this exception handler. - * `ExceptionType` in `except ExceptionType as e:` + * `Py::ExceptionType` in `except Py::ExceptionType as e:` */ ControlFlowNode getType() { - exists(ExceptStmt ex | + exists(Py::ExceptStmt ex | this.getBasicBlock().dominates(result.getBasicBlock()) and ex = this.getNode() and - result = ex.getType().getAFlowNode() + result.getNode() = ex.getType() ) } /** * Gets the name assigned to the handled exception, if any. - * `e` in `except ExceptionType as e:` + * `e` in `except Py::ExceptionType as e:` */ ControlFlowNode getName() { - exists(ExceptStmt ex | + exists(Py::ExceptStmt ex | this.getBasicBlock().dominates(result.getBasicBlock()) and ex = this.getNode() and - result = ex.getName().getAFlowNode() + result.getNode() = ex.getName() ) } } /** The ControlFlowNode for an 'except*' statement. */ class ExceptGroupFlowNode extends ControlFlowNode { - ExceptGroupFlowNode() { this.getNode() instanceof ExceptGroupStmt } + ExceptGroupFlowNode() { this.getNode() instanceof Py::ExceptGroupStmt } /** * Gets the type handled by this exception handler. - * `ExceptionType` in `except* ExceptionType as e:` + * `Py::ExceptionType` in `except* Py::ExceptionType as e:` */ ControlFlowNode getType() { this.getBasicBlock().dominates(result.getBasicBlock()) and - result = this.getNode().(ExceptGroupStmt).getType().getAFlowNode() + result.getNode() = this.getNode().(Py::ExceptGroupStmt).getType() } /** * Gets the name assigned to the handled exception, if any. - * `e` in `except* ExceptionType as e:` + * `e` in `except* Py::ExceptionType as e:` */ ControlFlowNode getName() { this.getBasicBlock().dominates(result.getBasicBlock()) and - result = this.getNode().(ExceptGroupStmt).getName().getAFlowNode() + result.getNode() = this.getNode().(Py::ExceptGroupStmt).getName() } } private module Scopes { private predicate fast_local(NameNode n) { - exists(FastLocalVariable v | + exists(Py::FastLocalVariable v | n.uses(v) and v.getScope() = n.getScope() ) @@ -952,15 +956,15 @@ private module Scopes { predicate local(NameNode n) { fast_local(n) or - exists(SsaVariable var | + exists(Py::SsaVariable var | var.getAUse() = n and - n.getScope() instanceof Class and + n.getScope() instanceof Py::Class and exists(var.getDefinition()) ) } predicate non_local(NameNode n) { - exists(FastLocalVariable flv | + exists(Py::FastLocalVariable flv | flv.getALoad() = n.getNode() and not flv.getScope() = n.getScope() ) @@ -968,20 +972,20 @@ private module Scopes { // magic is fine, but we get questionable join-ordering of it pragma[nomagic] - predicate use_of_global_variable(NameNode n, Module scope, string name) { + predicate use_of_global_variable(NameNode n, Py::Module scope, string name) { n.isLoad() and not non_local(n) and - not exists(SsaVariable var | var.getAUse() = n | - var.getVariable() instanceof FastLocalVariable + not exists(Py::SsaVariable var | var.getAUse() = n | + var.getVariable() instanceof Py::FastLocalVariable or - n.getScope() instanceof Class and + n.getScope() instanceof Py::Class and not maybe_undefined(var) ) and name = n.getId() and scope = n.getEnclosingModule() } - private predicate maybe_undefined(SsaVariable var) { + private predicate maybe_undefined(Py::SsaVariable var) { not exists(var.getDefinition()) and not py_ssa_phi(var, _) or var.getDefinition().isDelete() @@ -1058,13 +1062,13 @@ class BasicBlock extends @py_flow_node { private predicate oneNodeBlock() { this.firstNode() = this.getLastNode() } private predicate startLocationInfo(string file, int line, int col) { - if this.firstNode().getNode() instanceof Scope + if this.firstNode().getNode() instanceof Py::Scope then this.firstNode().getASuccessor().getLocation().hasLocationInfo(file, line, col, _, _) else this.firstNode().getLocation().hasLocationInfo(file, line, col, _, _) } private predicate endLocationInfo(int endl, int endc) { - if this.getLastNode().getNode() instanceof Scope and not this.oneNodeBlock() + if this.getLastNode().getNode() instanceof Py::Scope and not this.oneNodeBlock() then this.getLastNode().getAPredecessor().getLocation().hasLocationInfo(_, _, _, endl, endc) else this.getLastNode().getLocation().hasLocationInfo(_, _, _, endl, endc) } @@ -1081,7 +1085,7 @@ class BasicBlock extends @py_flow_node { /** Whether flow from this basic block reaches a normal exit from its scope */ predicate reachesExit() { - exists(Scope s | s.getANormalExit().getBasicBlock() = this) + exists(Py::Scope s | s.getANormalExit().getBasicBlock() = this) or this.getASuccessor().reachesExit() } @@ -1090,7 +1094,7 @@ class BasicBlock extends @py_flow_node { * 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 + * Py::For more information, see * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). */ predicate hasLocationInfo( @@ -1122,7 +1126,7 @@ class BasicBlock extends @py_flow_node { /** Gets the scope of this block */ pragma[nomagic] - Scope getScope() { + Py::Scope getScope() { exists(ControlFlowNode n | n.getBasicBlock() = this | /* Take care not to use an entry or exit node as that node's scope will be the outer scope */ not py_scope_flow(n, _, -1) and @@ -1145,17 +1149,17 @@ class BasicBlock extends @py_flow_node { predicate reaches(BasicBlock other) { this = other or this.strictlyReaches(other) } /** - * Gets the `ConditionBlock`, if any, that controls this block and - * does not control any other `ConditionBlock`s that control this block. - * That is the `ConditionBlock` that is closest dominator. + * Gets the `Py::ConditionBlock`, if any, that controls this block and + * does not control any other `Py::ConditionBlock`s that control this block. + * That is the `Py::ConditionBlock` that is closest dominator. */ - ConditionBlock getImmediatelyControllingBlock() { + Py::ConditionBlock getImmediatelyControllingBlock() { result = this.nonControllingImmediateDominator*().getImmediateDominator() } private BasicBlock nonControllingImmediateDominator() { result = this.getImmediateDominator() and - not result.(ConditionBlock).controls(this, _) + not result.(Py::ConditionBlock).controls(this, _) } /** @@ -1175,7 +1179,7 @@ private class ControlFlowNodeAlias = ControlFlowNode; final private class FinalBasicBlock = BasicBlock; -module Cfg implements BB::CfgSig { +module Cfg implements BB::CfgSig { private import codeql.controlflow.SuccessorType class ControlFlowNode = ControlFlowNodeAlias; @@ -1186,7 +1190,7 @@ module Cfg implements BB::CfgSig { // Using the location of the first node is simple // and we just need a way to identify the basic block // during debugging, so this will be serviceable. - Location getLocation() { result = super.getNode(0).getLocation() } + Py::Location getLocation() { result = super.getNode(0).getLocation() } int length() { result = count(int i | exists(this.getNode(i))) } diff --git a/python/ql/lib/semmle/python/Import.qll b/python/ql/lib/semmle/python/Import.qll index 2f7fae955399..d4f5109ed47c 100644 --- a/python/ql/lib/semmle/python/Import.qll +++ b/python/ql/lib/semmle/python/Import.qll @@ -162,8 +162,6 @@ class ImportMember extends ImportMember_ { string getImportedModuleName() { result = this.getModule().(ImportExpr).getImportedModuleName() + "." + this.getName() } - - override ImportMemberNode getAFlowNode() { result = super.getAFlowNode() } } /** An import statement */ diff --git a/python/ql/lib/semmle/python/SelfAttribute.qll b/python/ql/lib/semmle/python/SelfAttribute.qll index 90ef2b38401a..364e080dcdd7 100644 --- a/python/ql/lib/semmle/python/SelfAttribute.qll +++ b/python/ql/lib/semmle/python/SelfAttribute.qll @@ -46,20 +46,23 @@ class SelfAttributeRead extends SelfAttribute { } predicate guardedByHasattr() { - exists(Variable var, ControlFlowNode n | - var.getAUse() = this.getObject().getAFlowNode() and + exists(Variable var, ControlFlowNode n, ControlFlowNode this_, ControlFlowNode obj_ | + this_.getNode() = this and obj_.getNode() = this.getObject() + | + var.getAUse() = obj_ and hasattr(n, var.getAUse(), this.getName()) and - n.strictlyDominates(this.getAFlowNode()) + n.strictlyDominates(this_) ) } pragma[noinline] predicate locallyDefined() { - exists(SelfAttributeStore store | - this.getName() = store.getName() and - this.getScope() = store.getScope() + exists(SelfAttributeStore store, ControlFlowNode store_, ControlFlowNode this_ | + store_.getNode() = store and this_.getNode() = this | - store.getAFlowNode().strictlyDominates(this.getAFlowNode()) + this.getName() = store.getName() and + this.getScope() = store.getScope() and + store_.strictlyDominates(this_) ) } } diff --git a/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll b/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll new file mode 100644 index 000000000000..72f3a2e58fd7 --- /dev/null +++ b/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll @@ -0,0 +1,1595 @@ +/** + * Provides classes for the shared control-flow library, mediating between + * the Python AST and `AstSig`. + * + * The `Ast` module wraps Python's `Stmt`, `Expr`, `Scope`, and `Pattern`, + * and adds two synthetic kinds of node: + * - `BlockStmt`, identifying a body slot of a parent AST node (e.g. an + * `if`'s then or else branch). `Py::StmtList` itself is not directly + * wrapped. + * - Intermediate nodes for multi-operand boolean expressions. + */ +overlay[local?] +module; + +private import python as Py +private import codeql.controlflow.ControlFlowGraph +private import codeql.controlflow.SuccessorType +private import codeql.util.Void + +/** + * Gets the bound `Name` of a PEP 695 type parameter (`TypeVar`, + * `ParamSpec`, or `TypeVarTuple`). The base `TypeParameter` class does + * not expose `getName()`; this helper dispatches over the subtypes. + */ +private Py::Name typeParameterName(Py::TypeParameter tp) { + result = tp.(Py::TypeVar).getName() + or + result = tp.(Py::ParamSpec).getName() + or + result = tp.(Py::TypeVarTuple).getName() +} + +/** Provides the Python implementation of the shared CFG `AstSig`. */ +module Ast implements AstSig { + private newtype TAstNode = + TPyStmt(Py::Stmt s) or + TPyExpr(Py::Expr e) { not e instanceof Py::BoolExpr } or + TScope(Py::Scope sc) or + TPattern(Py::Pattern p) or + /** + * A synthetic node representing an operand pair of an `and`/`or` + * expression. For `a and b and c` (operands 0, 1, 2) we model the + * operation as a right-nested tree: pair 0 represents the whole + * expression with left=a and right=pair 1; pair 1 represents + * `b and c` with left=b and right=c. Each Python `Py::BoolExpr` + * with `n` operands has `n - 1` such pairs (indices `0 .. n - 2`). + */ + TBoolExprPair(Py::BoolExpr be, int index) { index = [0 .. count(be.getAValue()) - 2] } or + /** + * A synthetic block statement, wrapping a `Py::StmtList`. Each list of + * statements that represents an imperative block (a function/class/module + * body, an `if`/`while`/`for` branch, a `try`/`except`/`finally` body, + * etc.) becomes one `BlockStmt` node in the CFG. `Py::StmtList`s used + * in other roles - `Try.getHandlers()` (iterated via `getCatch`) and + * `MatchStmt.getCases()` (iterated via `getCase`) - are excluded, as + * the shared library's `Try`/`Switch` logic walks their items + * individually. + */ + TBlockStmt(Py::StmtList sl) { + not sl = any(Py::Try t).getHandlers() and + not sl = any(Py::MatchStmt m).getCases() + } + + /** + * The union of `TPyStmt` (wrapping `Py::Stmt`) and `TBlockStmt` (wrapping + * `Py::StmtList`). Both represent the kinds of node that can appear in + * a `Stmt` position in the CFG. + */ + private class TStmt = TPyStmt or TBlockStmt; + + /** + * The union of `TPyExpr` (wrapping non-boolean `Py::Expr`) and + * `TBoolExprPair` (synthetic operand pairs of `and`/`or` expressions). + * Both represent the kinds of node that can appear in an `Expr` + * position in the CFG. + */ + private class TExpr = TPyExpr or TBoolExprPair; + + /** + * An AST node visible to the shared CFG. + * + * This is the abstract implementation class. It enforces that each + * concrete subclass provides `toString`, `getLocation`, and + * `getEnclosingCallable` (one subclass per `TAstNode` newtype branch). + * The public alias `AstNode` is what users (and the `AstSig` signature) + * see; subclasses inside this module extend `AstNodeImpl` directly. + */ + abstract private class AstNodeImpl extends TAstNode { + /** Gets a textual representation of this AST node. */ + abstract string toString(); + + /** Gets the location of this AST node. */ + abstract Py::Location getLocation(); + + /** Gets the enclosing callable that contains this node, if any. */ + abstract Callable getEnclosingCallable(); + + /** Gets the underlying Python `Stmt`, if this node wraps one. */ + Py::Stmt asStmt() { this = TPyStmt(result) } + + /** + * Gets the underlying Python `Expr`, if this node wraps one. Boolean + * expressions are represented by `TBoolExprPair(_, 0)`; this + * predicate also recovers the underlying `Py::BoolExpr` from such a + * representation. + */ + Py::Expr asExpr() { + this = TPyExpr(result) + or + this = TBoolExprPair(result, 0) + } + + /** Gets the underlying Python `Scope`, if this node wraps one. */ + Py::Scope asScope() { this = TScope(result) } + + /** Gets the underlying Python `Pattern`, if this node wraps one. */ + Py::Pattern asPattern() { this = TPattern(result) } + + /** Gets the underlying Python `StmtList`, if this node is a `BlockStmt`. */ + Py::StmtList asStmtList() { this = TBlockStmt(result) } + + /** + * Gets the child of this AST node at the specified (zero-based) + * index, in evaluation order. Subclasses with children override + * this method. + */ + AstNode getChild(int index) { none() } + } + + /** An AST node visible to the shared CFG. */ + final class AstNode = AstNodeImpl; + + /** Gets the immediately enclosing callable that contains `node`. */ + Callable getEnclosingCallable(AstNode node) { result = node.getEnclosingCallable() } + + /** + * A callable: a function, class, or module scope. + * + * In Python, all three are executable scopes with statement bodies. + */ + class Callable extends AstNodeImpl, TScope { + private Py::Scope sc; + + Callable() { this = TScope(sc) } + + override string toString() { result = sc.toString() } + + override Py::Location getLocation() { result = sc.getLocation() } + + override Callable getEnclosingCallable() { result.asScope() = sc.getEnclosingScope() } + } + + /** Gets the body of callable `c`. */ + AstNode callableGetBody(Callable c) { result.asStmtList() = c.asScope().getBody() } + + /** + * A parameter of a callable. + * + * Modelled per the C# template (`csharp/.../ControlFlowGraph.qll:147-156`): + * each Python parameter (the `Py::Parameter` AST node, which is a `Name` + * or — Python 2 only — a `Tuple` in store context) becomes a CFG node + * at a stable position in the enclosing callable's entry sequence. + * + * Default-value expressions for positional and keyword-only parameters + * are wired separately on the `FunctionDefExpr` / `LambdaExpr` wrappers + * (they evaluate at function-definition time, not at call time). + * `Parameter::getDefaultValue()` returns `none()` here, signalling to + * the shared library that the parameter never falls back to a default + * during call binding. This mirrors C# for non-optional parameters. + */ + class Parameter extends Expr { + private Py::Parameter param; + + Parameter() { this = TPyExpr(param) } + + /** Gets the underlying Python parameter. */ + Py::Parameter asParameter() { result = param } + + /** + * Gets the default-value expression of this parameter, if any. + * + * Returns `none()`: defaults evaluate at function-definition time and + * are wired into the CFG via `FunctionDefExpr.getDefault` / + * `LambdaExpr.getDefault`. The shared library calls this predicate + * to model the "missing argument → evaluate default" fallback during + * call binding, which Python does not model at the CFG level. + */ + Expr getDefaultValue() { none() } + } + + /** + * Gets the `index`th parameter of callable `c`, ordered as Python binds + * them at call time: positional, then vararg (`*args`), then + * keyword-only, then kwarg (`**kwargs`). + */ + Parameter callableGetParameter(Callable c, int index) { + exists(Py::Function f | f = c.asScope() | + result.asParameter() = + rank[index + 1](Py::Parameter p, int subOrder, int subIndex | + // positional parameters first + p = f.getArg(subIndex) and subOrder = 0 + or + // then *args + p = f.getVararg() and subOrder = 1 and subIndex = 0 + or + // then keyword-only parameters + p = f.getKeywordOnlyArg(subIndex) and subOrder = 2 + or + // finally **kwargs + p = f.getKwarg() and subOrder = 3 and subIndex = 0 + | + p order by subOrder, subIndex + ) + ) + } + + /** A statement. */ + class Stmt extends AstNodeImpl, TStmt { + // For `TPyStmt` instances, delegate to the wrapped Python statement. + // `BlockStmt` (the only `TBlockStmt` subclass) provides its own overrides. + override string toString() { result = this.asStmt().toString() } + + override Py::Location getLocation() { result = this.asStmt().getLocation() } + + override Callable getEnclosingCallable() { result.asScope() = this.asStmt().getScope() } + } + + /** An expression. */ + class Expr extends AstNodeImpl, TExpr { + // For `TPyExpr` instances, delegate to the wrapped Python expression. + // `BinaryExpr` (the only `TBoolExprPair` subclass) provides its own overrides. + override string toString() { result = this.asExpr().toString() } + + override Py::Location getLocation() { result = this.asExpr().getLocation() } + + override Callable getEnclosingCallable() { result.asScope() = this.asExpr().getScope() } + } + + /** A pattern in a `match` statement. */ + additional class Pattern extends AstNodeImpl, TPattern { + private Py::Pattern p; + + Pattern() { this = TPattern(p) } + + override string toString() { result = p.toString() } + + override Py::Location getLocation() { result = p.getLocation() } + + override Callable getEnclosingCallable() { result.asScope() = p.getScope() } + } + + /** + * A `case x` pattern that binds `x` to the matched value. + */ + additional class MatchCapturePattern extends Pattern { + private Py::MatchCapturePattern cap; + + MatchCapturePattern() { this = TPattern(cap) } + + /** Gets the bound Name expression. */ + Expr getVariable() { result.asExpr() = cap.getVariable() } + + override AstNode getChild(int index) { index = 0 and result = this.getVariable() } + } + + /** + * A `case pattern as name` pattern. + */ + additional class MatchAsPattern extends Pattern { + private Py::MatchAsPattern asp; + + MatchAsPattern() { this = TPattern(asp) } + + /** Gets the inner pattern. */ + AstNode getPattern() { result.asPattern() = asp.getPattern() } + + /** Gets the bound Name expression. */ + Expr getAlias() { result.asExpr() = asp.getAlias() } + + override AstNode getChild(int index) { + index = 0 and result = this.getPattern() + or + index = 1 and result = this.getAlias() + } + } + + /** + * A `case [a, b, *rest]` star pattern. Binds `rest` to the remaining + * elements of the sequence. + */ + additional class MatchStarPattern extends Pattern { + private Py::MatchStarPattern starp; + + MatchStarPattern() { this = TPattern(starp) } + + /** Gets the target Pattern (a `MatchCapturePattern` if `*rest`). */ + AstNode getTarget() { result.asPattern() = starp.getTarget() } + + override AstNode getChild(int index) { index = 0 and result = this.getTarget() } + } + + /** + * A `case [a, b, ...]` sequence pattern. Recurses into the sub-patterns. + */ + additional class MatchSequencePattern extends Pattern { + private Py::MatchSequencePattern seqp; + + MatchSequencePattern() { this = TPattern(seqp) } + + /** Gets the `n`th sub-pattern. */ + AstNode getPattern(int n) { result.asPattern() = seqp.getPattern(n) } + + override AstNode getChild(int index) { result = this.getPattern(index) } + } + + /** + * A `case Cls(a, b, x=y)` class pattern. + */ + additional class MatchClassPattern extends Pattern { + private Py::MatchClassPattern clsp; + + MatchClassPattern() { this = TPattern(clsp) } + + /** Gets the class expression of this class pattern. */ + Expr getClass() { result.asExpr() = clsp.getClass() } + + /** Gets the `n`th positional sub-pattern. */ + AstNode getPositional(int n) { result.asPattern() = clsp.getPositional(n) } + + /** Gets the `n`th keyword sub-pattern. */ + AstNode getKeyword(int n) { result.asPattern() = clsp.getKeyword(n) } + + private int numPositional() { result = count(int i | exists(clsp.getPositional(i))) } + + override AstNode getChild(int index) { + index = 0 and result = this.getClass() + or + result = this.getPositional(index - 1) and index >= 1 + or + result = this.getKeyword(index - 1 - this.numPositional()) and + index >= 1 + this.numPositional() + } + } + + /** + * A `case {k: v}` mapping pattern. + */ + additional class MatchMappingPattern extends Pattern { + private Py::MatchMappingPattern mapp; + + MatchMappingPattern() { this = TPattern(mapp) } + + AstNode getMapping(int n) { result.asPattern() = mapp.getMapping(n) } + + override AstNode getChild(int index) { result = this.getMapping(index) } + } + + /** + * A key-value pair inside a `case {k: v}` mapping pattern. + */ + additional class MatchKeyValuePattern extends Pattern { + private Py::MatchKeyValuePattern kvp; + + MatchKeyValuePattern() { this = TPattern(kvp) } + + AstNode getKey() { result.asPattern() = kvp.getKey() } + + AstNode getValue() { result.asPattern() = kvp.getValue() } + + override AstNode getChild(int index) { + index = 0 and result = this.getKey() + or + index = 1 and result = this.getValue() + } + } + + /** + * A `case Cls(name=value)` keyword sub-pattern. + */ + additional class MatchKeywordPattern extends Pattern { + private Py::MatchKeywordPattern kwp; + + MatchKeywordPattern() { this = TPattern(kwp) } + + Expr getAttribute() { result.asExpr() = kwp.getAttribute() } + + AstNode getValue() { result.asPattern() = kwp.getValue() } + + override AstNode getChild(int index) { + index = 0 and result = this.getAttribute() + or + index = 1 and result = this.getValue() + } + } + + /** A `case **rest` double-star mapping sub-pattern. */ + additional class MatchDoubleStarPattern extends Pattern { + private Py::MatchDoubleStarPattern dsp; + + MatchDoubleStarPattern() { this = TPattern(dsp) } + + AstNode getTarget() { result.asPattern() = dsp.getTarget() } + + override AstNode getChild(int index) { index = 0 and result = this.getTarget() } + } + + /** A `case p1 | p2 | …` or-pattern. */ + additional class MatchOrPattern extends Pattern { + private Py::MatchOrPattern orp; + + MatchOrPattern() { this = TPattern(orp) } + + AstNode getPattern(int n) { result.asPattern() = orp.getPattern(n) } + + override AstNode getChild(int index) { result = this.getPattern(index) } + } + + /** A `case 1` literal pattern. */ + additional class MatchLiteralPattern extends Pattern { + private Py::MatchLiteralPattern litp; + + MatchLiteralPattern() { this = TPattern(litp) } + + Expr getLiteral() { result.asExpr() = litp.getLiteral() } + + override AstNode getChild(int index) { index = 0 and result = this.getLiteral() } + } + + /** A `case Cls.NAME` value pattern. */ + additional class MatchValuePattern extends Pattern { + private Py::MatchValuePattern vp; + + MatchValuePattern() { this = TPattern(vp) } + + Expr getValue() { result.asExpr() = vp.getValue() } + + override AstNode getChild(int index) { index = 0 and result = this.getValue() } + } + + /** + * A block statement, modeling the body of a parent AST node as a + * sequence of statements. + */ + class BlockStmt extends Stmt, TBlockStmt { + private Py::StmtList sl; + + BlockStmt() { this = TBlockStmt(sl) } + + /** Gets the `n`th (zero-based) statement in this block. */ + Stmt getStmt(int n) { result.asStmt() = sl.getItem(n) } + + /** Gets the last statement in this block. */ + Stmt getLastStmt() { result.asStmt() = sl.getLastItem() } + + override string toString() { result = sl.toString() } + + // `Py::StmtList` has no native location; approximate with the first + // item's location. + override Py::Location getLocation() { result = sl.getItem(0).getLocation() } + + override Callable getEnclosingCallable() { + result.asScope() = sl.getParent().(Py::Scope) + or + result.asScope() = sl.getParent().(Py::Stmt).getScope() + } + + override AstNode getChild(int index) { result = this.getStmt(index) } + } + + /** An expression statement. */ + class ExprStmt extends Stmt { + private Py::ExprStmt exprStmt; + + ExprStmt() { this = TPyStmt(exprStmt) } + + /** Gets the expression in this expression statement. */ + Expr getExpr() { result.asExpr() = exprStmt.getValue() } + + override AstNode getChild(int index) { index = 0 and result = this.getExpr() } + } + + /** An assignment statement (`x = y = expr`). */ + additional class AssignStmt extends Stmt { + private Py::Assign assign; + + AssignStmt() { this = TPyStmt(assign) } + + Expr getValue() { result.asExpr() = assign.getValue() } + + Expr getTarget(int n) { result.asExpr() = assign.getTarget(n) } + + int getNumberOfTargets() { result = count(assign.getATarget()) } + + override AstNode getChild(int index) { + index = 0 and result = this.getValue() + or + result = this.getTarget(index - 1) and index >= 1 + } + } + + /** An augmented assignment statement (`x += expr`). */ + additional class AugAssignStmt extends Stmt { + private Py::AugAssign augAssign; + + AugAssignStmt() { this = TPyStmt(augAssign) } + + Expr getOperation() { result.asExpr() = augAssign.getOperation() } + + override AstNode getChild(int index) { index = 0 and result = this.getOperation() } + } + + /** + * An annotated assignment statement (`x: T = expr`, or `x: T` without + * value). The evaluation order follows CPython: annotation first, then + * the optional value, then the target binding. + */ + additional class AnnAssignStmt extends Stmt { + private Py::AnnAssign annAssign; + + AnnAssignStmt() { this = TPyStmt(annAssign) } + + Expr getAnnotation() { result.asExpr() = annAssign.getAnnotation() } + + Expr getValue() { result.asExpr() = annAssign.getValue() } + + Expr getTarget() { result.asExpr() = annAssign.getTarget() } + + override AstNode getChild(int index) { + index = 0 and result = this.getAnnotation() + or + index = 1 and result = this.getValue() + or + index = 2 and result = this.getTarget() + } + } + + /** An assignment expression / walrus operator (`x := expr`). */ + additional class NamedExpr extends Expr { + private Py::AssignExpr assignExpr; + + NamedExpr() { this = TPyExpr(assignExpr) } + + Expr getValue() { result.asExpr() = assignExpr.getValue() } + + Expr getTarget() { result.asExpr() = assignExpr.getTarget() } + + override AstNode getChild(int index) { + index = 0 and result = this.getValue() + or + index = 1 and result = this.getTarget() + } + } + + /** + * An `if` statement. + * + * Python's `elif` chains are represented as nested `If` nodes in the + * else branch's `StmtList`. The shared CFG library handles this + * naturally: `getElse()` returns the `BlockStmt` wrapping the else + * branch, and if that block contains a single `If`, the result is + * a chained conditional. + */ + class IfStmt extends Stmt { + private Py::If ifStmt; + + IfStmt() { this = TPyStmt(ifStmt) } + + /** Gets the underlying Python `If` statement. */ + Py::If asIf() { result = ifStmt } + + /** Gets the condition of this `if` statement. */ + Expr getCondition() { result.asExpr() = ifStmt.getTest() } + + /** Gets the `then` (true) branch of this `if` statement. */ + Stmt getThen() { result.asStmtList() = ifStmt.getBody() } + + /** Gets the `else` (false) branch, if any. */ + Stmt getElse() { result.asStmtList() = ifStmt.getOrelse() } + + override AstNode getChild(int index) { + index = 0 and result = this.getCondition() + or + index = 1 and result = this.getThen() + or + index = 2 and result = this.getElse() + } + } + + /** A loop statement. */ + class LoopStmt extends Stmt { + LoopStmt() { + this = TPyStmt(any(Py::While w)) + or + this = TPyStmt(any(Py::For f)) + } + + /** Gets the body of this loop statement. */ + Stmt getBody() { none() } + } + + /** A `while` loop statement. */ + class WhileStmt extends LoopStmt { + private Py::While whileStmt; + + WhileStmt() { this = TPyStmt(whileStmt) } + + /** Gets the boolean condition of this `while` loop. */ + Expr getCondition() { result.asExpr() = whileStmt.getTest() } + + override Stmt getBody() { result.asStmtList() = whileStmt.getBody() } + + /** Gets the `else` branch of this `while` loop, if any. */ + Stmt getElse() { result.asStmtList() = whileStmt.getOrelse() } + + override AstNode getChild(int index) { + index = 0 and result = this.getCondition() + or + index = 1 and result = this.getBody() + or + index = 2 and result = this.getElse() + } + } + + /** + * A `do-while` loop statement. Python has no do-while construct. + */ + class DoStmt extends LoopStmt { + DoStmt() { none() } + + Expr getCondition() { none() } + } + + /** A C-style `for` loop. Python has no C-style for loop. */ + class ForStmt extends LoopStmt { + ForStmt() { none() } + + AstNode getInit(int index) { none() } + + Expr getCondition() { none() } + + AstNode getUpdate(int index) { none() } + } + + /** A for-each loop (`for x in iterable:`). */ + class ForeachStmt extends LoopStmt { + private Py::For forStmt; + + ForeachStmt() { this = TPyStmt(forStmt) } + + /** Gets the loop variable. */ + Expr getVariable() { result.asExpr() = forStmt.getTarget() } + + /** Gets the collection being iterated. */ + Expr getCollection() { result.asExpr() = forStmt.getIter() } + + override Stmt getBody() { result.asStmtList() = forStmt.getBody() } + + /** Gets the `else` branch of this `for` loop, if any. */ + Stmt getElse() { result.asStmtList() = forStmt.getOrelse() } + + override AstNode getChild(int index) { + index = 0 and result = this.getCollection() + or + index = 1 and result = this.getVariable() + or + index = 2 and result = this.getBody() + or + index = 3 and result = this.getElse() + } + } + + /** A `break` statement. */ + class BreakStmt extends Stmt { + BreakStmt() { this = TPyStmt(any(Py::Break b)) } + } + + /** A `continue` statement. */ + class ContinueStmt extends Stmt { + ContinueStmt() { this = TPyStmt(any(Py::Continue c)) } + } + + /** A `goto` statement. Python has no goto. */ + class GotoStmt extends Stmt { + GotoStmt() { none() } + } + + /** A `return` statement. */ + class ReturnStmt extends Stmt { + private Py::Return ret; + + ReturnStmt() { this = TPyStmt(ret) } + + /** Gets the expression being returned, if any. */ + Expr getExpr() { result.asExpr() = ret.getValue() } + + override AstNode getChild(int index) { index = 0 and result = this.getExpr() } + } + + /** A `raise` statement (mapped to `Throw`). */ + class Throw extends Stmt { + private Py::Raise raise; + + Throw() { this = TPyStmt(raise) } + + /** Gets the expression being raised. */ + Expr getExpr() { result.asExpr() = raise.getException() } + + /** Gets the cause of this `raise`, if any. */ + Expr getCause() { result.asExpr() = raise.getCause() } + + override AstNode getChild(int index) { + index = 0 and result = this.getExpr() + or + index = 1 and result = this.getCause() + } + } + + /** + * An `import` statement (`import a, b` or `from m import a, b`). + * + * Each alias contributes two children in evaluation order: first the + * value expression (which performs the import side-effect), then the + * bound `asname` Name (the in-scope binding). This makes both reachable + * from the CFG and allows `Name.defines(v)` for `asname` Names to have + * corresponding CFG nodes — which is essential for SSA to see import + * bindings. + */ + additional class ImportStmt extends Stmt { + private Py::Import imp; + + ImportStmt() { this = TPyStmt(imp) } + + /** Gets the value (module/member expression) of the `n`th alias. */ + Expr getValue(int n) { result.asExpr() = imp.getName(n).getValue() } + + /** Gets the bound `asname` of the `n`th alias. */ + Expr getAsname(int n) { result.asExpr() = imp.getName(n).getAsname() } + + /** Gets the number of aliases in this import statement. */ + int getNumberOfAliases() { result = count(int i | exists(imp.getName(i))) } + + override AstNode getChild(int index) { + exists(int i | + index = 2 * i and result = this.getValue(i) + or + index = 2 * i + 1 and result = this.getAsname(i) + ) + } + } + + /** + * A `from m import *` statement. Evaluates the module expression but + * binds no name (the bindings happen by side-effect at runtime, which + * is not modelled at the CFG level). + */ + additional class ImportStarStmt extends Stmt { + private Py::ImportStar imp; + + ImportStarStmt() { this = TPyStmt(imp) } + + Expr getModule() { result.asExpr() = imp.getModule() } + + override AstNode getChild(int index) { index = 0 and result = this.getModule() } + } + + /** A `with` statement. */ + additional class WithStmt extends Stmt { + private Py::With withStmt; + + WithStmt() { this = TPyStmt(withStmt) } + + Expr getContextExpr() { result.asExpr() = withStmt.getContextExpr() } + + Expr getOptionalVars() { result.asExpr() = withStmt.getOptionalVars() } + + Stmt getBody() { result.asStmtList() = withStmt.getBody() } + + override AstNode getChild(int index) { + index = 0 and result = this.getContextExpr() + or + index = 1 and result = this.getOptionalVars() + or + index = 2 and result = this.getBody() + } + } + + /** An `assert` statement. */ + additional class AssertStmt extends Stmt { + private Py::Assert assertStmt; + + AssertStmt() { this = TPyStmt(assertStmt) } + + Expr getTest() { result.asExpr() = assertStmt.getTest() } + + Expr getMsg() { result.asExpr() = assertStmt.getMsg() } + + override AstNode getChild(int index) { + index = 0 and result = this.getTest() + or + index = 1 and result = this.getMsg() + } + } + + /** A `delete` statement. */ + additional class DeleteStmt extends Stmt { + private Py::Delete del; + + DeleteStmt() { this = TPyStmt(del) } + + Expr getTarget(int n) { result.asExpr() = del.getTarget(n) } + + override AstNode getChild(int index) { result = this.getTarget(index) } + } + + /** + * A PEP 695 `type` statement (`type Alias[T1, T2] = value`). + * + * The type parameters bind at statement-evaluation time. The value + * expression is captured for lazy evaluation but the alias `Name` + * itself binds the resulting `TypeAliasType` object — so the CFG must + * visit at minimum the type-parameter names and the alias name. + */ + additional class TypeAliasStmt extends Stmt { + private Py::TypeAlias ta; + + TypeAliasStmt() { this = TPyStmt(ta) } + + /** Gets the alias `Name` bound by this statement. */ + Expr getName() { result.asExpr() = ta.getName() } + + /** + * Gets the `n`th PEP 695 type-parameter name (a `Name` in store + * context), in declaration order. + */ + Expr getTypeParamName(int n) { result.asExpr() = typeParameterName(ta.getTypeParameter(n)) } + + int getNumberOfTypeParams() { result = count(ta.getATypeParameter()) } + + override AstNode getChild(int index) { + result = this.getTypeParamName(index) + or + index = this.getNumberOfTypeParams() and result = this.getName() + } + } + + /** A `try` statement. */ + class TryStmt extends Stmt { + private Py::Try tryStmt; + + TryStmt() { this = TPyStmt(tryStmt) } + + Stmt getBody() { result.asStmtList() = tryStmt.getBody() } + + /** Gets the `else` branch of this `try` statement, if any. */ + Stmt getElse() { result.asStmtList() = tryStmt.getOrelse() } + + Stmt getFinally() { result.asStmtList() = tryStmt.getFinalbody() } + + CatchClause getCatch(int index) { result.asStmt() = tryStmt.getHandler(index) } + + override AstNode getChild(int index) { + index = 0 and result = this.getBody() + or + result = this.getCatch(index - 1) and index >= 1 + or + index = -1 and result = this.getFinally() + or + index = -2 and result = this.getElse() + } + } + + /** + * Gets the `else` branch of `try` statement `try`, if any. + */ + AstNode getTryElse(TryStmt try) { result = try.getElse() } + + /** + * Gets the `else` branch of `while` loop `loop`, if any. + */ + AstNode getWhileElse(WhileStmt loop) { result = loop.getElse() } + + /** + * Gets the `else` branch of `for` loop `loop`, if any. + */ + AstNode getForeachElse(ForeachStmt loop) { result = loop.getElse() } + + /** An exception handler (`except` or `except*`). */ + class CatchClause extends Stmt { + private Py::ExceptionHandler handler; + + CatchClause() { this = TPyStmt(handler) } + + /** Gets the type expression of this exception handler. */ + Expr getType() { result.asExpr() = handler.getType() } + + /** Gets the variable name of this exception handler, if any. */ + AstNode getVariable() { result.asExpr() = handler.getName() } + + /** Holds: catch clauses do not have a `Condition` in Python's model. */ + Expr getCondition() { none() } + + /** Gets the body of this exception handler. */ + Stmt getBody() { + result.asStmtList() = handler.(Py::ExceptStmt).getBody() + or + result.asStmtList() = handler.(Py::ExceptGroupStmt).getBody() + } + + override AstNode getChild(int index) { + index = 0 and result = this.getType() + or + index = 1 and result = this.getVariable() + or + index = 2 and result = this.getBody() + } + } + + /** A `match` statement, mapped to the shared CFG's `Switch`. */ + class Switch extends Stmt { + private Py::MatchStmt matchStmt; + + Switch() { this = TPyStmt(matchStmt) } + + Expr getExpr() { result.asExpr() = matchStmt.getSubject() } + + Case getCase(int index) { result.asStmt() = matchStmt.getCase(index) } + + Stmt getStmt(int index) { none() } + + override AstNode getChild(int index) { + index = 0 and result = this.getExpr() + or + result = this.getCase(index - 1) and index >= 1 + } + } + + /** A `case` clause in a match statement. */ + class Case extends Stmt { + private Py::Case caseStmt; + + Case() { this = TPyStmt(caseStmt) } + + AstNode getPattern(int index) { index = 0 and result.asPattern() = caseStmt.getPattern() } + + Expr getGuard() { result.asExpr() = caseStmt.getGuard().(Py::Guard).getTest() } + + AstNode getBody() { result.asStmtList() = caseStmt.getBody() } + + /** Holds if this case is a wildcard pattern (`case _:`). */ + predicate isWildcard() { caseStmt.getPattern() instanceof Py::MatchWildcardPattern } + + override AstNode getChild(int index) { + index = 0 and result = this.getPattern(0) + or + index = 1 and result = this.getGuard() + or + index = 2 and result = this.getBody() + } + } + + /** A wildcard case (`case _:`). */ + class DefaultCase extends Case { + DefaultCase() { this.isWildcard() } + } + + /** A conditional expression (`x if cond else y`). */ + class ConditionalExpr extends Expr { + private Py::IfExp ifExp; + + ConditionalExpr() { this = TPyExpr(ifExp) } + + /** Gets the condition of this expression. */ + Expr getCondition() { result.asExpr() = ifExp.getTest() } + + /** Gets the true branch of this expression. */ + Expr getThen() { result.asExpr() = ifExp.getBody() } + + /** Gets the false branch of this expression. */ + Expr getElse() { result.asExpr() = ifExp.getOrelse() } + + override AstNode getChild(int index) { + index = 0 and result = this.getCondition() + or + index = 1 and result = this.getThen() + or + index = 2 and result = this.getElse() + } + } + + /** + * A binary expression for the shared CFG. In Python, this covers all + * `and`/`or` expression operand pairs. + */ + class BinaryExpr extends Expr, TBoolExprPair { + private Py::BoolExpr be; + private int index; + + BinaryExpr() { this = TBoolExprPair(be, index) } + + /** Gets the underlying Python `BoolExpr`. */ + Py::BoolExpr getBoolExpr() { result = be } + + /** Gets the (zero-based) index of this pair within its `BoolExpr`. */ + int getIndex() { result = index } + + override string toString() { result = be.getOperator() } + + override Py::Location getLocation() { result = be.getValue(index).getLocation() } + + override Callable getEnclosingCallable() { result.asScope() = be.getScope() } + + /** Gets the left operand of this binary expression. */ + Expr getLeftOperand() { result.asExpr() = be.getValue(index) } + + /** Gets the right operand of this binary expression. */ + Expr getRightOperand() { + // Last pair: right operand is the final value. + index = count(be.getAValue()) - 2 and result.asExpr() = be.getValue(index + 1) + or + // Non-last pair: right operand is the next synthetic pair. + index < count(be.getAValue()) - 2 and + exists(BinaryExpr next | + next.getBoolExpr() = be and next.getIndex() = index + 1 and result = next + ) + } + + override AstNode getChild(int childIndex) { + childIndex = 0 and result = this.getLeftOperand() + or + childIndex = 1 and result = this.getRightOperand() + } + } + + /** A short-circuiting logical `and` expression. */ + class LogicalAndExpr extends BinaryExpr { + LogicalAndExpr() { this.getBoolExpr().getOp() instanceof Py::And } + } + + /** A short-circuiting logical `or` expression. */ + class LogicalOrExpr extends BinaryExpr { + LogicalOrExpr() { this.getBoolExpr().getOp() instanceof Py::Or } + } + + /** A null-coalescing expression. Python has no null-coalescing operator. */ + class NullCoalescingExpr extends BinaryExpr { + NullCoalescingExpr() { none() } + } + + /** + * A unary expression. Currently only used for the `not` subclass. + */ + class UnaryExpr extends Expr { + UnaryExpr() { exists(Py::UnaryExpr u | this = TPyExpr(u) and u.getOp() instanceof Py::Not) } + + /** Gets the operand of this unary expression. */ + Expr getOperand() { result.asExpr() = this.asExpr().(Py::UnaryExpr).getOperand() } + + override AstNode getChild(int index) { index = 0 and result = this.getOperand() } + } + + /** A logical `not` expression. */ + class LogicalNotExpr extends UnaryExpr { } + + /** + * An assignment expression. + * + * Empty in Python: `x = y` and `x += y` are statements (`AssignStmt` and + * `AugAssignStmt`), not expressions, and the walrus `x := y` is modeled + * separately as `NamedExpr`. The shared library's `Assignment` extends + * `BinaryExpr`, so it cannot share instances with our `Stmt`-based + * assignment forms. + */ + class Assignment extends BinaryExpr { + Assignment() { none() } + } + + /** A simple assignment expression. Empty in Python (see `Assignment`). */ + class AssignExpr extends Assignment { } + + /** A compound assignment expression. Empty in Python (see `Assignment`). */ + class CompoundAssignment extends Assignment { } + + /** + * A short-circuiting logical AND compound assignment expression (`&&=`). + * Python has no such operator. + */ + class AssignLogicalAndExpr extends CompoundAssignment { } + + /** + * A short-circuiting logical OR compound assignment expression (`||=`). + * Python has no such operator. + */ + class AssignLogicalOrExpr extends CompoundAssignment { } + + /** + * A short-circuiting null-coalescing compound assignment expression + * (`??=`). Python has no such operator. + */ + class AssignNullCoalescingExpr extends CompoundAssignment { } + + /** A boolean literal expression (`True` or `False`). */ + class BooleanLiteral extends Expr { + BooleanLiteral() { this = TPyExpr(any(Py::True t)) or this = TPyExpr(any(Py::False f)) } + + /** Gets the boolean value of this literal. */ + boolean getValue() { + this.asExpr() instanceof Py::True and result = true + or + this.asExpr() instanceof Py::False and result = false + } + } + + /** A pattern match expression. Python has no `instanceof`-style pattern match expression. */ + class PatternMatchExpr extends Expr { + PatternMatchExpr() { none() } + + Expr getExpr() { none() } + + AstNode getPattern() { none() } + } + + // ===== Python-specific expression classes (used by `getChild`) ===== + /** A Python binary expression (arithmetic, bitwise, matmul, etc.). */ + additional class ArithBinaryExpr extends Expr { + private Py::BinaryExpr binExpr; + + ArithBinaryExpr() { this = TPyExpr(binExpr) } + + Expr getLeft() { result.asExpr() = binExpr.getLeft() } + + Expr getRight() { result.asExpr() = binExpr.getRight() } + + override AstNode getChild(int index) { + index = 0 and result = this.getLeft() + or + index = 1 and result = this.getRight() + } + } + + /** A call expression (`func(args...)`). */ + additional class CallExpr extends Expr { + private Py::Call call; + + CallExpr() { this = TPyExpr(call) } + + Expr getFunc() { result.asExpr() = call.getFunc() } + + Expr getPositionalArg(int n) { result.asExpr() = call.getPositionalArg(n) } + + int getNumberOfPositionalArgs() { result = count(call.getAPositionalArg()) } + + Expr getKeywordValue(int n) { + result.asExpr() = call.getNamedArg(n).(Py::Keyword).getValue() + or + result.asExpr() = call.getNamedArg(n).(Py::DictUnpacking).getValue() + } + + int getNumberOfNamedArgs() { result = count(call.getANamedArg()) } + + override AstNode getChild(int index) { + index = 0 and result = this.getFunc() + or + result = this.getPositionalArg(index - 1) and index >= 1 + or + result = this.getKeywordValue(index - 1 - this.getNumberOfPositionalArgs()) and + index >= 1 + this.getNumberOfPositionalArgs() + } + } + + /** A subscript expression (`obj[index]`). */ + additional class SubscriptExpr extends Expr { + private Py::Subscript sub; + + SubscriptExpr() { this = TPyExpr(sub) } + + Expr getObject() { result.asExpr() = sub.getObject() } + + Expr getIndex() { result.asExpr() = sub.getIndex() } + + override AstNode getChild(int index) { + index = 0 and result = this.getObject() + or + index = 1 and result = this.getIndex() + } + } + + /** An attribute access (`obj.name`). */ + additional class AttributeExpr extends Expr { + private Py::Attribute attr; + + AttributeExpr() { this = TPyExpr(attr) } + + Expr getObject() { result.asExpr() = attr.getObject() } + + override AstNode getChild(int index) { index = 0 and result = this.getObject() } + } + + /** + * An `import x.y` module expression. Modelled as a leaf — the dotted + * name is just a string. + */ + additional class ImportExpression extends Expr { + ImportExpression() { this.asExpr() instanceof Py::ImportExpr } + } + + /** + * A `from m import x` member access. The module sub-expression is a + * child so that the CFG visits both the module load and this + * attribute selection. + */ + additional class ImportMemberExpr extends Expr { + private Py::ImportMember im; + + ImportMemberExpr() { this = TPyExpr(im) } + + /** Gets the module expression `m` in `from m import x`. */ + Expr getModule() { result.asExpr() = im.getModule() } + + override AstNode getChild(int index) { index = 0 and result = this.getModule() } + } + + /** A tuple literal. */ + additional class TupleExpr extends Expr { + private Py::Tuple tuple; + + TupleExpr() { this = TPyExpr(tuple) } + + Expr getElt(int n) { result.asExpr() = tuple.getElt(n) } + + override AstNode getChild(int index) { result = this.getElt(index) } + } + + /** A list literal. */ + additional class ListExpr extends Expr { + private Py::List list; + + ListExpr() { this = TPyExpr(list) } + + Expr getElt(int n) { result.asExpr() = list.getElt(n) } + + override AstNode getChild(int index) { result = this.getElt(index) } + } + + /** A set literal. */ + additional class SetExpr extends Expr { + private Py::Set set; + + SetExpr() { this = TPyExpr(set) } + + Expr getElt(int n) { result.asExpr() = set.getElt(n) } + + override AstNode getChild(int index) { result = this.getElt(index) } + } + + /** A dict literal. */ + additional class DictExpr extends Expr { + private Py::Dict dict; + + DictExpr() { this = TPyExpr(dict) } + + /** + * Gets the key of the `n`th item (at child index `2*n`); the value is + * at child index `2*n + 1`. + */ + Expr getKey(int n) { result.asExpr() = dict.getItem(n).(Py::KeyValuePair).getKey() } + + Expr getValue(int n) { result.asExpr() = dict.getItem(n).(Py::KeyValuePair).getValue() } + + int getNumberOfItems() { result = count(dict.getAnItem()) } + + override AstNode getChild(int index) { + exists(int item | + index = 2 * item and result = this.getKey(item) + or + index = 2 * item + 1 and result = this.getValue(item) + ) + } + } + + /** A unary expression other than `not` (e.g., `-x`, `+x`, `~x`). */ + additional class ArithUnaryExpr extends Expr { + private Py::UnaryExpr unaryExpr; + + ArithUnaryExpr() { this = TPyExpr(unaryExpr) and not unaryExpr.getOp() instanceof Py::Not } + + Expr getOperand() { result.asExpr() = unaryExpr.getOperand() } + + override AstNode getChild(int index) { index = 0 and result = this.getOperand() } + } + + /** + * A comprehension or generator expression. The iterable is evaluated in + * the enclosing scope; the body runs in a nested synthetic function + * scope handled by its own CFG. + */ + additional class Comprehension extends Expr { + private Py::Expr iterable; + + Comprehension() { + exists(Py::Expr c | this = TPyExpr(c) | + iterable = c.(Py::ListComp).getIterable() + or + iterable = c.(Py::SetComp).getIterable() + or + iterable = c.(Py::DictComp).getIterable() + or + iterable = c.(Py::GeneratorExp).getIterable() + ) + } + + Expr getIterable() { result.asExpr() = iterable } + + override AstNode getChild(int index) { index = 0 and result = this.getIterable() } + } + + /** A comparison expression (`a < b`, `a < b < c`, etc.). */ + additional class CompareExpr extends Expr { + private Py::Compare cmp; + + CompareExpr() { this = TPyExpr(cmp) } + + Expr getLeft() { result.asExpr() = cmp.getLeft() } + + Expr getComparator(int n) { result.asExpr() = cmp.getComparator(n) } + + override AstNode getChild(int index) { + index = 0 and result = this.getLeft() + or + result = this.getComparator(index - 1) and index >= 1 + } + } + + /** A slice expression (`start:stop:step`). */ + additional class SliceExpr extends Expr { + private Py::Slice slice; + + SliceExpr() { this = TPyExpr(slice) } + + Expr getStart() { result.asExpr() = slice.getStart() } + + Expr getStop() { result.asExpr() = slice.getStop() } + + Expr getStep() { result.asExpr() = slice.getStep() } + + override AstNode getChild(int index) { + index = 0 and result = this.getStart() + or + index = 1 and result = this.getStop() + or + index = 2 and result = this.getStep() + } + } + + /** A starred expression (`*x`). */ + additional class StarredExpr extends Expr { + private Py::Starred starred; + + StarredExpr() { this = TPyExpr(starred) } + + Expr getValue() { result.asExpr() = starred.getValue() } + + override AstNode getChild(int index) { index = 0 and result = this.getValue() } + } + + /** A formatted string literal (`f"...{expr}..."`). */ + additional class FstringExpr extends Expr { + private Py::Fstring fstring; + + FstringExpr() { this = TPyExpr(fstring) } + + Expr getValue(int n) { result.asExpr() = fstring.getValue(n) } + + override AstNode getChild(int index) { result = this.getValue(index) } + } + + /** A formatted value inside an f-string (`{expr}` or `{expr:spec}`). */ + additional class FormattedValueExpr extends Expr { + private Py::FormattedValue fv; + + FormattedValueExpr() { this = TPyExpr(fv) } + + Expr getValue() { result.asExpr() = fv.getValue() } + + Expr getFormatSpec() { result.asExpr() = fv.getFormatSpec() } + + override AstNode getChild(int index) { + index = 0 and result = this.getValue() + or + index = 1 and result = this.getFormatSpec() + } + } + + /** A `yield` expression. */ + additional class YieldExpr extends Expr { + private Py::Yield yield; + + YieldExpr() { this = TPyExpr(yield) } + + Expr getValue() { result.asExpr() = yield.getValue() } + + override AstNode getChild(int index) { index = 0 and result = this.getValue() } + } + + /** A `yield from` expression. */ + additional class YieldFromExpr extends Expr { + private Py::YieldFrom yieldFrom; + + YieldFromExpr() { this = TPyExpr(yieldFrom) } + + Expr getValue() { result.asExpr() = yieldFrom.getValue() } + + override AstNode getChild(int index) { index = 0 and result = this.getValue() } + } + + /** An `await` expression. */ + additional class AwaitExpr extends Expr { + private Py::Await await; + + AwaitExpr() { this = TPyExpr(await) } + + Expr getValue() { result.asExpr() = await.getValue() } + + override AstNode getChild(int index) { index = 0 and result = this.getValue() } + } + + /** + * A class definition expression (visits bases, but NOT PEP 695 type + * parameters — those bind in an annotation scope that nests the class + * body, so they belong to the inner scope's CFG, not the enclosing + * scope's; the legacy CFG also omitted them). + */ + additional class ClassDefExpr extends Expr { + private Py::ClassExpr classExpr; + + ClassDefExpr() { this = TPyExpr(classExpr) } + + Expr getBase(int n) { result.asExpr() = classExpr.getBase(n) } + + override AstNode getChild(int index) { result = this.getBase(index) } + } + + /** + * A function definition expression (visits positional and keyword + * defaults, but NOT PEP 695 type parameters — those bind in an + * annotation scope that nests the function body, so they belong to + * the inner scope's CFG, not the enclosing scope's; the legacy CFG + * also omitted them). + */ + additional class FunctionDefExpr extends Expr { + private Py::FunctionExpr funcExpr; + + FunctionDefExpr() { this = TPyExpr(funcExpr) } + + /** + * Gets the `n`th default for a positional argument, in evaluation + * order. Note that `Args.getDefault(int)` is indexed by argument + * position (with gaps for arguments without defaults), so we must + * renumber here to obtain contiguous indices. + */ + Expr getDefault(int n) { + result.asExpr() = + rank[n + 1](Py::Expr d, int i | d = funcExpr.getArgs().getDefault(i) | d order by i) + } + + /** Gets the `n`th default for a keyword-only argument, in evaluation order. */ + Expr getKwDefault(int n) { + result.asExpr() = + rank[n + 1](Py::Expr d, int i | d = funcExpr.getArgs().getKwDefault(i) | d order by i) + } + + int getNumberOfDefaults() { result = count(funcExpr.getArgs().getADefault()) } + + override AstNode getChild(int index) { + result = this.getDefault(index) + or + result = this.getKwDefault(index - this.getNumberOfDefaults()) + } + } + + /** A lambda expression (has default args evaluated at definition time). */ + additional class LambdaExpr extends Expr { + private Py::Lambda lambda; + + LambdaExpr() { this = TPyExpr(lambda) } + + /** Gets the `n`th default for a positional argument, in evaluation order. */ + Expr getDefault(int n) { + result.asExpr() = + rank[n + 1](Py::Expr d, int i | d = lambda.getArgs().getDefault(i) | d order by i) + } + + /** Gets the `n`th default for a keyword-only argument, in evaluation order. */ + Expr getKwDefault(int n) { + result.asExpr() = + rank[n + 1](Py::Expr d, int i | d = lambda.getArgs().getKwDefault(i) | d order by i) + } + + int getNumberOfDefaults() { result = count(lambda.getArgs().getADefault()) } + + override AstNode getChild(int index) { + result = this.getDefault(index) + or + result = this.getKwDefault(index - this.getNumberOfDefaults()) + } + } + + /** Gets the child of `n` at the specified (zero-based) index. */ + AstNode getChild(AstNode n, int index) { result = n.getChild(index) } +} + +private module Cfg0 = Make0; + +private import Cfg0 + +private module Cfg1 = Make1; + +private import Cfg1 + +private module Cfg2 = Make2; + +private import Cfg2 + +private module Input implements InputSig1, InputSig2 { + predicate cfgCachedStageRef() { CfgCachedStage::ref() } + + private newtype TLabel = TNone() + + class Label extends TLabel { + string toString() { result = "label" } + } + + class CallableContext = Void; + + predicate inConditionalContext(Ast::AstNode n, ConditionKind kind) { + kind.isBoolean() and + n = any(Ast::AssertStmt a).getTest() + } + + private string assertThrowTag() { result = "[assert-throw]" } + + predicate additionalNode(Ast::AstNode n, string tag, NormalSuccessor t) { + n instanceof Ast::AssertStmt and tag = assertThrowTag() and t instanceof DirectSuccessor + } + + predicate beginAbruptCompletion( + Ast::AstNode ast, PreControlFlowNode n, AbruptCompletion c, boolean always + ) { + ast instanceof Ast::AssertStmt and + n.isAdditional(ast, assertThrowTag()) and + c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and + always = true + } + + predicate endAbruptCompletion(Ast::AstNode ast, PreControlFlowNode n, AbruptCompletion c) { + none() + } + + predicate step(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Ast::AssertStmt assertStmt | + n1.isBefore(assertStmt) and + n2.isBefore(assertStmt.getTest()) + or + n1.isAfterTrue(assertStmt.getTest()) and + n2.isAfter(assertStmt) + or + n1.isAfterFalse(assertStmt.getTest()) and + ( + n2.isBefore(assertStmt.getMsg()) + or + not exists(assertStmt.getMsg()) and + n2.isAdditional(assertStmt, assertThrowTag()) + ) + or + n1.isAfter(assertStmt.getMsg()) and + n2.isAdditional(assertStmt, assertThrowTag()) + ) + } +} + +import CfgCachedStage +import Public + +/** + * Maps a CFG AST wrapper node to the corresponding Python AST node, if any. + * Entry, exit, and synthetic nodes have no corresponding Python AST node. + */ +Py::AstNode astNodeToPyNode(Ast::AstNode n) { + result = n.asExpr() + or + result = n.asStmt() + or + result = n.asScope() + or + result = n.asPattern() +} diff --git a/python/ql/lib/semmle/python/controlflow/internal/Cfg.qll b/python/ql/lib/semmle/python/controlflow/internal/Cfg.qll new file mode 100644 index 000000000000..4d5b7d4d8053 --- /dev/null +++ b/python/ql/lib/semmle/python/controlflow/internal/Cfg.qll @@ -0,0 +1,1163 @@ +/** + * Provides a Python control flow graph facade backed by the shared + * `codeql.controlflow.ControlFlowGraph` library (via `AstNodeImpl.qll`). + * + * This module re-exposes the same API surface as `semmle/python/Flow.qll` + * (the legacy CFG), but is implemented on the new shared CFG. It is + * intended as a drop-in replacement for use by the Python dataflow library + * and other downstream code. + * + * Layering follows the Java pattern (`java/ql/lib/semmle/code/java/Expr.qll` + * and `SsaImpl.qll`): variable identity and similar AST-level semantics + * live on the Python AST classes (`Name.defines(v)`, `Name.uses(v)`, ...); + * the CFG layer is purely positional, with `toAst` / `getNode` bridging + * back to the AST. The shared SSA library can then be parameterized on + * (`BasicBlock`, `int`) directly, with no CFG-level variable predicates. + */ +overlay[local?] +module; + +private import python as Py +private import semmle.python.controlflow.internal.AstNodeImpl as CfgImpl +private import codeql.controlflow.SuccessorType +private import codeql.controlflow.BasicBlock as BB + +/** + * A nested sub-module that explicitly implements `BB::CfgSig`, so this + * `Cfg` facade can be passed to parameterised shared modules such as + * `codeql.dataflow.VariableCapture::Flow`. The sub-module + * exposes the *raw* shared-CFG types from `AstNodeImpl.qll` (where the + * signature is satisfied natively), not the facade's wrapped types. + */ +module CfgSigImpl implements BB::CfgSig { + class ControlFlowNode = CfgImpl::ControlFlowNode; + + class BasicBlock = CfgImpl::BasicBlock; + + class EntryBasicBlock = CfgImpl::Cfg::EntryBasicBlock; + + predicate dominatingEdge = CfgImpl::Cfg::dominatingEdge/2; +} + +/** + * Gets the Python AST node corresponding to CFG node `n`, if any. + * + * Entry/exit/synthetic CFG nodes have no Python AST node, so this is + * partial. + */ +private Py::AstNode toAst(CfgImpl::ControlFlowNode n) { + result = CfgImpl::astNodeToPyNode(n.getAstNode()) +} + +/** + * Holds if `n` is a CFG node representing the canonical position for an + * AST node from the dataflow library's perspective. + * + * For most expressions this is the "after"-evaluation point (post-order + * representative). For statements it is the post-order node when one + * exists. We additionally include the synthetic entry/exit nodes for the + * benefit of API consumers that ask "is this the entry node of a scope?". + * + * In conditional contexts the after-position of a boolean expression + * splits into separate `isAfterTrue` and `isAfterFalse` nodes; both are + * canonical, so a single AST expression may correspond to more than one + * `ControlFlowNode`. + */ +private predicate isCanonical(CfgImpl::ControlFlowNode n) { + n.isAfter(_) + or + n instanceof CfgImpl::ControlFlow::EntryNode + or + n instanceof CfgImpl::ControlFlow::ExitNode + or + // Annotated exit nodes (normal + abnormal) — needed so that dataflow + // consumers can ask "is this the normal-exit of a scope?" and also + // so that scope-exit synthetic uses in SsaImpl can attach here. + n instanceof CfgImpl::ControlFlow::AnnotatedExitNode +} + +/** + * Holds if `n` is genuinely the `TAfterValueNode` variant for a boolean-true + * outcome of its AST node. + * + * The shared CFG's `isAfterValue` predicate has a kind-mismatch fallback + * (see `ControlFlowGraph.qll`'s `isAfterValue` lines 870-892): when asking + * `isAfterValue(_, BooleanSuccessor true)` on, say, an emptiness-empty + * variant, it falsely returns `true` because the kinds differ and Python + * does not provide `successorValueImplies`. The same fallback also makes + * `TAfterNode` (the unsplit case) satisfy `isAfterValue(_, t)` for *every* + * `t`. + * + * The combination `isAfterTrue ∧ ¬isAfterFalse` excludes both: a genuine + * boolean-true variant satisfies `isAfterTrue` directly (newtype branch 3) + * but not `isAfterFalse` (the dual variant, same kind, no fallback). A + * non-boolean variant satisfies both via the cross-kind fallback. A + * `TAfterNode` satisfies both via branch 1. So `isAfterTrue ∧ ¬isAfterFalse` + * picks exactly the genuine boolean-true variant. + */ +private predicate isGenuineAfterTrue(ControlFlowNode n) { + n.isAfterTrue(_) and not n.isAfterFalse(_) +} + +/** + * Holds if `n` is genuinely the `TAfterValueNode` variant for the "empty" + * outcome of its AST node (e.g. `for x in xs: ...` when `xs` is empty). + * + * See `isGenuineAfterTrue` for why we cannot just use a single + * `isAfterValue` check. + */ +private predicate isGenuineAfterEmpty(ControlFlowNode n) { + exists(EmptinessSuccessor empty | + empty.getValue() = true and n.isAfterValue(n.getAstNode(), empty) + ) and + not exists(EmptinessSuccessor nonEmpty | + nonEmpty.getValue() = false and n.isAfterValue(n.getAstNode(), nonEmpty) + ) +} + +/** + * Holds if `n` is genuinely the `TAfterValueNode` variant for the "matched" + * outcome of its AST node (e.g. a `match` case-pattern that matched). + * + * See `isGenuineAfterTrue` for why we cannot just use a single + * `isAfterValue` check. + */ +private predicate isGenuineAfterMatched(ControlFlowNode n) { + exists(MatchingSuccessor matched | + matched.getValue() = true and n.isAfterValue(n.getAstNode(), matched) + ) and + not exists(MatchingSuccessor unmatched | + unmatched.getValue() = false and n.isAfterValue(n.getAstNode(), unmatched) + ) +} + +/** + * Holds if `n` is the canonical representative of its corresponding AST node + * for dataflow purposes. + * + * The shared CFG associates a single AST node with multiple `ControlFlowNode`s + * when the AST appears in a conditional context (boolean conditions split into + * `afterTrue`/`afterFalse`; for-loop iters split into `[empty]`/`[non-empty]`; + * `match`-case patterns split into `[matched]`/`[unmatched]`). These splits + * matter for control-flow analysis, but for dataflow purposes — where we + * ask "what is the value of this expression?" — a single representative + * suffices and is required to avoid double-counting calls, arguments, store + * steps, etc. + * + * The pick is structural: when an AST has a single `ControlFlowNode` (the + * normal `TAfterNode` or `TBeforeNode`-leaf case), that node is canonical. + * When an AST has a conditional split, the "positive" outcome variant + * (true / empty / matched) is canonical. The three split kinds are mutually + * exclusive per AST, so exactly one variant is selected. + */ +predicate isCanonicalAstNodeRepresentative(ControlFlowNode n) { + // Non-split AST: the unique variant is canonical. + not exists(ControlFlowNode other | other.getNode() = n.getNode() and other != n) + or + // Split AST: pick the "positive" outcome of the split. + isGenuineAfterTrue(n) + or + isGenuineAfterEmpty(n) + or + isGenuineAfterMatched(n) +} + +/** + * A control flow node. Control flow nodes have a many-to-one relation + * with syntactic nodes, although most syntactic nodes have only one + * corresponding control flow node. + * + * Edges between control flow nodes include exceptional as well as normal + * control flow. + */ +class ControlFlowNode extends CfgImpl::ControlFlowNode { + ControlFlowNode() { isCanonical(this) } + + /** Gets the syntactic element corresponding to this flow node, if any. */ + Py::AstNode getNode() { result = toAst(this) } + + /** Gets a predecessor of this flow node. */ + ControlFlowNode getAPredecessor() { this = result.getASuccessor() } + + /** Gets a successor of this flow node. */ + pragma[inline] + ControlFlowNode getASuccessor() { result = nextCanonical(this) } + + /** Gets a successor for this node if the relevant condition is True. */ + ControlFlowNode getATrueSuccessor() { + super.isAfterTrue(_) and + exists(CfgImpl::ControlFlowNode other | other.isAfterFalse(super.getAstNode())) and + result = nextCanonical(this) + } + + /** Gets a successor for this node if the relevant condition is False. */ + ControlFlowNode getAFalseSuccessor() { + super.isAfterFalse(_) and + exists(CfgImpl::ControlFlowNode other | other.isAfterTrue(super.getAstNode())) and + result = nextCanonical(this) + } + + /** Gets a successor for this node if an exception is raised. */ + ControlFlowNode getAnExceptionalSuccessor() { + exists(CfgImpl::ControlFlowNode mid | + mid = super.getAnExceptionSuccessor() and + result = nextCanonicalFrom(mid) + ) + } + + /** Gets a successor for this node if no exception is raised. */ + ControlFlowNode getANormalSuccessor() { + result = this.getASuccessor() and + not result = this.getAnExceptionalSuccessor() + } + + /** Gets the basic block containing this flow node. */ + BasicBlock getBasicBlock() { result = super.getBasicBlock() } + + /** Gets the scope containing this flow node. */ + Py::Scope getScope() { result = super.getEnclosingCallable().asScope() } + + /** Gets the enclosing module. */ + Py::Module getEnclosingModule() { result = this.getScope().getEnclosingModule() } + + /** Gets the immediate dominator of this flow node. */ + ControlFlowNode getImmediateDominator() { + // Defined positionally via the basic-block dominance tree. + exists(BasicBlock bb, int i | bb.getNode(i) = this | + // Predecessor within the same basic block. + i > 0 and result = bb.getNode(i - 1) + or + // First node of `bb`: dominator is the last node of the immediate dominator block. + i = 0 and result = bb.getImmediateDominator().getLastNode() + ) + } + + /** Holds if this strictly dominates `other`. */ + pragma[inline] + predicate strictlyDominates(ControlFlowNode other) { super.strictlyDominates(other) } + + /** Holds if this dominates `other` (reflexively). */ + pragma[inline] + predicate dominates(ControlFlowNode other) { super.dominates(other) } + + /** Holds if this is the first node in its enclosing scope. */ + predicate isEntryNode() { this instanceof CfgImpl::ControlFlow::EntryNode } + + /** Holds if this is the first node of a module. */ + predicate isModuleEntry() { + this.isEntryNode() and super.getAstNode().asScope() instanceof Py::Module + } + + /** Holds if this node may exit its scope by raising an exception. */ + predicate isExceptionalExit(Py::Scope s) { + this instanceof CfgImpl::ControlFlow::ExceptionalExitNode and + super.getEnclosingCallable().asScope() = s + } + + /** Holds if this node is a normal (non-exceptional) exit. */ + predicate isNormalExit() { this instanceof CfgImpl::ControlFlow::NormalExitNode } + + // ===== AST-shape predicates (bridges to the wrapped Python AST) ===== + /** + * Holds if this flow node is a load (including those in augmented + * assignments). + * + * Note: an augmented-assignment target (`x[i]` in `x[i] += 1`) is + * both a load and a store — `isLoad` and `isStore` both hold on the + * canonical CFG node. This mirrors Java's `VarAccess.isVarRead`, + * which holds on the destination of compound and unary assignments + * even though the destination is also a write. + */ + predicate isLoad() { exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 3, e)) } + + /** Holds if this flow node is a store (including those in augmented assignments). */ + predicate isStore() { + exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 5, e) or augstore(_, this)) + } + + /** Holds if this flow node is a delete. */ + predicate isDelete() { exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 2, e)) } + + /** Holds if this flow node is a parameter. */ + predicate isParameter() { exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 4, e)) } + + /** Holds if this flow node is a store in an augmented assignment. */ + predicate isAugStore() { augstore(_, this) } + + /** Holds if this flow node is a load in an augmented assignment. */ + predicate isAugLoad() { augstore(this, _) } + + /** Holds if this flow node corresponds to a literal. */ + predicate isLiteral() { + toAst(this) instanceof Py::Bytes or + toAst(this) instanceof Py::Dict or + toAst(this) instanceof Py::DictComp or + toAst(this) instanceof Py::Set or + toAst(this) instanceof Py::SetComp or + toAst(this) instanceof Py::Ellipsis or + toAst(this) instanceof Py::GeneratorExp or + toAst(this) instanceof Py::Lambda or + toAst(this) instanceof Py::ListComp or + toAst(this) instanceof Py::List or + toAst(this) instanceof Py::Num or + toAst(this) instanceof Py::Tuple or + toAst(this) instanceof Py::Unicode or + toAst(this) instanceof Py::NameConstant + } + + /** Holds if this flow node corresponds to an attribute expression. */ + predicate isAttribute() { toAst(this) instanceof Py::Attribute } + + /** Holds if this flow node corresponds to a subscript expression. */ + predicate isSubscript() { toAst(this) instanceof Py::Subscript } + + /** Holds if this flow node corresponds to an import member. */ + predicate isImportMember() { toAst(this) instanceof Py::ImportMember } + + /** Holds if this flow node corresponds to a call. */ + predicate isCall() { toAst(this) instanceof Py::Call } + + /** Holds if this flow node corresponds to an import. */ + predicate isImport() { toAst(this) instanceof Py::ImportExpr } + + /** Holds if this flow node corresponds to a conditional expression. */ + predicate isIfExp() { toAst(this) instanceof Py::IfExp } + + /** Holds if this flow node corresponds to a function definition expression. */ + predicate isFunction() { toAst(this) instanceof Py::FunctionExpr } + + /** Holds if this flow node corresponds to a class definition expression. */ + predicate isClass() { toAst(this) instanceof Py::ClassExpr } + + /** + * Holds if this flow node is a branch (i.e. has both a true and a + * false successor). + */ + predicate isBranch() { exists(this.getATrueSuccessor()) or exists(this.getAFalseSuccessor()) } + + /** + * Gets a CFG child of this node, defined as a CFG node whose AST node + * is a child of this CFG node's AST node, restricted to nodes that + * dominate this one (so the child has been evaluated by the time we + * reach this node). + * + * Mirrors `Flow.qll`'s `getAChild`. UnaryExprNode is excluded because + * its operand is its CFG predecessor (handled separately). + */ + pragma[nomagic] + ControlFlowNode getAChild() { + toAst(this).(Py::Expr).getAChildNode() = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) and + not this instanceof UnaryExprNode + } + + /** Holds if this flow node strictly reaches `other`. */ + predicate strictlyReaches(ControlFlowNode other) { this.getASuccessor+() = other } + + /** Internal: raw successor predicate that does NOT skip non-canonical nodes. */ + CfgImpl::ControlFlowNode getASuccessorRaw() { result = super.getASuccessor() } +} + +/** + * Holds if `load` is the load half of an augmented-assignment target, + * and `store` is the corresponding store half. + * + * In the legacy CFG (`Flow.qll`) the same Python `Name` had two + * distinct CFG nodes — a load node (context 3) earlier in the BB, and + * a store node (context 5) later. The legacy `augstore` related the + * pair via dominance. + * + * In the new (shared) CFG, the canonical node for an AST expression is + * unique, so `load` and `store` collapse onto the same CFG node. The + * predicate is therefore reflexive on the augmented-assignment + * target's canonical node. + */ +private predicate augstore(ControlFlowNode load, ControlFlowNode store) { + exists(Py::AugAssign aa | aa.getTarget() = toAst(load)) and + load = store +} + +/** + * Gets the nearest canonical CFG node reachable from `n` via one or more + * raw CFG edges (skipping non-canonical intermediaries). + */ +private CfgImpl::ControlFlowNode nextCanonicalFrom(CfgImpl::ControlFlowNode n) { + result = n.getASuccessor() and isCanonical(result) + or + exists(CfgImpl::ControlFlowNode mid | + mid = n.getASuccessor() and + not isCanonical(mid) and + result = nextCanonicalFrom(mid) + ) +} + +/** Gets the nearest canonical CFG successor of canonical node `n`. */ +private ControlFlowNode nextCanonical(ControlFlowNode n) { result = nextCanonicalFrom(n) } + +/** + * A basic block — a maximal-length sequence of control flow nodes such + * that no node except the first has a predecessor outside the sequence, + * and no node except the last has a successor outside the sequence. + */ +class BasicBlock extends CfgImpl::BasicBlock { + /** Gets the `n`th node in this basic block, restricted to canonical nodes. */ + ControlFlowNode getNode(int n) { + result = rank[n + 1](ControlFlowNode node, int i | super.getNode(i) = node | node order by i) + } + + /** Gets a node in this basic block. */ + ControlFlowNode getANode() { result = this.getNode(_) } + + /** Gets the first canonical node in this basic block. */ + ControlFlowNode firstNode() { result = this.getNode(0) } + + /** Gets the last canonical node in this basic block. */ + ControlFlowNode getLastNode() { result = this.getNode(max(int n | exists(this.getNode(n)))) } + + /** Holds if this basic block contains `node`. */ + predicate contains(ControlFlowNode node) { node = this.getANode() } + + // Inherited from the shared library's `BasicBlock`: + // getASuccessor(), getASuccessor(SuccessorType), getAPredecessor(), + // getNode(int) (raw, includes non-canonical), getANode() (raw), + // strictlyDominates(), dominates(), getImmediateDominator(), + // length(), inLoop(). + // We expose canonical-only positional access via `getNode(int)` below + // (shadows the shared-lib version) and additional Python-style helpers. + /** Gets a true successor to this basic block. */ + BasicBlock getATrueSuccessor() { + result = super.getASuccessor(any(BooleanSuccessor t | t.getValue() = true)) + } + + /** Gets a false successor to this basic block. */ + BasicBlock getAFalseSuccessor() { + result = super.getASuccessor(any(BooleanSuccessor t | t.getValue() = false)) + } + + /** Gets an unconditional successor to this basic block. */ + BasicBlock getAnUnconditionalSuccessor() { + result = super.getASuccessor() and + not result = this.getATrueSuccessor() and + not result = this.getAFalseSuccessor() + } + + /** Gets an exceptional successor to this basic block. */ + BasicBlock getAnExceptionalSuccessor() { result = super.getASuccessor(any(ExceptionSuccessor t)) } + + /** + * Holds if this basic block is in the dominance frontier of `df`. + * + * Note: implemented locally rather than via the shared lib, which + * doesn't currently expose a `dominanceFrontier` predicate at this + * level. + */ + predicate inDominanceFrontier(BasicBlock df) { + this = df.getAPredecessor() and not this = df.getImmediateDominator() + or + exists(BasicBlock prev | prev.inDominanceFrontier(df) | + this = prev.getImmediateDominator() and + not this = df.getImmediateDominator() + ) + } + + /** Holds if this basic block strictly reaches `other`. */ + predicate strictlyReaches(BasicBlock other) { super.getASuccessor+() = other } + + /** Holds if this basic block reaches `other` (reflexively). */ + predicate reaches(BasicBlock other) { this = other or this.strictlyReaches(other) } + + /** Holds if flow from this basic block reaches a normal exit from its scope. */ + predicate reachesExit() { + this.getANode() instanceof CfgImpl::ControlFlow::NormalExitNode + or + exists(BasicBlock succ | succ = super.getASuccessor() and succ.reachesExit()) + } + + /** Gets the scope of this basic block. */ + Py::Scope getScope() { exists(ControlFlowNode n | n = this.getANode() | result = n.getScope()) } + + /** Holds if flow from this BasicBlock always reaches `succ`. */ + predicate alwaysReaches(BasicBlock succ) { + succ = this + or + strictcount(BasicBlock s | s = super.getASuccessor()) = 1 and + succ = super.getASuccessor() + or + forex(BasicBlock immsucc | immsucc = super.getASuccessor() | immsucc.alwaysReaches(succ)) + } + + /** + * Holds if this basic block ends in a node that branches on a boolean + * outcome, and `other` is dominated by the corresponding successor + * for `branch` while not being reachable from the other branch + * without going through this BB. + * + * In other words: any execution that reaches `other` must have just + * evaluated the last node of this BB and taken the `branch` outcome. + * This mirrors the legacy `ConditionBlock.controls(BB, branch)`. + */ + predicate controls(BasicBlock other, boolean branch) { + exists(BasicBlock succ | + branch = true and succ = this.getATrueSuccessor() + or + branch = false and succ = this.getAFalseSuccessor() + | + succ.dominates(other) and + // The other branch must not also reach `other` — otherwise + // `other` is not actually controlled by `branch`. + not exists(BasicBlock otherSucc | + branch = true and otherSucc = this.getAFalseSuccessor() + or + branch = false and otherSucc = this.getATrueSuccessor() + | + otherSucc.reaches(other) + ) + ) + } +} + +// =========================================================================== +// Re-exports for SSA / dominance consumers +// +// The shared `BB::CfgSig` requires `EntryBasicBlock` and `dominatingEdge` in +// addition to the BasicBlock class we already expose. They are provided by +// the shared CFG library on the `BB::Make` instantiation produced by +// `AstNodeImpl.qll`. +// =========================================================================== +/** An entry basic block, that is, a basic block whose first node is an entry node. */ +class EntryBasicBlock = CfgImpl::Cfg::EntryBasicBlock; + +/** + * Holds if `bb1` has `bb2` as a direct successor and the edge between `bb1` + * and `bb2` is a dominating edge. + */ +predicate dominatingEdge = CfgImpl::Cfg::dominatingEdge/2; + +// =========================================================================== +// AST-shape subclasses of ControlFlowNode +// +// Each class is a thin wrapper around the canonical CFG node for a given +// kind of Python AST node. Methods that take/return CFG nodes look up +// related CFG nodes by AST identity (via `getNode()`), and the dominance +// constraint from the old CFG (`result.getBasicBlock().dominates(this.getBasicBlock())`) +// is preserved. +// =========================================================================== +/** Gets the canonical `ControlFlowNode` for AST expression `e`. */ +ControlFlowNode astExprToCfg(Py::Expr e) { result.getNode() = e } + +/** A control flow node corresponding to a `Name` or `PlaceHolder` expression. */ +class NameNode extends ControlFlowNode { + NameNode() { + toAst(this) instanceof Py::Name + or + toAst(this) instanceof Py::PlaceHolder + } + + /** + * Holds if this flow node defines the variable `v`. + * + * This includes augmented-assignment targets — `n += 1` is both a + * read and a write of `n`, so `defines(n)` and `uses(n)` both hold + * on the same canonical CFG node. Mirrors Java's `VariableUpdate` + * semantics where compound assignments register both a write + * (`VarWrite`) and a read (`VarRead`) on the destination. + */ + predicate defines(Py::Variable v) { exists(Py::Name n | n = toAst(this) and n.defines(v)) } + + /** Holds if this flow node deletes the variable `v`. */ + predicate deletes(Py::Variable v) { exists(Py::Name n | n = toAst(this) and n.deletes(v)) } + + /** Holds if this flow node uses the variable `v`. */ + predicate uses(Py::Variable v) { + this.isLoad() and + exists(Py::Name u | u = toAst(this) and u.uses(v)) + or + exists(Py::PlaceHolder u | + u = toAst(this) and u.getVariable() = v and u.getCtx() instanceof Py::Load + ) + } + + /** Gets the identifier of this name node. */ + string getId() { + result = toAst(this).(Py::Name).getId() + or + result = toAst(this).(Py::PlaceHolder).getId() + } + + /** Holds if this is a use of a local variable. */ + predicate isLocal() { exists(Py::Variable v | this.uses(v) and v instanceof Py::LocalVariable) } + + /** Holds if this is a use of a non-local variable. */ + predicate isNonLocal() { + exists(Py::Variable v | this.uses(v) and v.getScope() != this.getScope()) + } + + /** Holds if this is a use of a global (including builtin) variable. */ + predicate isGlobal() { exists(Py::Variable v | this.uses(v) and v instanceof Py::GlobalVariable) } + + /** + * Holds if this is a use of `self` — the first parameter of an + * enclosing method. + * + * AST-level approximation: matches when the Name uses a `Variable` + * that is the first parameter of an enclosing `Function` defined + * inside a `Class`. + */ + predicate isSelf() { + exists(Py::Variable v, Py::Function f, Py::Class c | + this.uses(v) and + f = c.getAMethod() and + v.getScope() = f and + v = f.getArg(0).(Py::Name).getVariable() + ) + } +} + +/** A control flow node corresponding to a named constant (`None`, `True`, `False`). */ +class NameConstantNode extends NameNode { + NameConstantNode() { toAst(this) instanceof Py::NameConstant } +} + +/** A control flow node corresponding to a call. */ +class CallNode extends ControlFlowNode { + CallNode() { toAst(this) instanceof Py::Call } + + override Py::Call getNode() { result = super.getNode() } + + /** Gets the underlying Python `Call`. */ + Py::Call getCall() { result = toAst(this) } + + /** Gets the flow node for the function component of this call. */ + ControlFlowNode getFunction() { + exists(Py::Call c | + c = toAst(this) and + c.getFunc() = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } + + /** Gets the flow node for the `n`th positional argument. */ + ControlFlowNode getArg(int n) { + exists(Py::Call c | + c = toAst(this) and + c.getArg(n) = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } + + /** Gets the flow node for the named argument with name `name`. */ + ControlFlowNode getArgByName(string name) { + exists(Py::Call c, Py::Keyword k | + c = toAst(this) and + k = c.getANamedArg() and + k.getValue() = toAst(result) and + k.getArg() = name and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } + + /** Gets a flow node corresponding to any argument. */ + ControlFlowNode getAnArg() { result = this.getArg(_) or result = this.getArgByName(_) } + + /** Gets the first tuple (`*args`) argument, if any. */ + ControlFlowNode getStarArg() { + exists(Py::Call c | + c = toAst(this) and + c.getStarArg() = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } + + /** Gets a dictionary (`**kwargs`) argument, if any. */ + ControlFlowNode getKwargs() { + exists(Py::Call c | + c = toAst(this) and + c.getKwargs() = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } + + predicate isDecoratorCall() { this.isClassDecoratorCall() or this.isFunctionDecoratorCall() } + + predicate isClassDecoratorCall() { + exists(Py::ClassExpr cls | toAst(this) = cls.getADecoratorCall()) + } + + predicate isFunctionDecoratorCall() { + exists(Py::FunctionExpr func | toAst(this) = func.getADecoratorCall()) + } +} + +/** A control flow node corresponding to an attribute expression. */ +class AttrNode extends ControlFlowNode { + AttrNode() { toAst(this) instanceof Py::Attribute } + + override Py::Attribute getNode() { result = super.getNode() } + + /** Gets the flow node for the object of the attribute expression. */ + ControlFlowNode getObject() { + exists(Py::Attribute a | + a = toAst(this) and + a.getObject() = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } + + /** Gets the flow node for the object of this attribute expression, with the matching name. */ + ControlFlowNode getObject(string name) { + exists(Py::Attribute a | + a = toAst(this) and + a.getObject() = toAst(result) and + a.getName() = name and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } + + /** Gets the attribute name. */ + string getName() { exists(Py::Attribute a | a = toAst(this) and a.getName() = result) } +} + +/** A control flow node corresponding to an import statement (`import x`). */ +class ImportExprNode extends ControlFlowNode { + ImportExprNode() { toAst(this) instanceof Py::ImportExpr } + + override Py::ImportExpr getNode() { result = super.getNode() } +} + +/** A control flow node corresponding to a `from ... import name` expression. */ +class ImportMemberNode extends ControlFlowNode { + ImportMemberNode() { toAst(this) instanceof Py::ImportMember } + + override Py::ImportMember getNode() { result = super.getNode() } + + /** Gets the flow node for the module being imported from, with the matching name. */ + ControlFlowNode getModule(string name) { + exists(Py::ImportMember i | + i = toAst(this) and + i.getModule() = toAst(result) and + i.getName() = name and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } +} + +/** A control flow node corresponding to a `from ... import *` statement. */ +class ImportStarNode extends ControlFlowNode { + ImportStarNode() { toAst(this) instanceof Py::ImportStar } + + override Py::ImportStar getNode() { result = super.getNode() } + + /** Gets the flow node for the module being imported from. */ + ControlFlowNode getModule() { + exists(Py::ImportStar i | + i = toAst(this) and + i.getModuleExpr() = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } +} + +/** A control flow node corresponding to a subscript expression. */ +class SubscriptNode extends ControlFlowNode { + SubscriptNode() { toAst(this) instanceof Py::Subscript } + + override Py::Subscript getNode() { result = super.getNode() } + + /** Gets the flow node for the value being subscripted. */ + ControlFlowNode getObject() { + exists(Py::Subscript s | + s = toAst(this) and + s.getObject() = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } + + /** Gets the flow node for the index expression. */ + ControlFlowNode getIndex() { + exists(Py::Subscript s | + s = toAst(this) and + s.getIndex() = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } +} + +/** A control flow node corresponding to a comparison operation. */ +class CompareNode extends ControlFlowNode { + CompareNode() { toAst(this) instanceof Py::Compare } + + override Py::Compare getNode() { result = super.getNode() } + + /** Holds if `left` and `right` are a pair of operands for this comparison. */ + predicate operands(ControlFlowNode left, Py::Cmpop op, ControlFlowNode right) { + exists(Py::Compare c, Py::Expr eleft, Py::Expr eright | + c = toAst(this) and eleft = toAst(left) and eright = toAst(right) + | + eleft = c.getLeft() and eright = c.getComparator(0) and op = c.getOp(0) + or + exists(int i | + eleft = c.getComparator(i - 1) and eright = c.getComparator(i) and op = c.getOp(i) + ) + ) and + left.getBasicBlock().dominates(this.getBasicBlock()) and + right.getBasicBlock().dominates(this.getBasicBlock()) + } +} + +/** A control flow node corresponding to a conditional expression (`x if c else y`). */ +class IfExprNode extends ControlFlowNode { + IfExprNode() { toAst(this) instanceof Py::IfExp } + + override Py::IfExp getNode() { result = super.getNode() } + + /** Gets the flow node for one of the operands of an if-expression. */ + ControlFlowNode getAnOperand() { result = this.getAPredecessor() } +} + +/** A control flow node corresponding to an assignment expression (walrus `:=`). */ +class AssignmentExprNode extends ControlFlowNode { + AssignmentExprNode() { toAst(this) instanceof Py::AssignExpr } + + override Py::AssignExpr getNode() { result = super.getNode() } + + /** Gets the flow node for the left-hand side. */ + ControlFlowNode getTarget() { + exists(Py::AssignExpr a | + a = toAst(this) and + a.getTarget() = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } + + /** Gets the flow node for the right-hand side. */ + ControlFlowNode getValue() { + exists(Py::AssignExpr a | + a = toAst(this) and + a.getValue() = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } +} + +/** A control flow node corresponding to a binary expression (`a + b` etc.). */ +class BinaryExprNode extends ControlFlowNode { + BinaryExprNode() { toAst(this) instanceof Py::BinaryExpr } + + override Py::BinaryExpr getNode() { result = super.getNode() } + + ControlFlowNode getLeft() { + exists(Py::BinaryExpr be | + be = toAst(this) and + be.getLeft() = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } + + ControlFlowNode getRight() { + exists(Py::BinaryExpr be | + be = toAst(this) and + be.getRight() = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } + + Py::Operator getOp() { result = toAst(this).(Py::BinaryExpr).getOp() } + + /** Holds if `left` and `right` are the operands and `op` is the operator. */ + predicate operands(ControlFlowNode left, Py::Operator op, ControlFlowNode right) { + left = this.getLeft() and right = this.getRight() and op = this.getOp() + } + + /** Gets either operand. */ + ControlFlowNode getAnOperand() { result = this.getLeft() or result = this.getRight() } +} + +/** A control flow node corresponding to a boolean expression (`a and b`, `a or b`). */ +class BoolExprNode extends ControlFlowNode { + BoolExprNode() { toAst(this) instanceof Py::BoolExpr } + + override Py::BoolExpr getNode() { result = super.getNode() } + + Py::Boolop getOp() { result = toAst(this).(Py::BoolExpr).getOp() } + + /** Gets any operand of this boolean expression. */ + ControlFlowNode getAnOperand() { + exists(Py::BoolExpr be | + be = toAst(this) and + be.getAValue() = toAst(result) + ) + } +} + +/** A control flow node corresponding to a unary expression (`-x`, `not x`, etc.). */ +class UnaryExprNode extends ControlFlowNode { + UnaryExprNode() { toAst(this) instanceof Py::UnaryExpr } + + override Py::UnaryExpr getNode() { result = super.getNode() } + + ControlFlowNode getOperand() { + exists(Py::UnaryExpr u | + u = toAst(this) and + u.getOperand() = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } + + Py::Unaryop getOp() { result = toAst(this).(Py::UnaryExpr).getOp() } +} + +/** + * A control flow node that is a definition: it appears in a context that + * binds a variable (assignment target, parameter, etc.). + */ +class DefinitionNode extends ControlFlowNode { + DefinitionNode() { this.isStore() or this.isParameter() } + + /** Gets the value assigned, if any. */ + ControlFlowNode getValue() { + // For-target: the value is the for-loop's iter expression (which + // is also where `Cfg::ForNode` lives — its `getNode()` returns the + // enclosing `Py::For` statement). Treated specially because there + // is no AST node holding the result of `iter(next(seq))`; we use + // the iter expression's CFG node as the stand-in. + exists(Py::For f | + f.getTarget() = toAst(this) and + toAst(result) = f.getIter() + ) + or + exists(Py::AstNode value | value = assignedValue(toAst(this)) | + toAst(result) = value and + ( + result.getBasicBlock().dominates(this.getBasicBlock()) + or + result.isImport() + or + // The default value for a parameter is evaluated in the same basic block as + // the function definition, but the parameter belongs to the basic block of the + // function, so there is no dominance relationship between the two. + exists(Py::Parameter param | toAst(this) = param.asName()) + ) + ) + } +} + +/** + * Gets the AST node that holds the value assigned to `lhs` in a binding + * context. Mirrors `Flow.qll::assigned_value`. + */ +private Py::AstNode assignedValue(Py::Expr lhs) { + // lhs = result + exists(Py::Assign a | a.getATarget() = lhs and result = a.getValue()) + or + // lhs := result + exists(Py::AssignExpr a | a.getTarget() = lhs and result = a.getValue()) + or + // lhs: annotation = result + exists(Py::AnnAssign a | a.getTarget() = lhs and result = a.getValue()) + or + // import result as lhs (also covers plain `import lhs`, where alias.getAsname() = lhs) + exists(Py::Alias a | a.getAsname() = lhs and result = a.getValue()) + or + // lhs += x -> result is the (lhs + x) binary expression + exists(Py::AugAssign a, Py::BinaryExpr b | + b = a.getOperation() and result = b and lhs = b.getLeft() + ) + or + // Nested sequence assign: ..., lhs, ... = ..., result, ... + exists(Py::Assign a | nestedSequenceAssign(a.getATarget(), a.getValue(), lhs, result)) + or + // Parameter default + exists(Py::Parameter param | lhs = param.asName() and result = param.getDefault()) +} + +/** + * Helper for nested sequence assignments such as `(a, b), c = (1, 2), 3`. + */ +private predicate nestedSequenceAssign( + Py::Expr leftParent, Py::Expr rightParent, Py::Expr left, Py::Expr right +) { + exists(int i | + leftParent.(Py::Tuple).getElt(i) = left and rightParent.(Py::Tuple).getElt(i) = right + or + leftParent.(Py::List).getElt(i) = left and rightParent.(Py::List).getElt(i) = right + ) + or + exists(Py::Expr leftMid, Py::Expr rightMid | + nestedSequenceAssign(leftParent, rightParent, leftMid, rightMid) and + nestedSequenceAssign(leftMid, rightMid, left, right) + ) +} + +/** A control flow node corresponding to a deletion (`del x`). */ +class DeletionNode extends ControlFlowNode { + DeletionNode() { this.isDelete() } +} + +/** A control flow node corresponding to a `for` loop target. */ +class ForNode extends ControlFlowNode { + ForNode() { exists(Py::For f | toAst(this) = f.getIter()) } + + /** Gets the iterable expression. */ + ControlFlowNode getIter() { + result = this and result = result // canonical "after" of the iterable + } + + /** Gets the sequence expression (alias for `getIter()`, matches legacy Flow naming). */ + ControlFlowNode getSequence() { result = this.getIter() } + + /** Gets the target (loop variable) of the `for` loop. */ + ControlFlowNode getTarget() { + exists(Py::For f | + f.getIter() = toAst(this) and + f.getTarget() = toAst(result) + ) + } + + /** Holds if `target` is the loop variable and `sequence` is the iterable. */ + predicate iterates(ControlFlowNode target, ControlFlowNode sequence) { + target = this.getTarget() and sequence = this.getSequence() + } +} + +/** A control flow node corresponding to a `raise` statement. */ +class RaiseStmtNode extends ControlFlowNode { + RaiseStmtNode() { toAst(this) instanceof Py::Raise } + + override Py::Raise getNode() { result = super.getNode() } + + /** Gets the exception expression, if any. */ + ControlFlowNode getException() { + exists(Py::Raise r | + r = toAst(this) and + r.getException() = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } +} + +/** A control flow node corresponding to a starred expression (`*x`). */ +class StarredNode extends ControlFlowNode { + StarredNode() { toAst(this) instanceof Py::Starred } + + /** Gets the value being starred. */ + ControlFlowNode getValue() { + exists(Py::Starred s | + s = toAst(this) and + s.getValue() = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } +} + +/** A control flow node corresponding to an `except` clause's name binding. */ +class ExceptFlowNode extends ControlFlowNode { + ExceptFlowNode() { exists(Py::ExceptStmt e | toAst(this) = e.getName()) } + + /** Gets the CFG node for the bound `as`-name itself. */ + ControlFlowNode getName() { result = this } + + /** Gets the type expression of this exception handler. */ + ControlFlowNode getType() { + exists(Py::ExceptStmt e | + e.getName() = toAst(this) and + e.getType() = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } +} + +/** A control flow node corresponding to an `except*` clause's name binding. */ +class ExceptGroupFlowNode extends ControlFlowNode { + ExceptGroupFlowNode() { exists(Py::ExceptGroupStmt e | toAst(this) = e.getName()) } + + /** Gets the CFG node for the bound `as`-name itself. */ + ControlFlowNode getName() { result = this } +} + +/** Abstract base class for sequence nodes (tuple, list). */ +abstract class SequenceNode extends ControlFlowNode { + /** Gets the `n`th element of this sequence. */ + abstract ControlFlowNode getElement(int n); + + /** Gets any element of this sequence. */ + ControlFlowNode getAnElement() { result = this.getElement(_) } +} + +/** A control flow node corresponding to a tuple literal. */ +class TupleNode extends SequenceNode { + TupleNode() { toAst(this) instanceof Py::Tuple } + + override ControlFlowNode getElement(int n) { + exists(Py::Tuple t | + t = toAst(this) and + t.getElt(n) = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } +} + +/** A control flow node corresponding to a list literal. */ +class ListNode extends SequenceNode { + ListNode() { toAst(this) instanceof Py::List } + + override ControlFlowNode getElement(int n) { + exists(Py::List l | + l = toAst(this) and + l.getElt(n) = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } +} + +/** A control flow node corresponding to a set literal. */ +class SetNode extends ControlFlowNode { + SetNode() { toAst(this) instanceof Py::Set } + + /** Gets the flow node for an element of the set. */ + ControlFlowNode getAnElement() { + exists(Py::Set s | + s = toAst(this) and + s.getAnElt() = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } +} + +/** A control flow node corresponding to a dict literal. */ +class DictNode extends ControlFlowNode { + DictNode() { toAst(this) instanceof Py::Dict } + + /** Gets the flow node for a key of the dict. */ + ControlFlowNode getAKey() { + exists(Py::Dict d | + d = toAst(this) and + d.getAKey() = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } + + /** Gets the flow node for a value of the dict. */ + ControlFlowNode getAValue() { + exists(Py::Dict d | + d = toAst(this) and + d.getAValue() = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } +} + +/** A control flow node corresponding to an iterable in a `for` loop. */ +class IterableNode extends ControlFlowNode { + IterableNode() { + this instanceof SequenceNode + or + this instanceof SetNode + } + + /** Gets the control flow node for an element of this iterable. */ + ControlFlowNode getAnElement() { + result = this.(SequenceNode).getAnElement() + or + result = this.(SetNode).getAnElement() + } +} diff --git a/python/ql/lib/semmle/python/dataflow/new/BarrierGuards.qll b/python/ql/lib/semmle/python/dataflow/new/BarrierGuards.qll index fefa30965cec..39e8d40fd172 100644 --- a/python/ql/lib/semmle/python/dataflow/new/BarrierGuards.qll +++ b/python/ql/lib/semmle/python/dataflow/new/BarrierGuards.qll @@ -1,36 +1,43 @@ /** Provides commonly used BarrierGuards. */ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.DataFlow -private predicate constCompare(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) { - exists(CompareNode cn | cn = g | - exists(ImmutableLiteral const, Cmpop op | - op = any(Eq eq) and branch = true - or - op = any(NotEq ne) and branch = false +private predicate constCompare(DataFlow::GuardNode g, Cfg::ControlFlowNode node, boolean branch) { + exists(Cfg::CompareNode cn | cn = g | + exists(ImmutableLiteral const, Cmpop op, Cfg::ControlFlowNode c | + c.getNode() = const and + ( + op = any(Eq eq) and branch = true + or + op = any(NotEq ne) and branch = false + ) | - cn.operands(const.getAFlowNode(), op, node) + cn.operands(c, op, node) or - cn.operands(node, op, const.getAFlowNode()) + cn.operands(node, op, c) ) or - exists(NameConstant const, Cmpop op | - op = any(Is is_) and branch = true - or - op = any(IsNot isn) and branch = false + exists(NameConstant const, Cmpop op, Cfg::ControlFlowNode c | + c.getNode() = const and + ( + op = any(Is is_) and branch = true + or + op = any(IsNot isn) and branch = false + ) | - cn.operands(const.getAFlowNode(), op, node) + cn.operands(c, op, node) or - cn.operands(node, op, const.getAFlowNode()) + cn.operands(node, op, c) ) or - exists(IterableNode const_iterable, Cmpop op | + exists(Cfg::IterableNode const_iterable, Cmpop op | op = any(In in_) and branch = true or op = any(NotIn ni) and branch = false | - forall(ControlFlowNode elem | elem = const_iterable.getAnElement() | + forall(Cfg::ControlFlowNode elem | elem = const_iterable.getAnElement() | elem.getNode() instanceof ImmutableLiteral ) and cn.operands(node, op, const_iterable) diff --git a/python/ql/lib/semmle/python/dataflow/new/SensitiveDataSources.qll b/python/ql/lib/semmle/python/dataflow/new/SensitiveDataSources.qll index 1a32965d08d7..644f88e311dd 100644 --- a/python/ql/lib/semmle/python/dataflow/new/SensitiveDataSources.qll +++ b/python/ql/lib/semmle/python/dataflow/new/SensitiveDataSources.qll @@ -4,6 +4,7 @@ */ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.DataFlow // Need to import `semmle.python.Frameworks` since frameworks can extend `SensitiveDataSource::Range` private import semmle.python.Frameworks @@ -105,7 +106,7 @@ private module SensitiveDataModeling { or // to cover functions that we don't have the definition for, and where the // reference to the function has not already been marked as being sensitive - this.getFunction().asCfgNode().(NameNode).getId() = sensitiveString(classification) + this.getFunction().asCfgNode().(Cfg::NameNode).getId() = sensitiveString(classification) } override SensitiveDataClassification getClassification() { result = classification } @@ -251,12 +252,12 @@ private module SensitiveDataModeling { SensitiveDataClassification classification; SensitiveVariableAssignment() { - exists(DefinitionNode def | - def.(NameNode).getId() = sensitiveString(classification) and + exists(Cfg::DefinitionNode def | + def.(Cfg::NameNode).getId() = sensitiveString(classification) and ( this.asCfgNode() = def.getValue() or - this.asCfgNode() = def.getValue().(ForNode).getSequence() + this.asCfgNode() = def.getValue().(Cfg::ForNode).getSequence() ) and not this.asExpr() instanceof FunctionExpr and not this.asExpr() instanceof ClassExpr @@ -293,7 +294,7 @@ private module SensitiveDataModeling { SensitiveDataClassification classification; SensitiveSubscript() { - this.asCfgNode().(SubscriptNode).getIndex() = + this.asCfgNode().(Cfg::SubscriptNode).getIndex() = sensitiveLookupStringConst(classification).asCfgNode() } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/Attributes.qll b/python/ql/lib/semmle/python/dataflow/new/internal/Attributes.qll index 8778ae288667..cfdef2a1a905 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/Attributes.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/Attributes.qll @@ -3,6 +3,7 @@ overlay[local] module; private import python +private import semmle.python.controlflow.internal.Cfg as Cfg import DataFlowUtil import DataFlowPublic private import DataFlowPrivate @@ -83,9 +84,9 @@ abstract class AttrWrite extends AttrRef { * ```python * object.attr = value * ``` - * Also gives access to the `value` being written, by extending `DefinitionNode`. + * Also gives access to the `value` being written, by extending `Cfg::DefinitionNode`. */ -private class AttributeAssignmentNode extends DefinitionNode, AttrNode { } +private class AttributeAssignmentNode extends Cfg::DefinitionNode, Cfg::AttrNode { } /** A simple attribute assignment: `object.attr = value`. */ private class AttributeAssignmentAsAttrWrite extends AttrWrite, CfgNode { @@ -131,13 +132,13 @@ private class GlobalAttributeAssignmentAsAttrWrite extends AttrWrite, CfgNode { override string getAttributeName() { result = node.getName() } } -/** Represents `CallNode`s that may refer to calls to built-in functions or classes. */ -private class BuiltInCallNode extends CallNode { +/** Represents `Cfg::CallNode`s that may refer to calls to built-in functions or classes. */ +private class BuiltInCallNode extends Cfg::CallNode { string name; BuiltInCallNode() { // TODO disallow instances where the name of the built-in may refer to an in-scope variable of that name. - exists(NameNode id | + exists(Cfg::NameNode id | name = Builtins::getBuiltinName() and this.getFunction() = id and id.getId() = name and @@ -145,7 +146,7 @@ private class BuiltInCallNode extends CallNode { ) } - /** Gets the name of the built-in function that is called at this `CallNode` */ + /** Gets the name of the built-in function that is called at this `Cfg::CallNode` */ string getBuiltinName() { result = name } } @@ -157,20 +158,20 @@ private class BuiltinAttrCallNode extends BuiltInCallNode { BuiltinAttrCallNode() { name in ["setattr", "getattr", "hasattr", "delattr"] } /** Gets the control flow node for object on which the attribute is accessed. */ - ControlFlowNode getObject() { result in [this.getArg(0), this.getArgByName("object")] } + Cfg::ControlFlowNode getObject() { result in [this.getArg(0), this.getArgByName("object")] } /** * Gets the control flow node for the value that is being written to the attribute. * Only relevant for `setattr` calls. */ - ControlFlowNode getValue() { + Cfg::ControlFlowNode getValue() { // only valid for `setattr` name = "setattr" and result in [this.getArg(2), this.getArgByName("value")] } /** Gets the control flow node that defines the name of the attribute being accessed. */ - ControlFlowNode getName() { result in [this.getArg(1), this.getArgByName("name")] } + Cfg::ControlFlowNode getName() { result in [this.getArg(1), this.getArgByName("name")] } } /** Represents calls to the built-in `setattr`. */ @@ -205,10 +206,10 @@ private class SetAttrCallAsAttrWrite extends AttrWrite, CfgNode { * attr = value * ... * ``` - * Instances of this class correspond to the `NameNode` for `attr`, and also gives access to `value` by - * virtue of being a `DefinitionNode`. + * Instances of this class correspond to the `Cfg::NameNode` for `attr`, and also gives access to `value` by + * virtue of being a `Cfg::DefinitionNode`. */ -private class ClassAttributeAssignmentNode extends DefinitionNode, NameNode { +private class ClassAttributeAssignmentNode extends Cfg::DefinitionNode, Cfg::NameNode { ClassAttributeAssignmentNode() { this.getScope() = any(ClassExpr c).getInnerScope() } } @@ -228,7 +229,7 @@ private class ClassDefinitionAsAttrWrite extends AttrWrite, CfgNode { override Node getValue() { result.asCfgNode() = node.getValue() } - override Node getObject() { result.asCfgNode() = cls.getAFlowNode() } + override Node getObject() { result.asCfgNode().getNode() = cls } override ExprNode getAttributeNameExpr() { none() } @@ -248,7 +249,7 @@ abstract class AttrRead extends AttrRef, Node, LocalSourceNode { /** A simple attribute read, e.g. `object.attr` */ private class AttributeReadAsAttrRead extends AttrRead, CfgNode { - override AttrNode node; + override Cfg::AttrNode node; AttributeReadAsAttrRead() { node.isLoad() } @@ -285,7 +286,7 @@ private class GetAttrCallAsAttrRead extends AttrRead, CfgNode { * is treated as if it is a read of the attribute `module.attr`, even if `module` is not imported directly. */ private class ModuleAttributeImportAsAttrRead extends AttrRead, CfgNode { - override ImportMemberNode node; + override Cfg::ImportMemberNode node; override Node getObject() { result.asCfgNode() = node.getModule(_) } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/Builtins.qll b/python/ql/lib/semmle/python/dataflow/new/internal/Builtins.qll index 764af5d9dc57..94c9b486448f 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/Builtins.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/Builtins.qll @@ -3,6 +3,7 @@ overlay[local] module; private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.internal.ImportStar @@ -67,7 +68,7 @@ module Builtins { DataFlow::CfgNode likelyBuiltin(string name) { exists(Module m | result.getNode() = - any(NameNode n | + any(Cfg::NameNode n | possible_builtin_accessed_in_module(n, name, m) and not possible_builtin_defined_in_module(name, m) ) @@ -87,7 +88,7 @@ module Builtins { * Holds if `n` is an access of a global variable called `name` (which is also the name of a * built-in) inside the module `m`. */ - private predicate possible_builtin_accessed_in_module(NameNode n, string name, Module m) { + private predicate possible_builtin_accessed_in_module(Cfg::NameNode n, string name, Module m) { n.isGlobal() and n.isLoad() and name = n.getId() and diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll index 1db6c08f5f43..872e030e0ecb 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll @@ -25,7 +25,7 @@ * what callable this call might end up targeting. * * Specifically this means that we cannot use type-backtrackers from the function of a - * `CallNode`, since there is no `CallNode` to backtrack from for `func` in the example + * `Cfg::CallNode`, since there is no `Cfg::CallNode` to backtrack from for `func` in the example * above. * * Note: This hasn't been 100% realized yet, so we don't currently expose a predicate to @@ -35,6 +35,7 @@ overlay[local?] module; private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import DataFlowPublic private import DataFlowPrivate private import FlowSummaryImpl as FlowSummaryImpl @@ -162,7 +163,7 @@ newtype TArgumentPosition = */ TLambdaSelfArgumentPosition() or TPositionalArgumentPosition(int index) { - exists(any(CallNode c).getArg(index)) + exists(any(Cfg::CallNode c).getArg(index)) or // since synthetic calls within a summarized callable could use a unique argument // position, we need to ensure we make these available (these are specified as @@ -174,7 +175,7 @@ newtype TArgumentPosition = index = 0 } or TKeywordArgumentPosition(string name) { - exists(any(CallNode c).getArgByName(name)) + exists(any(Cfg::CallNode c).getArgByName(name)) or // see comment for TPositionalArgumentPosition FlowSummaryImpl::ParsePositions::isParsedKeywordParameterPosition(_, name) @@ -256,9 +257,13 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) { */ overlay[local] predicate isStaticmethod(Function func) { - exists(NameNode id | id.getId() = "staticmethod" and id.isGlobal() | - func.getADecorator() = id.getNode() - ) + // The decorator is *syntactically* a Name "staticmethod" — we don't + // care which variable it resolves to. Even if a class redefines + // `staticmethod`, the binding hasn't happened yet at the decorator + // position, so Python's runtime semantics is "use the builtin". + // Matches legacy ESSA semantics which used `isGlobal()` (i.e. "no + // SSA def reaches this load") at the decorator's `NameNode`. + func.getADecorator().(Name).getId() = "staticmethod" } /** @@ -268,9 +273,7 @@ predicate isStaticmethod(Function func) { */ overlay[local] predicate isClassmethod(Function func) { - exists(NameNode id | id.getId() = "classmethod" and id.isGlobal() | - func.getADecorator() = id.getNode() - ) + func.getADecorator().(Name).getId() = "classmethod" or exists(Class cls | cls.getAMethod() = func and @@ -284,21 +287,19 @@ predicate isClassmethod(Function func) { /** Holds if the function `func` has a `property` decorator. */ overlay[local] -predicate hasPropertyDecorator(Function func) { - exists(NameNode id | id.getId() = "property" and id.isGlobal() | - func.getADecorator() = id.getNode() - ) -} +predicate hasPropertyDecorator(Function func) { func.getADecorator().(Name).getId() = "property" } /** * Holds if the function `func` has a `contextlib.contextmanager`. */ overlay[local] predicate hasContextmanagerDecorator(Function func) { - exists(ControlFlowNode contextmanager | - contextmanager.(NameNode).getId() = "contextmanager" and contextmanager.(NameNode).isGlobal() + exists(Cfg::ControlFlowNode contextmanager | + contextmanager.(Cfg::NameNode).getId() = "contextmanager" and + contextmanager.(Cfg::NameNode).isGlobal() or - contextmanager.(AttrNode).getObject("contextmanager").(NameNode).getId() = "contextlib" + contextmanager.(Cfg::AttrNode).getObject("contextmanager").(Cfg::NameNode).getId() = + "contextlib" | func.getADecorator() = contextmanager.getNode() ) @@ -314,10 +315,10 @@ predicate hasContextmanagerDecorator(Function func) { */ overlay[local] private predicate hasOverloadDecorator(Function func) { - exists(ControlFlowNode overload | - overload.(NameNode).getId() = "overload" and overload.(NameNode).isGlobal() + exists(Cfg::ControlFlowNode overload | + overload.(Cfg::NameNode).getId() = "overload" and overload.(Cfg::NameNode).isGlobal() or - overload.(AttrNode).getObject("overload").(NameNode).isGlobal() + overload.(Cfg::AttrNode).getObject("overload").(Cfg::NameNode).isGlobal() | func.getADecorator() = overload.getNode() ) @@ -536,7 +537,7 @@ class LibraryCallableValue extends DataFlowCallable, TLibraryCallable { // ============================================================================= /** Gets a call to `type`. */ private CallCfgNode getTypeCall() { - exists(NameNode id | id.getId() = "type" and id.isGlobal() | + exists(Cfg::NameNode id | id.getId() = "type" and id.isGlobal() | result.getFunction().asCfgNode() = id ) } @@ -548,7 +549,7 @@ private CallCfgNode getSuperCall() { // link below), but otherwise only 2 edgecases. Overall it seems ok to ignore this complexity. // // https://github.com/python/cpython/blob/18b1782192f85bd26db89f5bc850f8bee4247c1a/Lib/unittest/mock.py#L48-L50 - exists(NameNode id | id.getId() = "super" and id.isGlobal() | + exists(Cfg::NameNode id | id.getId() = "super" and id.isGlobal() | result.getFunction().asCfgNode() = id ) } @@ -1034,7 +1035,7 @@ private module MethodCalls { */ pragma[nomagic] private predicate directCall( - CallNode call, Function target, string functionName, Class cls, AttrRead attr, Node self + Cfg::CallNode call, Function target, string functionName, Class cls, AttrRead attr, Node self ) { target = findFunctionAccordingToMroKnownStartingClass(cls, functionName) and directCall_join(call, functionName, cls, attr, self) @@ -1043,7 +1044,7 @@ private module MethodCalls { /** Extracted to give good join order */ pragma[nomagic] private predicate directCall_join( - CallNode call, string functionName, Class cls, AttrRead attr, Node self + Cfg::CallNode call, string functionName, Class cls, AttrRead attr, Node self ) { call.getFunction() = attrReadTracker(attr).asCfgNode() and attr.accesses(self, functionName) and @@ -1060,7 +1061,7 @@ private module MethodCalls { */ pragma[nomagic] private predicate callWithinMethodImplicitSelfOrCls( - CallNode call, Function target, string functionName, Class classWithMethod, AttrRead attr, + Cfg::CallNode call, Function target, string functionName, Class classWithMethod, AttrRead attr, Node self ) { target = findFunctionAccordingToMro(getADirectSubclass*(classWithMethod), functionName) and @@ -1070,7 +1071,7 @@ private module MethodCalls { /** Extracted to give good join order */ pragma[nomagic] private predicate callWithinMethodImplicitSelfOrCls_join( - CallNode call, string functionName, Class classWithMethod, AttrRead attr, Node self + Cfg::CallNode call, string functionName, Class classWithMethod, AttrRead attr, Node self ) { call.getFunction() = attrReadTracker(attr).asCfgNode() and attr.accesses(self, functionName) and @@ -1082,7 +1083,7 @@ private module MethodCalls { * resolve the call to a known target (since the only super class might be the * builtin `object`, so we never have the implementation of `__new__` in the DB). */ - predicate fromSuperNewCall(CallNode call, Class classUsedInSuper, AttrRead attr, Node self) { + predicate fromSuperNewCall(Cfg::CallNode call, Class classUsedInSuper, AttrRead attr, Node self) { fromSuper_join(call, "__new__", classUsedInSuper, attr, self) and self in [classTracker(_), clsArgumentTracker(_)] } @@ -1104,7 +1105,7 @@ private module MethodCalls { */ pragma[nomagic] predicate fromSuper( - CallNode call, Function target, string functionName, Class classUsedInSuper, AttrRead attr, + Cfg::CallNode call, Function target, string functionName, Class classUsedInSuper, AttrRead attr, Node self ) { target = findFunctionAccordingToMro(getNextClassInMro(classUsedInSuper), functionName) and @@ -1114,7 +1115,7 @@ private module MethodCalls { /** Extracted to give good join order */ pragma[nomagic] private predicate fromSuper_join( - CallNode call, string functionName, Class classUsedInSuper, AttrRead attr, Node self + Cfg::CallNode call, string functionName, Class classUsedInSuper, AttrRead attr, Node self ) { call.getFunction() = attrReadTracker(attr).asCfgNode() and ( @@ -1133,7 +1134,7 @@ private module MethodCalls { ) } - predicate resolveMethodCall(CallNode call, Function target, CallType type, Node self) { + predicate resolveMethodCall(Cfg::CallNode call, Function target, CallType type, Node self) { ( directCall(call, target, _, _, _, self) or @@ -1180,7 +1181,7 @@ import MethodCalls * NOTE: We have this predicate mostly to be able to compare with old point-to * call-graph resolution. So it could be removed in the future. */ -predicate resolveClassCall(CallNode call, Class cls) { +predicate resolveClassCall(Cfg::CallNode call, Class cls) { call.getFunction() = classTracker(cls).asCfgNode() or // `cls()` inside a classmethod (which also contains `type(self)()` inside a method) @@ -1210,7 +1211,7 @@ Function invokedFunctionFromClassConstruction(Class cls, string funcName) { * * See https://docs.python.org/3/reference/datamodel.html#object.__call__ */ -predicate resolveClassInstanceCall(CallNode call, Function target, Node self) { +predicate resolveClassInstanceCall(Cfg::CallNode call, Function target, Node self) { exists(Class cls | call.getFunction() = classInstanceTracker(cls).asCfgNode() and target = findFunctionAccordingToMroKnownStartingClass(cls, "__call__") @@ -1229,7 +1230,7 @@ predicate resolveClassInstanceCall(CallNode call, Function target, Node self) { * Holds if `call` is a call to the `target`, with call-type `type`. */ cached -predicate resolveCall(CallNode call, Function target, CallType type) { +predicate resolveCall(Cfg::CallNode call, Function target, CallType type) { Stages::DataFlow::ref() and ( type instanceof CallTypePlainFunction and @@ -1254,11 +1255,11 @@ predicate resolveCall(CallNode call, Function target, CallType type) { // ============================================================================= /** * Holds if the argument of `call` at position `apos` is `arg`. This is just a helper - * predicate that maps ArgumentPositions to the arguments of the underlying `CallNode`. + * predicate that maps ArgumentPositions to the arguments of the underlying `Cfg::CallNode`. */ overlay[local] cached -predicate normalCallArg(CallNode call, Node arg, ArgumentPosition apos) { +predicate normalCallArg(Cfg::CallNode call, Node arg, ArgumentPosition apos) { exists(int index | apos.isPositional(index) and arg.asCfgNode() = call.getArg(index) @@ -1273,7 +1274,7 @@ predicate normalCallArg(CallNode call, Node arg, ArgumentPosition apos) { exists(int index | apos.isStarArgs(index) and arg.asCfgNode() = call.getStarArg() and - // since `CallNode.getArg` doesn't include `*args`, we need to drop to the AST level + // since `Cfg::CallNode.getArg` doesn't include `*args`, we need to drop to the AST level // to get the index. Notice that we only use the AST for getting the index, so we // don't need to check for dominance in regards to splitting. call.getStarArg().getNode() = call.getNode().getPositionalArg(index).(Starred).getValue() @@ -1347,7 +1348,9 @@ predicate normalCallArg(CallNode call, Node arg, ArgumentPosition apos) { * translated into `l.clear()`, and we can still have use-use flow. */ cached -predicate getCallArg(CallNode call, Function target, CallType type, Node arg, ArgumentPosition apos) { +predicate getCallArg( + Cfg::CallNode call, Function target, CallType type, Node arg, ArgumentPosition apos +) { Stages::DataFlow::ref() and resolveCall(call, target, type) and ( @@ -1440,10 +1443,13 @@ private predicate sameEnclosingCallable(Node node1, Node node2) { // DataFlowCall // ============================================================================= newtype TDataFlowCall = - TNormalCall(CallNode call, Function target, CallType type) { resolveCall(call, target, type) } or + TNormalCall(Cfg::CallNode call, Function target, CallType type) { + resolveCall(call, target, type) and + Cfg::isCanonicalAstNodeRepresentative(call) + } or /** A call to the generated function inside a comprehension */ TComprehensionCall(Comp c) or - TPotentialLibraryCall(CallNode call) or + TPotentialLibraryCall(Cfg::CallNode call) { Cfg::isCanonicalAstNodeRepresentative(call) } or /** A synthesized call inside a summarized callable */ TSummaryCall( FlowSummaryImpl::Public::SummarizedCallable c, FlowSummaryImpl::Private::SummaryNode receiver @@ -1463,7 +1469,7 @@ abstract class DataFlowCall extends TDataFlowCall { abstract ArgumentNode getArgument(ArgumentPosition apos); /** Get the control flow node representing this call, if any. */ - abstract ControlFlowNode getNode(); + abstract Cfg::ControlFlowNode getNode(); /** Gets the enclosing callable of this call. */ DataFlowCallable getEnclosingCallable() { result = getCallableScope(this.getScope()) } @@ -1494,28 +1500,28 @@ abstract class ExtractedDataFlowCall extends DataFlowCall { } /** - * A resolved call in source code with an underlying `CallNode`. + * A resolved call in source code with an underlying `Cfg::CallNode`. * * This is considered normal, compared with special calls such as `obj[0]` calling the * `__getitem__` method on the object. However, this also includes calls that go to the * `__call__` special method. */ class NormalCall extends ExtractedDataFlowCall, TNormalCall { - CallNode call; + Cfg::CallNode call; Function target; CallType type; NormalCall() { this = TNormalCall(call, target, type) } override string toString() { - // note: if we used toString directly on the CallNode we would get - // `ControlFlowNode for func()` - // but the `ControlFlowNode` part is just clutter, so we go directly to the AST node + // note: if we used toString directly on the Cfg::CallNode we would get + // `Cfg::ControlFlowNode for func()` + // but the `Cfg::ControlFlowNode` part is just clutter, so we go directly to the AST node // instead. result = call.getNode().toString() } - override ControlFlowNode getNode() { result = call } + override Cfg::ControlFlowNode getNode() { result = call } override Scope getScope() { result = call.getScope() } @@ -1543,7 +1549,7 @@ class ComprehensionCall extends ExtractedDataFlowCall, TComprehensionCall { override string toString() { result = "comprehension call" } - override ControlFlowNode getNode() { result.getNode() = c } + override Cfg::ControlFlowNode getNode() { result.getNode() = c } override Scope getScope() { result = c.getScope() } @@ -1566,14 +1572,14 @@ class ComprehensionCall extends ExtractedDataFlowCall, TComprehensionCall { * in this class. */ class PotentialLibraryCall extends ExtractedDataFlowCall, TPotentialLibraryCall { - CallNode call; + Cfg::CallNode call; PotentialLibraryCall() { this = TPotentialLibraryCall(call) } override string toString() { - // note: if we used toString directly on the CallNode we would get - // `ControlFlowNode for func()` - // but the `ControlFlowNode` part is just clutter, so we go directly to the AST node + // note: if we used toString directly on the Cfg::CallNode we would get + // `Cfg::ControlFlowNode for func()` + // but the `Cfg::ControlFlowNode` part is just clutter, so we go directly to the AST node // instead. result = call.getNode().toString() } @@ -1590,10 +1596,10 @@ class PotentialLibraryCall extends ExtractedDataFlowCall, TPotentialLibraryCall // potential self argument, from `foo.bar()` -- note that this could also just be a // module reference, but we really don't have a good way of knowing :| apos.isSelf() and - result.asCfgNode() = call.getFunction().(AttrNode).getObject() + result.asCfgNode() = call.getFunction().(Cfg::AttrNode).getObject() } - override ControlFlowNode getNode() { result = call } + override Cfg::ControlFlowNode getNode() { result = call } override Scope getScope() { result = call.getScope() } } @@ -1625,7 +1631,7 @@ class SummaryCall extends DataFlowCall, TSummaryCall { override ArgumentNode getArgument(ArgumentPosition apos) { none() } - override ControlFlowNode getNode() { none() } + override Cfg::ControlFlowNode getNode() { none() } override string toString() { result = "[summary] call to " + receiver + " in " + c } @@ -1767,12 +1773,12 @@ private class SummaryPostUpdateNode extends FlowSummaryNode, PostUpdateNodeImpl * This is used for tracking flow through captured variables. */ class SynthCapturedVariablesArgumentNode extends Node, TSynthCapturedVariablesArgumentNode { - ControlFlowNode callable; + Cfg::ControlFlowNode callable; SynthCapturedVariablesArgumentNode() { this = TSynthCapturedVariablesArgumentNode(callable) } - /** Gets the `CallNode` corresponding to this captured variables argument node. */ - CallNode getCallNode() { result.getFunction() = callable } + /** Gets the `Cfg::CallNode` corresponding to this captured variables argument node. */ + Cfg::CallNode getCallNode() { result.getFunction() = callable } /** Gets the `CfgNode` that corresponds to this synthetic node. */ CfgNode getUnderlyingNode() { result.asCfgNode() = callable } @@ -1790,7 +1796,7 @@ class CapturedVariablesArgumentNodeAsArgumentNode extends ArgumentNode, { overlay[global] override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) { - exists(CallNode callNode | callNode = this.getCallNode() | + exists(Cfg::CallNode callNode | callNode = this.getCallNode() | callNode = call.getNode() and exists(Function target | resolveCall(callNode, target, _) | target = any(VariableCapture::CapturedVariable v).getACapturingScope() @@ -1804,7 +1810,7 @@ class CapturedVariablesArgumentNodeAsArgumentNode extends ArgumentNode, class SynthCapturedVariablesArgumentPostUpdateNode extends PostUpdateNodeImpl, TSynthCapturedVariablesArgumentPostUpdateNode { - ControlFlowNode callable; + Cfg::ControlFlowNode callable; SynthCapturedVariablesArgumentPostUpdateNode() { this = TSynthCapturedVariablesArgumentPostUpdateNode(callable) @@ -1911,8 +1917,8 @@ abstract class ReturnNode extends Node { class ExtractedReturnNode extends ReturnNode, CfgNode { // See `TaintTrackingImplementation::returnFlowStep` ExtractedReturnNode() { - node = any(Return ret).getValue().getAFlowNode() or - node = any(Yield yield).getAFlowNode() + node.getNode() = any(Return ret).getValue() or + node.getNode() = any(Yield yield) } override ReturnKind getKind() { any() } @@ -1930,7 +1936,7 @@ class ExtractedReturnNode extends ReturnNode, CfgNode { class YieldNodeInContextManagerFunction extends ReturnNode, CfgNode { YieldNodeInContextManagerFunction() { hasContextmanagerDecorator(node.getScope()) and - node = any(Yield yield).getValue().getAFlowNode() + node.getNode() = any(Yield yield).getValue() } override ReturnKind getKind() { any() } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll index fffd0150008e..2ef018a912e5 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll @@ -2,8 +2,9 @@ overlay[local?] module; private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import DataFlowPublic -private import semmle.python.essa.SsaCompute +private import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl private import semmle.python.dataflow.new.internal.ImportResolution private import FlowSummaryImpl as FlowSummaryImpl private import semmle.python.frameworks.data.ModelsAsData @@ -43,13 +44,23 @@ predicate isArgumentNode(ArgumentNode arg, DataFlowCall c, ArgumentPosition pos) // Nodes //-------- overlay[local] -predicate isExpressionNode(ControlFlowNode node) { node.getNode() instanceof Expr } +predicate isExpressionNode(Cfg::ControlFlowNode node) { + node.getNode() instanceof Expr + or + // `Cfg::ForNode` wraps a `For` statement's iter position, but + // overrides `.getNode()` to return the `Py::For` statement (for + // legacy parity). The underlying AST is still an `Expr` (the iter + // expression); we want a dataflow node here so that for-loop + // content reads (`for y in l`) have a source expression node to + // read content from. + node instanceof Cfg::ForNode +} // ============================================================================= // SyntheticPreUpdateNode // ============================================================================= class SyntheticPreUpdateNode extends Node, TSyntheticPreUpdateNode { - CallNode node; + Cfg::CallNode node; SyntheticPreUpdateNode() { this = TSyntheticPreUpdateNode(node) } @@ -151,7 +162,7 @@ predicate synthStarArgsElementParameterNodeStoreStep( * been passed in a `**kwargs` argument. */ class SynthDictSplatArgumentNode extends Node, TSynthDictSplatArgumentNode { - CallNode node; + Cfg::CallNode node; SynthDictSplatArgumentNode() { this = TSynthDictSplatArgumentNode(node) } @@ -165,7 +176,7 @@ class SynthDictSplatArgumentNode extends Node, TSynthDictSplatArgumentNode { private predicate synthDictSplatArgumentNodeStoreStep( ArgumentNode nodeFrom, DictionaryElementContent c, SynthDictSplatArgumentNode nodeTo ) { - exists(string name, CallNode call, ArgumentPosition keywordPos | + exists(string name, Cfg::CallNode call, ArgumentPosition keywordPos | nodeTo = TSynthDictSplatArgumentNode(call) and getCallArg(call, _, _, nodeFrom, keywordPos) and keywordPos.isKeyword(name) and @@ -185,8 +196,8 @@ private predicate synthDictSplatArgumentNodeStoreStep( */ predicate yieldStoreStep(Node nodeFrom, Content c, Node nodeTo) { exists(Yield yield | - nodeTo.asCfgNode() = yield.getAFlowNode() and - nodeFrom.asCfgNode() = yield.getValue().getAFlowNode() and + nodeTo.asCfgNode().getNode() = yield and + nodeFrom.asCfgNode().getNode() = yield.getValue() and // TODO: Consider if this will also need to transfer dictionary content // once dictionary comprehensions are supported. c instanceof ListElementContent @@ -289,7 +300,7 @@ abstract class PostUpdateNodeImpl extends Node { * Synthetic post-update nodes for synthetic nodes need to be listed one by one. */ class SyntheticPostUpdateNode extends PostUpdateNodeImpl, TSyntheticPostUpdateNode { - ControlFlowNode node; + Cfg::ControlFlowNode node; SyntheticPostUpdateNode() { this = TSyntheticPostUpdateNode(node) } @@ -333,16 +344,42 @@ module LocalFlow { // `x = f(42)` // nodeFrom is `f(42)` // nodeTo is `x` - exists(AssignmentDefinition def | + // + // We use the CFG-level `DefinitionNode.getValue()` directly rather + // than going through SSA, because the new SSA library prunes write + // definitions that have no subsequent read in the same scope (e.g. + // a module-level `def f():` whose `f` is only read inside other + // functions). The CFG-level link is unconditional. + // + // The Name-target restriction mirrors legacy ESSA's + // `SsaDefinitions::assignment_definition`, which required + // `defn.(NameNode).defines(v)`. Subscript and attribute writes + // (`x[i] = 42`, `obj.attr = 42`) are intentionally excluded — their + // value flow is handled by the content-flow / `AttrWrite` machinery, + // not by a local-flow step *into* the Subscript/Attribute expression. + // Excluding them is essential for keeping augmented-assignment + // targets (`x[i] += 42`) classifiable as `LocalSourceNode` on the + // read side: the single canonical CFG node is both a load and a + // store, and any incoming local-flow step would disqualify it from + // being a local source. + exists(Cfg::DefinitionNode def | nodeFrom.(CfgNode).getNode() = def.getValue() and - nodeTo.(CfgNode).getNode() = def.getDefiningNode() + nodeTo.(CfgNode).getNode() = def and + def instanceof Cfg::NameNode and + // Parameter defaults are evaluated in the enclosing scope, while the + // parameter itself lives in the function's scope. The cross-scope + // edge is provided by `runtimeJumpStep` instead. + not exists(Py::Parameter param | def.getNode() = param.asName()) ) or // With definition // `with f(42) as x:` // nodeFrom is `f(42)` // nodeTo is `x` - exists(With with, ControlFlowNode contextManager, WithDefinition withDef, ControlFlowNode var | + exists( + With with, Cfg::ControlFlowNode contextManager, SsaImpl::WithDefinition withDef, + Cfg::ControlFlowNode var + | var = withDef.getDefiningNode() | nodeFrom.(CfgNode).getNode() = contextManager and @@ -361,13 +398,13 @@ module LocalFlow { predicate expressionFlowStep(Node nodeFrom, Node nodeTo) { // If expressions - nodeFrom.asCfgNode() = nodeTo.asCfgNode().(IfExprNode).getAnOperand() + nodeFrom.asCfgNode() = nodeTo.asCfgNode().(Cfg::IfExprNode).getAnOperand() or // Assignment expressions - nodeFrom.asCfgNode() = nodeTo.asCfgNode().(AssignmentExprNode).getValue() + nodeFrom.asCfgNode() = nodeTo.asCfgNode().(Cfg::AssignmentExprNode).getValue() or // boolean inline expressions such as `x or y` or `x and y` - nodeFrom.asCfgNode() = nodeTo.asCfgNode().(BoolExprNode).getAnOperand() + nodeFrom.asCfgNode() = nodeTo.asCfgNode().(Cfg::BoolExprNode).getAnOperand() or // Flow inside an unpacking assignment iterableUnpackingFlowStep(nodeFrom, nodeTo) @@ -376,12 +413,28 @@ module LocalFlow { matchFlowStep(nodeFrom, nodeTo) } - predicate useToNextUse(NameNode nodeFrom, NameNode nodeTo) { - AdjacentUses::adjacentUseUse(nodeFrom, nodeTo) + predicate useToNextUse(Cfg::NameNode nodeFrom, Cfg::NameNode nodeTo) { + // The SSA-level adjacent-use predicate works on specific CFG variants + // (e.g. boolean-outcome `[true]`/`[false]` or emptiness `[empty]`/`[non-empty]` + // splits of the same AST node), but dataflow values are insensitive to + // those splits — there is at most one `CfgNode` per AST. Project both + // ends through `Cfg::isCanonicalAstNodeRepresentative` so all variants + // contribute their use-use edges to the canonical pair. + exists(Cfg::NameNode fromVariant, Cfg::NameNode toVariant | + SsaImpl::AdjacentUses::adjacentUseUse(fromVariant, toVariant) and + fromVariant.getNode() = nodeFrom.getNode() and + toVariant.getNode() = nodeTo.getNode() and + Cfg::isCanonicalAstNodeRepresentative(nodeFrom) and + Cfg::isCanonicalAstNodeRepresentative(nodeTo) + ) } - predicate defToFirstUse(EssaVariable var, NameNode nodeTo) { - AdjacentUses::firstUse(var.getDefinition(), nodeTo) + predicate defToFirstUse(SsaImpl::EssaVariable var, Cfg::NameNode nodeTo) { + exists(Cfg::NameNode toVariant | + SsaImpl::AdjacentUses::firstUse(var.getDefinition(), toVariant) and + toVariant.getNode() = nodeTo.getNode() and + Cfg::isCanonicalAstNodeRepresentative(nodeTo) + ) } predicate useUseFlowStep(Node nodeFrom, Node nodeTo) { @@ -390,12 +443,14 @@ module LocalFlow { // `x = f(y)` // nodeFrom is `y` on first line // nodeTo is `y` on second line - exists(EssaDefinition def | - nodeFrom.(CfgNode).getNode() = def.(EssaNodeDefinition).getDefiningNode() + exists(SsaImpl::EssaDefinition def, Cfg::NameNode toVariant | + nodeFrom.(CfgNode).getNode() = def.(SsaImpl::EssaNodeDefinition).getDefiningNode() or nodeFrom.(ScopeEntryDefinitionNode).getDefinition() = def | - AdjacentUses::firstUse(def, nodeTo.(CfgNode).getNode()) + SsaImpl::AdjacentUses::firstUse(def, toVariant) and + toVariant.getNode() = nodeTo.(CfgNode).getNode().getNode() and + Cfg::isCanonicalAstNodeRepresentative(nodeTo.(CfgNode).getNode()) ) or // Next use after use @@ -557,9 +612,9 @@ predicate runtimeJumpStep(Node nodeFrom, Node nodeTo) { // a parameter with a default value, since the parameter will be in the scope of the // function, while the default value itself will be in the scope that _defines_ the // function. - exists(ParameterDefinition param | + exists(SsaImpl::ParameterDefinition param | // note: we go to the _control-flow node_ of the parameter, and not the ESSA node of the parameter, since for type-tracking, the ESSA node is not a LocalSourceNode, so we would get in trouble. - nodeFrom.asCfgNode() = param.getDefault() and + nodeFrom.asCfgNode().getNode() = param.getParameter().(Parameter).getDefault() and nodeTo.asCfgNode() = param.getDefiningNode() ) or @@ -663,7 +718,7 @@ predicate neverSkipInPathGraph(Node n) { // ``` // we would end up saying that the path MUST not skip the x in `y = x`, which is just // annoying and doesn't help the path explanation become clearer. - n.asCfgNode() = any(EssaNodeDefinition def).getDefiningNode() + n.asCfgNode() = any(SsaImpl::EssaNodeDefinition def).getDefiningNode() } /** @@ -872,7 +927,7 @@ predicate listStoreStep(CfgNode nodeFrom, ListElementContent c, CfgNode nodeTo) // nodeFrom is `42`, cfg node // nodeTo is the list, `[..., 42, ...]`, cfg node // c denotes element of list - nodeTo.getNode().(ListNode).getAnElement() = nodeFrom.getNode() and + nodeTo.getNode().(Cfg::ListNode).getAnElement() = nodeFrom.getNode() and not nodeTo.getNode() instanceof UnpackingAssignmentSequenceTarget and // Suppress unused variable warning c = c @@ -885,7 +940,7 @@ predicate setStoreStep(CfgNode nodeFrom, SetElementContent c, CfgNode nodeTo) { // nodeFrom is `42`, cfg node // nodeTo is the set, `{..., 42, ...}`, cfg node // c denotes element of list - nodeTo.getNode().(SetNode).getAnElement() = nodeFrom.getNode() and + nodeTo.getNode().(Cfg::SetNode).getAnElement() = nodeFrom.getNode() and // Suppress unused variable warning c = c } @@ -898,7 +953,7 @@ predicate tupleStoreStep(CfgNode nodeFrom, TupleElementContent c, CfgNode nodeTo // nodeTo is the tuple, `(..., 42, ...)`, cfg node // c denotes element of tuple and index of nodeFrom exists(int n | - nodeTo.getNode().(TupleNode).getElement(n) = nodeFrom.getNode() and + nodeTo.getNode().(Cfg::TupleNode).getElement(n) = nodeFrom.getNode() and not nodeTo.getNode() instanceof UnpackingAssignmentSequenceTarget and c.getIndex() = n ) @@ -912,7 +967,7 @@ predicate dictStoreStep(CfgNode nodeFrom, DictionaryElementContent c, Node nodeT // nodeTo is the dict, `{..., "key" = 42, ...}`, cfg node // c denotes element of dictionary and the key `"key"` exists(KeyValuePair item | - item = nodeTo.asCfgNode().(DictNode).getNode().(Dict).getAnItem() and + item = nodeTo.asCfgNode().(Cfg::DictNode).getNode().(Dict).getAnItem() and nodeFrom.getNode().getNode() = item.getValue() and c.getKey() = item.getKey().(StringLiteral).getS() ) @@ -927,9 +982,9 @@ predicate dictStoreStep(CfgNode nodeFrom, DictionaryElementContent c, Node nodeT private predicate moreDictStoreSteps(CfgNode nodeFrom, DictionaryElementContent c, Node nodeTo) { // NOTE: It's important to add logic to the newtype definition of // DictionaryElementContent if you add new cases here. - exists(SubscriptNode subscript | + exists(Cfg::SubscriptNode subscript | nodeTo.(PostUpdateNode).getPreUpdateNode().asCfgNode() = subscript.getObject() and - nodeFrom.asCfgNode() = subscript.(DefinitionNode).getValue() and + nodeFrom.asCfgNode() = subscript.(Cfg::DefinitionNode).getValue() and c.getKey() = subscript.getIndex().getNode().(StringLiteral).getText() ) or @@ -942,8 +997,8 @@ private predicate moreDictStoreSteps(CfgNode nodeFrom, DictionaryElementContent } predicate dictClearStep(Node node, DictionaryElementContent c) { - exists(SubscriptNode subscript | - subscript instanceof DefinitionNode and + exists(Cfg::SubscriptNode subscript | + subscript instanceof Cfg::DefinitionNode and node.asCfgNode() = subscript.getObject() and c.getKey() = subscript.getIndex().getNode().(StringLiteral).getText() ) @@ -1018,7 +1073,7 @@ predicate subscriptReadStep(CfgNode nodeFrom, Content c, CfgNode nodeTo) { // nodeFrom is `l`, cfg node // nodeTo is `l[3]`, cfg node // c is compatible with 3 - nodeFrom.getNode() = nodeTo.getNode().(SubscriptNode).getObject() and + nodeFrom.getNode() = nodeTo.getNode().(Cfg::SubscriptNode).getObject() and ( c instanceof ListElementContent or @@ -1027,10 +1082,10 @@ predicate subscriptReadStep(CfgNode nodeFrom, Content c, CfgNode nodeTo) { c instanceof DictionaryElementAnyContent or c.(TupleElementContent).getIndex() = - nodeTo.getNode().(SubscriptNode).getIndex().getNode().(IntegerLiteral).getValue() + nodeTo.getNode().(Cfg::SubscriptNode).getIndex().getNode().(IntegerLiteral).getValue() or c.(DictionaryElementContent).getKey() = - nodeTo.getNode().(SubscriptNode).getIndex().getNode().(StringLiteral).getS() + nodeTo.getNode().(Cfg::SubscriptNode).getIndex().getNode().(StringLiteral).getS() ) } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll index 8612d4a253e0..4724a8e59129 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll @@ -5,11 +5,12 @@ overlay[local] module; private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import DataFlowPrivate import semmle.python.dataflow.new.TypeTracking import Attributes import LocalSources -private import semmle.python.essa.SsaCompute +private import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl private import semmle.python.dataflow.new.internal.ImportStar private import semmle.python.frameworks.data.ModelsAsData private import FlowSummaryImpl as FlowSummaryImpl @@ -27,16 +28,21 @@ private import semmle.python.frameworks.data.ModelsAsData overlay[local] newtype TNode = /** A node corresponding to a control flow node. */ - TCfgNode(ControlFlowNode node) { - isExpressionNode(node) - or - node.getNode() instanceof Pattern + TCfgNode(Cfg::ControlFlowNode node) { + ( + isExpressionNode(node) + or + node.getNode() instanceof Pattern + ) and + Cfg::isCanonicalAstNodeRepresentative(node) } or /** * A node corresponding to a scope entry definition. That is, the value of a variable * as it enters a scope. */ - TScopeEntryDefinitionNode(ScopeEntryDefinition def) { not def.getScope() instanceof Module } or + TScopeEntryDefinitionNode(SsaImpl::ScopeEntryDefinition def) { + not def.getScope() instanceof Module + } or /** * A synthetic node representing the value of an object before a state change. * @@ -47,36 +53,39 @@ newtype TNode = // 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 + TSyntheticPreUpdateNode(Cfg::CallNode call) { Cfg::isCanonicalAstNodeRepresentative(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(_) + TSyntheticPostUpdateNode(Cfg::ControlFlowNode node) { + Cfg::isCanonicalAstNodeRepresentative(node) and + ( + exists(Cfg::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 = call.getArgByName(_) + node = any(Cfg::AttrNode a).getObject() 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) + node = any(Cfg::SubscriptNode s).getObject() + or + // self parameter when used implicitly in `super()` + exists(Class cls, Function func, SsaImpl::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 + // the iterable argument to the implicit comprehension function + node.getNode() = any(Comp c).getIterable() ) - or - // the iterable argument to the implicit comprehension function - node.getNode() = any(Comp c).getIterable() } or /** A node representing a global (module-level) variable in a specific module. */ TModuleVariableNode(Module m, GlobalVariable v) { v.getScope() = m } or @@ -112,7 +121,9 @@ newtype TNode = 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 + TSynthDictSplatArgumentNode(Cfg::CallNode call) { + exists(call.getArgByName(_)) and Cfg::isCanonicalAstNodeRepresentative(call) + } 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))) @@ -128,15 +139,17 @@ newtype TNode = * A synthetic node representing the values of the variables captured * by the callable being called. */ - TSynthCapturedVariablesArgumentNode(ControlFlowNode callable) { - callable = any(CallNode c).getFunction() + TSynthCapturedVariablesArgumentNode(Cfg::ControlFlowNode callable) { + callable = any(Cfg::CallNode c).getFunction() and + Cfg::isCanonicalAstNodeRepresentative(callable) } or /** * A synthetic node representing the values of the variables captured * by the callable being called, after the output has been computed. */ - TSynthCapturedVariablesArgumentPostUpdateNode(ControlFlowNode callable) { - callable = any(CallNode c).getFunction() + TSynthCapturedVariablesArgumentPostUpdateNode(Cfg::ControlFlowNode callable) { + callable = any(Cfg::CallNode c).getFunction() and + Cfg::isCanonicalAstNodeRepresentative(callable) } or /** A synthetic node representing the values of variables captured by a comprehension. */ TSynthCompCapturedVariablesArgumentNode(Comp comp) { @@ -194,7 +207,7 @@ class Node extends TNode { } /** Gets the control-flow node corresponding to this node, if any. */ - ControlFlowNode asCfgNode() { none() } + Cfg::ControlFlowNode asCfgNode() { none() } /** Gets the expression corresponding to this node, if any. */ Expr asExpr() { none() } @@ -207,14 +220,14 @@ class Node extends TNode { /** A data-flow node corresponding to a control-flow node. */ class CfgNode extends Node, TCfgNode { - ControlFlowNode node; + Cfg::ControlFlowNode node; CfgNode() { this = TCfgNode(node) } - /** Gets the `ControlFlowNode` represented by this data-flow node. */ - ControlFlowNode getNode() { result = node } + /** Gets the `Cfg::ControlFlowNode` represented by this data-flow node. */ + Cfg::ControlFlowNode getNode() { result = node } - override ControlFlowNode asCfgNode() { result = node } + override Cfg::ControlFlowNode asCfgNode() { result = node } /** Gets a textual representation of this element. */ override string toString() { result = node.toString() } @@ -224,9 +237,9 @@ class CfgNode extends Node, TCfgNode { override Location getLocation() { result = node.getLocation() } } -/** A data-flow node corresponding to a `CallNode` in the control-flow graph. */ +/** A data-flow node corresponding to a `Cfg::CallNode` in the control-flow graph. */ class CallCfgNode extends CfgNode, LocalSourceNode { - override CallNode node; + override Cfg::CallNode node; /** * Gets the data-flow node for the function component of the call corresponding to this data-flow @@ -307,15 +320,15 @@ ExprNode exprNode(DataFlowExpr e) { result.getNode().getNode() = e } * as it enters a scope. */ class ScopeEntryDefinitionNode extends Node, TScopeEntryDefinitionNode { - ScopeEntryDefinition def; + SsaImpl::ScopeEntryDefinition def; ScopeEntryDefinitionNode() { this = TScopeEntryDefinitionNode(def) } - /** Gets the `ScopeEntryDefinition` associated with this node. */ - ScopeEntryDefinition getDefinition() { result = def } + /** Gets the `SsaImpl::ScopeEntryDefinition` associated with this node. */ + SsaImpl::ScopeEntryDefinition getDefinition() { result = def } /** Gets the source variable represented by this node. */ - SsaSourceVariable getVariable() { result = def.getSourceVariable() } + SsaImpl::SsaSourceVariable getVariable() { result = def.getSourceVariable() } override Location getLocation() { result = def.getLocation() } @@ -337,7 +350,7 @@ class ParameterNode extends Node instanceof ParameterNodeImpl { /** A parameter node found in the source code (not in a summary). */ class ExtractedParameterNode extends ParameterNodeImpl, CfgNode { //, LocalSourceNode { - ParameterDefinition def; + SsaImpl::ParameterDefinition def; ExtractedParameterNode() { node = def.getDefiningNode() } @@ -368,10 +381,10 @@ Node getCallArgApproximation() { exists(Class c | result.asExpr() = c.getAMethod().getArg(0)) or // the object part of an attribute expression (which might be a bound method) - result.asCfgNode() = any(AttrNode a).getObject() + result.asCfgNode() = any(Cfg::AttrNode a).getObject() or // the function part of any call - result.asCfgNode() = any(CallNode c).getFunction() + result.asCfgNode() = any(Cfg::CallNode c).getFunction() } /** Gets the extracted argument nodes that do not rely on `getCallArg`. */ @@ -380,7 +393,7 @@ private Node implicitArgumentNode() { normalCallArg(_, result, _) or // and self arguments - result.asCfgNode() = any(CallNode c).getFunction().(AttrNode).getObject() + result.asCfgNode() = any(Cfg::CallNode c).getFunction().(Cfg::AttrNode).getObject() or // for comprehensions, we allow the synthetic `iterable` argument result.asExpr() = any(Comp c).getIterable() @@ -485,21 +498,24 @@ class ModuleVariableNode extends Node, TModuleVariableNode { /** Gets a node that reads this variable, excluding reads that happen through `from ... import *`. */ Node getALocalRead() { - result.asCfgNode() = var.getALoad().getAFlowNode() and + result.asCfgNode().getNode() = var.getALoad() and not result.getScope() = mod } - /** Gets an `EssaNode` that corresponds to an assignment of this global variable. */ + /** Gets a CFG node that corresponds to an assignment of this global variable. */ Node getAWrite() { - any(EssaNodeDefinition def).definedBy(var, result.asCfgNode().(DefinitionNode)) + exists(Cfg::NameNode n | + n.defines(var) and + result.asCfgNode() = n + ) } /** Gets the possible values of the variable at the end of import time */ CfgNode getADefiningWrite() { - exists(SsaVariable def | - def = any(SsaVariable ssa_var).getAnUltimateDefinition() and - def.getDefinition() = result.asCfgNode() and - def.getVariable() = var + exists(SsaImpl::EssaVariable def | + def = any(SsaImpl::EssaVariable ssa_var).getAnUltimateDefinition() and + def.getDefinition().(SsaImpl::EssaNodeDefinition).getDefiningNode() = result.asCfgNode() and + def.getSourceVariable().getVariable() = var ) } @@ -516,7 +532,7 @@ private ModuleVariableNode import_star_read(Node n) { overlay[global] pragma[nomagic] private predicate resolved_import_star_module(Module m, string name, Node n) { - exists(NameNode nn | nn = n.asCfgNode() | + exists(Cfg::NameNode nn | nn = n.asCfgNode() | ImportStar::importStarResolvesTo(pragma[only_bind_into](nn), m) and nn.getId() = name ) @@ -574,88 +590,110 @@ class StarPatternElementNode extends Node, TStarPatternElementNode { } /** - * Gets a node that controls whether other nodes are evaluated. + * A node that participates in a conditional split: a CFG node whose + * evaluation outcome (true/false) is used to choose between two + * successor basic blocks. In the new shared CFG, such nodes appear in + * pairs of `isAfterTrue`/`isAfterFalse` annotated CFG nodes. * - * In the base case, this is the last node of `conditionBlock`, and `flipped` is `false`. - * This definition accounts for (short circuting) `and`- and `or`-expressions, as the structure - * of basic blocks will reflect their semantics. + * Users typically obtain a `GuardNode` by casting from a more specific + * Cfg type: `g.(Cfg::CallNode)` for a call-based check, etc. * - * However, in the program - * ```python - * if not is_safe(path): - * return - * ``` - * the last node in the `ConditionBlock` is `not is_safe(path)`. + * This replaces the legacy (pre-shared-CFG) `GuardNode`/`flipped` + * machinery: the shared CFG carries outcome information structurally + * (via `isAfterTrue`/`isAfterFalse`), so no separate polarity field + * is required. + */ +class GuardNode extends Cfg::ControlFlowNode { + GuardNode() { + // This is the canonical (post-order) version of an AST node, and + // some `[true]`/`[false]` variant of the same AST exists. We + // include the canonical node because users identify guards by + // their AST (`g.(Cfg::CallNode)` etc.), and the outcome-tagged + // variants are accessed by `outcomeOfGuard` below. + exists(Cfg::ControlFlowNode outcome | + outcome.getNode() = this.getNode() and + (outcome.isAfterTrue(_) or outcome.isAfterFalse(_)) + ) + or + // Or: this IS one of the outcome-tagged variants, supporting + // users who want to query the split point directly. + this.isAfterTrue(_) + or + this.isAfterFalse(_) + } + + /** Holds if this guard controls block `b` upon evaluating to `branch`. */ + predicate controlsBlock(Cfg::BasicBlock b, boolean branch) { + branch in [true, false] and + exists(Cfg::ControlFlowNode outcomeNode | + outcomeOfGuard(this, outcomeNode, branch) and + outcomeNode.getBasicBlock().dominates(b) + ) + } +} + +/** + * Holds if some execution that arrives at `outcomeNode` corresponds + * to `guard` having evaluated to `branch`. * - * We would like to consider also `is_safe(path)` a guard node, albeit with `flipped` being `true`. - * Thus we recurse through `not`-expressions. + * For a direct guard `if g:`, the outcome node is `g` itself with + * `isAfterTrue`/`isAfterFalse`. For wrapped guards like `not g` or + * `g == True`, the outcome is on the wrapping expression with an + * appropriate polarity transform — we follow those wrappers up the + * AST to find the outermost expression that carries an actual + * `isAfterTrue`/`isAfterFalse` outcome. */ -ControlFlowNode guardNode(ConditionBlock conditionBlock, boolean flipped) { - // Base case: the last node truly does determine which successor is chosen - result = conditionBlock.getLastNode() and - flipped = false +private predicate outcomeOfGuard( + Cfg::ControlFlowNode guard, Cfg::ControlFlowNode outcomeNode, boolean branch +) { + // Base case: the guard itself splits — the outcome node is the + // first node of an outcome BB, with matching outcome label. + // (The shared CFG also marks inner expressions with outcome flags + // for analysis purposes, but only "splitting" nodes — those that + // actually start an outcome BB — are valid guards on their own.) + outcomeNode.getNode() = guard.getNode() and + outcomeNode = outcomeNode.getBasicBlock().firstNode() and + ( + outcomeNode.isAfterTrue(_) and branch = true + or + outcomeNode.isAfterFalse(_) and branch = false + ) or - // Recursive cases: - // if a guard node is a `not`-expression, - // the operand is also a guard node, but with inverted polarity. - exists(UnaryExprNode notNode | - result = notNode.getOperand() and - notNode.getNode().getOp() instanceof Not - | - notNode = guardNode(conditionBlock, flipped.booleanNot()) + // Recursive: `not guard` — same outcome split as `guard`, flipped. + exists(Cfg::UnaryExprNode notNode, boolean notBranch | + notNode.getOperand().getNode() = guard.getNode() and + notNode.getNode().getOp() instanceof Not and + outcomeOfGuard(notNode, outcomeNode, notBranch) and + branch = notBranch.booleanNot() ) or - // if a guard node is compared to a boolean literal, - // the other operand is also a guard node, - // but with polarity depending on the literal (and on the comparison). - exists(CompareNode cmpNode, Cmpop op, ControlFlowNode b, boolean should_flip | + // Recursive: comparisons against a boolean literal. + exists( + Cfg::CompareNode cmpNode, Cmpop op, Cfg::ControlFlowNode otherOperand, + Cfg::ControlFlowNode guardOperand, boolean polarity, boolean cmpBranch + | + guardOperand.getNode() = guard.getNode() and ( - cmpNode.operands(result, op, b) or - cmpNode.operands(b, op, result) + cmpNode.operands(guardOperand, op, otherOperand) or + cmpNode.operands(otherOperand, op, guardOperand) ) and - not result.getNode() instanceof BooleanLiteral and + not guard.getNode() instanceof BooleanLiteral and ( - // comparing to the boolean (op instanceof Eq or op instanceof Is) and - // we should flip if the value compared against, here the value of `b`, is false - should_flip = b.getNode().(BooleanLiteral).booleanValue().booleanNot() + polarity = otherOperand.getNode().(BooleanLiteral).booleanValue() or - // comparing to the negation of the boolean (op instanceof NotEq or op instanceof IsNot) and - // again, we should flip if the value compared against, here the value of `not b`, is false. - // That is, if the value of `b` is true. - should_flip = b.getNode().(BooleanLiteral).booleanValue() - ) - | - // we flip `flipped` according to `should_flip` via the formula `flipped xor should_flip`. - flipped in [true, false] and - cmpNode = guardNode(conditionBlock, flipped.booleanXor(should_flip)) + polarity = otherOperand.getNode().(BooleanLiteral).booleanValue().booleanNot() + ) and + outcomeOfGuard(cmpNode, outcomeNode, cmpBranch) and + branch = cmpBranch.booleanXor(polarity.booleanNot()) ) } -/** - * A node that controls whether other nodes are evaluated. - * - * The field `flipped` allows us to match `GuardNode`s underneath - * `not`-expressions and still choose the appropriate branch. - */ -class GuardNode extends ControlFlowNode { - ConditionBlock conditionBlock; - boolean flipped; - - GuardNode() { this = guardNode(conditionBlock, flipped) } - - /** Holds if this guard controls block `b` upon evaluating to `branch`. */ - predicate controlsBlock(BasicBlock b, boolean branch) { - branch in [true, false] and - conditionBlock.controls(b, branch.booleanXor(flipped)) - } -} - /** * Holds if the guard `g` validates `node` upon evaluating to `branch`. */ -signature predicate guardChecksSig(GuardNode g, ControlFlowNode node, boolean branch); +signature predicate guardChecksSig(GuardNode g, Cfg::ControlFlowNode node, boolean branch); /** * Provides a set of barrier nodes for a guard that validates a node. @@ -670,7 +708,9 @@ module BarrierGuard { result = ParameterizedBarrierGuard::getABarrierNode(_) } - private predicate extendedGuardChecks(GuardNode g, ControlFlowNode node, boolean branch, Unit u) { + private predicate extendedGuardChecks( + GuardNode g, Cfg::ControlFlowNode node, boolean branch, Unit u + ) { guardChecks(g, node, branch) and u = u } @@ -680,7 +720,7 @@ bindingset[this] private signature class ParamSig; private module WithParam { - signature predicate guardChecksSig(GuardNode g, ControlFlowNode node, boolean branch, P param); + signature predicate guardChecksSig(GuardNode g, Cfg::ControlFlowNode node, boolean branch, P param); } /** @@ -693,10 +733,10 @@ module ParameterizedBarrierGuard::guardChecksSig/4 guar /** Gets a node that is safely guarded by the given guard check with parameter `param`. */ overlay[global] ExprNode getABarrierNode(P param) { - exists(GuardNode g, EssaDefinition def, ControlFlowNode node, boolean branch | - AdjacentUses::useOfDef(def, node) and + exists(GuardNode g, SsaImpl::EssaDefinition def, Cfg::ControlFlowNode node, boolean branch | + SsaImpl::AdjacentUses::useOfDef(def, node) and guardChecks(g, node, branch, param) and - AdjacentUses::useOfDef(def, result.asCfgNode()) and + SsaImpl::AdjacentUses::useOfDef(def, result.asCfgNode()) and g.controlsBlock(result.asCfgNode().getBasicBlock(), branch) ) } @@ -712,7 +752,7 @@ module ExternalBarrierGuard { private import semmle.python.ApiGraphs overlay[global] - private predicate guardCheck(GuardNode g, ControlFlowNode node, boolean branch, string kind) { + private predicate guardCheck(GuardNode g, Cfg::ControlFlowNode node, boolean branch, string kind) { exists(API::CallNode call, API::Node parameter | parameter = call.getAParameter() and parameter = ModelOutput::getABarrierGuardNode(kind, branch) @@ -748,10 +788,10 @@ newtype TContent = TSetElementContent() or /** An element of a tuple at a specific index. */ TTupleElementContent(int index) { - exists(any(TupleNode tn).getElement(index)) + exists(any(Cfg::TupleNode tn).getElement(index)) or // Arguments can overflow and end up in the starred parameter tuple. - exists(any(CallNode cn).getArg(index)) + exists(any(Cfg::CallNode cn).getArg(index)) or // since flow summaries might use tuples, we ensure that we at least have valid // TTupleElementContent for the 0..7 (7 was picked to match `small_tuple` in @@ -768,10 +808,14 @@ newtype TContent = or // d["key"] = ... key = - any(SubscriptNode sub | sub.isStore() | sub.getIndex().getNode().(StringLiteral).getText()) + any(Cfg::SubscriptNode sub | + sub.isStore() + | + sub.getIndex().getNode().(StringLiteral).getText() + ) or // d.setdefault("key", ...) - exists(CallNode call | call.getFunction().(AttrNode).getName() = "setdefault" | + exists(Cfg::CallNode call | call.getFunction().(Cfg::AttrNode).getName() = "setdefault" | key = call.getArg(0).getNode().(StringLiteral).getText() ) } or diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/ImportResolution.qll b/python/ql/lib/semmle/python/dataflow/new/internal/ImportResolution.qll index f3943f53f860..735a7e831bd0 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/ImportResolution.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/ImportResolution.qll @@ -5,11 +5,24 @@ */ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg +private import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl private import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.internal.ImportStar private import semmle.python.dataflow.new.TypeTracking private import semmle.python.dataflow.new.internal.DataFlowPrivate -private import semmle.python.essa.SsaDefinitions + +/** + * Holds if the name of `var` refers to a submodule of a package and `init` is + * the `__init__` module of that package. Locally inlined replacement for the + * legacy `SsaSource::init_module_submodule_defn` so that this module has no + * direct dependency on `semmle.python.essa.SsaDefinitions`. + */ +private predicate initModuleSubmoduleDefn(GlobalVariable var, Module init) { + init.isPackageInit() and + exists(init.getPackage().getSubModule(var.getId())) and + var.getScope() = init +} /** * Python modules and the way imports are resolved are... complicated. Here's a crash course in how @@ -69,13 +82,13 @@ module ImportResolution { * Holds if there is an ESSA step from `defFrom` to `defTo`, which should be allowed * for import resolution. */ - private predicate allowedEssaImportStep(EssaDefinition defFrom, EssaDefinition defTo) { + private predicate allowedEssaImportStep( + SsaImpl::EssaDefinition defFrom, SsaImpl::EssaDefinition defTo + ) { // to handle definitions guarded by if-then-else - defFrom = defTo.(PhiFunction).getAnInput() - or - // refined variable - // example: https://github.com/nvbn/thefuck/blob/ceeaeab94b5df5a4fe9d94d61e4f6b0bbea96378/thefuck/utils.py#L25-L45 - defFrom = defTo.(EssaNodeRefinement).getInput().getDefinition() + defFrom = defTo.(SsaImpl::PhiFunction).getAnInput() + // Note: legacy ESSA refinement-step (e.g. for `foo.bar = X`) is + // not modelled in the new SSA. We rely on phi steps only. } /** @@ -92,30 +105,32 @@ module ImportResolution { // Definitions made inside `m` itself // // for code such as `foo = ...; foo.bar = ...` there will be TWO - // EssaDefinition/EssaVariable. One for `foo = ...` (AssignmentDefinition) and one + // SsaImpl::EssaDefinition/SsaImpl::EssaVariable. One for `foo = ...` (SsaImpl::AssignmentDefinition) and one // for `foo.bar = ...`. The one for `foo.bar = ...` (EssaNodeRefinement). The // EssaNodeRefinement is the one that will reach the end of the module (normal // exit). // // However, we cannot just use the EssaNodeRefinement as the `val`, because the // normal data-flow depends on use-use flow, and use-use flow targets CFG nodes not - // EssaNodes. So we need to go back from the EssaDefinition/EssaVariable that + // EssaNodes. So we need to go back from the SsaImpl::EssaDefinition/SsaImpl::EssaVariable that // reaches the end of the module, to the first definition of the variable, and then // track forwards using use-use flow to find a suitable CFG node that has flow into // it from use-use flow. - exists(EssaVariable lastUseVar, EssaVariable firstDef | + exists(SsaImpl::EssaVariable lastUseVar, SsaImpl::EssaVariable firstDef | lastUseVar.getName() = name and // we ignore special variable $ introduced by our analysis (not used for anything) // we ignore special variable * introduced by `from import *` -- TODO: understand why we even have this? not name in ["$", "*"] and - lastUseVar.getAUse() = m.getANormalExit() and + exists(Cfg::ControlFlowNode exit | + exit.isNormalExit() and exit.getScope() = m and lastUseVar.getAUse() = exit + ) and allowedEssaImportStep*(firstDef, lastUseVar) and not allowedEssaImportStep(_, firstDef) | not LocalFlow::defToFirstUse(firstDef, _) and - val.asCfgNode() = firstDef.getDefinition().(EssaNodeDefinition).getDefiningNode() + val.asCfgNode() = firstDef.getDefinition().(SsaImpl::EssaNodeDefinition).getDefiningNode() or - exists(ControlFlowNode mid, ControlFlowNode end | + exists(Cfg::ControlFlowNode mid, Cfg::ControlFlowNode end | LocalFlow::defToFirstUse(firstDef, mid) and LocalFlow::useToNextUse*(mid, end) and not LocalFlow::useToNextUse(end, _) and @@ -143,9 +158,9 @@ module ImportResolution { * handles simple cases where we can statically tell that this is the case. */ private predicate all_mentions_name(Module m, string name) { - exists(DefinitionNode def, SequenceNode n | + exists(Cfg::DefinitionNode def, Cfg::SequenceNode n | def.getValue() = n and - def.(NameNode).getId() = "__all__" and + def.(Cfg::NameNode).getId() = "__all__" and def.getScope() = m and any(StringLiteral s | s.getText() = name) = n.getAnElement().getNode() ) @@ -158,18 +173,20 @@ module ImportResolution { */ private predicate no_or_complicated_all(Module m) { // No mention of `__all__` in the module - not exists(DefinitionNode def | def.getScope() = m and def.(NameNode).getId() = "__all__") + not exists(Cfg::DefinitionNode def | + def.getScope() = m and def.(Cfg::NameNode).getId() = "__all__" + ) or // `__all__` is set to a non-sequence value - exists(DefinitionNode def | - def.(NameNode).getId() = "__all__" and + exists(Cfg::DefinitionNode def | + def.(Cfg::NameNode).getId() = "__all__" and def.getScope() = m and - not def.getValue() instanceof SequenceNode + not def.getValue() instanceof Cfg::SequenceNode ) or // `__all__` is used in some way that doesn't involve storing a value in it. This usually means // it is being mutated through `append` or `extend`, which we don't handle. - exists(NameNode n | n.getId() = "__all__" and n.getScope() = m and n.isLoad()) + exists(Cfg::NameNode n | n.getId() = "__all__" and n.getScope() = m and n.isLoad()) } private predicate potential_module_export(Module m, string name) { @@ -177,7 +194,7 @@ module ImportResolution { or no_or_complicated_all(m) and ( - exists(NameNode n | n.getId() = name and n.getScope() = m and name.charAt(0) != "_") + exists(Cfg::NameNode n | n.getId() = name and n.getScope() = m and name.charAt(0) != "_") or exists(Alias a | a.getAsname().(Name).getId() = name and a.getValue().getScope() = m) ) @@ -207,12 +224,12 @@ module ImportResolution { /** Gets a module that may have been added to `sys.modules`. */ private Module sys_modules_module_with_name(string name) { - exists(ControlFlowNode n, DataFlow::Node mod | - exists(SubscriptNode sub | + exists(Cfg::ControlFlowNode n, DataFlow::Node mod | + exists(Cfg::SubscriptNode sub | sub.getObject() = sys_modules_reference().asCfgNode() and sub.getIndex() = n and n.getNode().(StringLiteral).getText() = name and - sub.(DefinitionNode).getValue() = mod.asCfgNode() and + sub.(Cfg::DefinitionNode).getValue() = mod.asCfgNode() and mod = getModuleReference(result) ) ) @@ -324,11 +341,11 @@ module ImportResolution { // name as a submodule, we always consider that this attribute _could_ be a // reference to the submodule, even if we don't know that the submodule has been // imported yet. - exists(string submodule, Module package, EssaVariable var | + exists(string submodule, Module package, SsaImpl::EssaVariable var | submodule = var.getName() and - SsaSource::init_module_submodule_defn(var.getSourceVariable(), package.getEntryNode()) and + initModuleSubmoduleDefn(var.getSourceVariable().getVariable(), package) and m = getModuleFromName(package.getPackageName() + "." + submodule) and - result.asCfgNode() = var.getDefinition().(EssaNodeDefinition).getDefiningNode() + result.asCfgNode() = var.getDefinition().(SsaImpl::EssaNodeDefinition).getDefiningNode() ) } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/ImportStar.qll b/python/ql/lib/semmle/python/dataflow/new/internal/ImportStar.qll index 83f8ee862c39..8c906696be7a 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/ImportStar.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/ImportStar.qll @@ -3,6 +3,7 @@ overlay[local] module; private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.internal.Builtins private import semmle.python.dataflow.new.internal.ImportResolution private import semmle.python.dataflow.new.DataFlow @@ -15,7 +16,7 @@ module ImportStar { */ overlay[local] cached - predicate namePossiblyDefinedInImportStar(NameNode n, string name, Scope s) { + predicate namePossiblyDefinedInImportStar(Cfg::NameNode n, string name, Scope s) { n.isLoad() and name = n.getId() and s = n.getScope().getEnclosingScope*() and @@ -52,7 +53,7 @@ module ImportStar { /** Holds if a global variable called `name` is assigned a value in the module `m`. */ cached predicate globalNameDefinedInModule(string name, Module m) { - exists(NameNode n | + exists(Cfg::NameNode n | not exists(LocalVariable v | n.defines(v)) and n.isStore() and name = n.getId() and @@ -66,7 +67,7 @@ module ImportStar { */ overlay[global] cached - predicate importStarResolvesTo(NameNode n, Module m) { + predicate importStarResolvesTo(Cfg::NameNode n, Module m) { m = getStarImported+(n.getEnclosingModule()) and globalNameDefinedInModule(n.getId(), m) and not isDefinedLocally(n.getNode()) @@ -99,7 +100,7 @@ module ImportStar { */ overlay[local] cached - ControlFlowNode potentialImportStarBase(Scope s) { - result = any(ImportStarNode n | n.getScope() = s).getModule() + Cfg::ControlFlowNode potentialImportStarBase(Scope s) { + result = any(Cfg::ImportStarNode n | n.getScope() = s).getModule() } } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/IterableUnpacking.qll b/python/ql/lib/semmle/python/dataflow/new/internal/IterableUnpacking.qll index 5def15fa3c8a..ac7200115ce2 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/IterableUnpacking.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/IterableUnpacking.qll @@ -170,6 +170,8 @@ overlay[local] module; private import python +private import semmle.python.controlflow.internal.Cfg as Cfg +private import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl private import DataFlowPublic /** @@ -178,7 +180,7 @@ private import DataFlowPublic * This class abstracts away the differing representations of comprehensions and * for statements. */ -class ForTarget extends ControlFlowNode { +class ForTarget extends Cfg::ControlFlowNode { Expr source; ForTarget() { @@ -198,7 +200,7 @@ class ForTarget extends ControlFlowNode { } /** The LHS of an assignment, it also records the assigned value. */ -class AssignmentTarget extends ControlFlowNode { +class AssignmentTarget extends Cfg::ControlFlowNode { Expr value; AssignmentTarget() { @@ -209,7 +211,7 @@ class AssignmentTarget extends ControlFlowNode { } /** A direct (or top-level) target of an unpacking assignment. */ -class UnpackingAssignmentDirectTarget extends ControlFlowNode instanceof SequenceNode { +class UnpackingAssignmentDirectTarget extends Cfg::ControlFlowNode instanceof Cfg::SequenceNode { Expr value; UnpackingAssignmentDirectTarget() { @@ -222,7 +224,7 @@ class UnpackingAssignmentDirectTarget extends ControlFlowNode instanceof Sequenc } /** A (possibly recursive) target of an unpacking assignment. */ -class UnpackingAssignmentTarget extends ControlFlowNode { +class UnpackingAssignmentTarget extends Cfg::ControlFlowNode { UnpackingAssignmentTarget() { this instanceof UnpackingAssignmentDirectTarget or @@ -231,10 +233,11 @@ class UnpackingAssignmentTarget extends ControlFlowNode { } /** A (possibly recursive) target of an unpacking assignment which is also a sequence. */ -class UnpackingAssignmentSequenceTarget extends UnpackingAssignmentTarget instanceof SequenceNode { - ControlFlowNode getElement(int i) { result = super.getElement(i) } +class UnpackingAssignmentSequenceTarget extends UnpackingAssignmentTarget instanceof Cfg::SequenceNode +{ + Cfg::ControlFlowNode getElement(int i) { result = super.getElement(i) } - ControlFlowNode getAnElement() { result = this.getElement(_) } + Cfg::ControlFlowNode getAnElement() { result = this.getElement(_) } } /** @@ -255,7 +258,7 @@ predicate iterableUnpackingAssignmentFlowStep(Node nodeFrom, Node nodeTo) { predicate iterableUnpackingForReadStep(CfgNode nodeFrom, Content c, Node nodeTo) { exists(ForTarget target | nodeFrom.getNode().getNode() = target.getSource() and - target instanceof SequenceNode and + target instanceof Cfg::SequenceNode and nodeTo = TIterableSequenceNode(target) ) and ( @@ -323,11 +326,11 @@ predicate iterableUnpackingConvertingStoreStep(Node nodeFrom, Content c, Node no */ predicate iterableUnpackingElementReadStep(Node nodeFrom, Content c, Node nodeTo) { exists( - UnpackingAssignmentSequenceTarget target, int index, ControlFlowNode element, int starIndex + UnpackingAssignmentSequenceTarget target, int index, Cfg::ControlFlowNode element, int starIndex | - target.getElement(starIndex) instanceof StarredNode + target.getElement(starIndex) instanceof Cfg::StarredNode or - not exists(target.getAnElement().(StarredNode)) and + not exists(target.getAnElement().(Cfg::StarredNode)) and starIndex = -1 | nodeFrom.(CfgNode).getNode() = target and @@ -342,18 +345,18 @@ predicate iterableUnpackingElementReadStep(Node nodeFrom, Content c, Node nodeTo else c.(TupleElementContent).getIndex() >= index - 1 ) and ( - if element instanceof SequenceNode + if element instanceof Cfg::SequenceNode then // Step 5b nodeTo = TIterableSequenceNode(element) else - if element instanceof StarredNode + if element instanceof Cfg::StarredNode then // Step 5c nodeTo = TIterableElementNode(element) else // Step 5a - exists(MultiAssignmentDefinition mad | element = mad.getDefiningNode() | + exists(SsaImpl::MultiAssignmentDefinition mad | element = mad.getDefiningNode() | nodeTo.(CfgNode).getNode() = element ) ) @@ -366,7 +369,7 @@ predicate iterableUnpackingElementReadStep(Node nodeFrom, Content c, Node nodeTo * content type `ListElementContent`. */ predicate iterableUnpackingStarredElementStoreStep(Node nodeFrom, Content c, Node nodeTo) { - exists(ControlFlowNode starred, MultiAssignmentDefinition mad | + exists(Cfg::ControlFlowNode starred, SsaImpl::MultiAssignmentDefinition mad | starred.getNode() instanceof Starred and starred = mad.getDefiningNode() | diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/LocalSources.qll b/python/ql/lib/semmle/python/dataflow/new/internal/LocalSources.qll index 5cbe7b44ab30..9f63e2160ed8 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/LocalSources.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/LocalSources.qll @@ -9,6 +9,7 @@ overlay[local] module; private import python +private import semmle.python.controlflow.internal.Cfg as Cfg import DataFlowPublic private import DataFlowPrivate private import semmle.python.internal.CachedStages @@ -314,7 +315,7 @@ private module Cached { */ cached predicate subscript(LocalSourceNode node, CfgNode subscript, CfgNode index) { - exists(CfgNode seq, SubscriptNode subscriptNode | subscriptNode = subscript.getNode() | + exists(CfgNode seq, Cfg::SubscriptNode subscriptNode | subscriptNode = subscript.getNode() | node.flowsTo(seq) and seq.getNode() = subscriptNode.getObject() and index.getNode() = subscriptNode.getIndex() diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/MatchUnpacking.qll b/python/ql/lib/semmle/python/dataflow/new/internal/MatchUnpacking.qll index e72e378da528..f931c4606034 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/MatchUnpacking.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/MatchUnpacking.qll @@ -55,6 +55,7 @@ module; private import python private import DataFlowPublic +private import semmle.python.controlflow.internal.Cfg as Cfg /** * Holds when there is flow from the subject `nodeFrom` to the (top-level) pattern `nodeTo` of a `match` statement. @@ -91,8 +92,8 @@ predicate matchAsFlowStep(Node nodeFrom, Node nodeTo) { or // the interior pattern flows to the alias nodeFrom.(CfgNode).getNode().getNode() = subject.getPattern() and - exists(PatternAliasDefinition pad | pad.getDefiningNode().getNode() = alias | - nodeTo.(CfgNode).getNode() = pad.getDefiningNode() + exists(Cfg::ControlFlowNode aliasCfg | aliasCfg.getNode() = alias | + nodeTo.(CfgNode).getNode() = aliasCfg ) ) } @@ -126,8 +127,8 @@ predicate matchLiteralFlowStep(Node nodeFrom, Node nodeTo) { predicate matchCaptureFlowStep(Node nodeFrom, Node nodeTo) { exists(MatchCapturePattern capture, Name var | capture.getVariable() = var | nodeFrom.(CfgNode).getNode().getNode() = capture and - exists(PatternCaptureDefinition pcd | pcd.getDefiningNode().getNode() = var | - nodeTo.(CfgNode).getNode() = pcd.getDefiningNode() + exists(Cfg::ControlFlowNode varCfg | varCfg.getNode() = var | + nodeTo.(CfgNode).getNode() = varCfg ) ) } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/SsaImpl.qll b/python/ql/lib/semmle/python/dataflow/new/internal/SsaImpl.qll new file mode 100644 index 000000000000..d9609599a583 --- /dev/null +++ b/python/ql/lib/semmle/python/dataflow/new/internal/SsaImpl.qll @@ -0,0 +1,547 @@ +/** + * Provides the Python SSA implementation built on the new (shared) CFG. + * + * Mirrors the Java SSA adapter at + * `java/ql/lib/semmle/code/java/dataflow/internal/SsaImpl.qll`: + * an `InputSig` is defined in terms of positional `(BasicBlock, int)` + * variable references, and the shared + * `codeql.ssa.Ssa::Make` module is then + * instantiated. + * + * `SourceVariable` is the AST-level `Py::Variable`. Variable references + * are looked up via the CFG facade's `NameNode.defines`/`uses`/`deletes` + * predicates, which themselves are one-line bridges to AST-level + * `Name.defines`/`uses`/`deletes`. + * + * Implicit-entry definitions are inserted for: + * - non-local / global / builtin variables that are read in the scope + * but never assigned (no enclosing CFG node defines them), + * - captured variables (variables defined in an enclosing scope that + * are read inside the scope), and + * - parameters, but only if the corresponding parameter name is *not* + * itself a CFG node. With the C#-style parameter wiring already + * installed in `AstNodeImpl.qll`, parameter names *are* CFG nodes, + * so the regular `variableWrite` path handles them — no `i = -1` + * entry is needed for ordinary parameters. + */ +overlay[local?] +module; + +private import python as Py +private import semmle.python.controlflow.internal.AstNodeImpl as CfgImpl +private import semmle.python.controlflow.internal.Cfg as Cfg +private import codeql.ssa.Ssa as SsaImplCommon +private import codeql.controlflow.BasicBlock as BB + +/** + * Adapts the Python `Cfg` facade to the shared SSA library's `CfgSig`. + * All members are inherited from `Cfg::ControlFlowNode` and + * `Cfg::BasicBlock`. + */ +private module CfgForSsa implements BB::CfgSig { + class ControlFlowNode = CfgImpl::ControlFlowNode; + + class BasicBlock = CfgImpl::BasicBlock; + + class EntryBasicBlock = CfgImpl::Cfg::EntryBasicBlock; + + predicate dominatingEdge = CfgImpl::Cfg::dominatingEdge/2; +} + +/** + * A source variable for SSA, wrapping a Python AST `Variable`. + * + * We only track variables that are read at least once in their scope — + * tracking write-only variables would be unnecessary work — *except* + * for module-scope globals, where the "read" can be external (e.g. + * `import mymodule; mymodule.x`). Such globals are tracked + * unconditionally so that import-resolution can find their defining + * write. + */ +private newtype TSsaSourceVariable = + TPyVar(Py::Variable v) { + // Has a use somewhere — read-relevant for SSA. + exists(Cfg::NameNode n | n.uses(v)) + or + // Or has a deletion (treated as a write that destroys the value). + exists(Cfg::NameNode n | n.deletes(v)) + or + // Or is a module-scope global written in this module — must be + // tracked even if never read locally, because importers may read + // it as an attribute on the module object. + v.getScope() instanceof Py::Module and + exists(Cfg::NameNode n | n.defines(v)) + or + // Or is a parameter — parameters must always have a + // `ParameterDefinition` for dataflow argument-routing to work, + // even if the parameter is never read in its scope. Mirrors + // legacy ESSA's `ParameterDefinition` (which fired for every + // parameter binding regardless of liveness). + exists(Py::Parameter p | p.asName() = v.getAStore()) + } + +/** + * A source variable for SSA, wrapping a Python AST `Variable`. + */ +class SsaSourceVariable extends TSsaSourceVariable { + /** Gets the underlying Python AST variable. */ + Py::Variable getVariable() { this = TPyVar(result) } + + /** Gets the (textual) name of this variable. */ + string getName() { result = this.getVariable().getId() } + + /** Gets a textual representation of this source variable. */ + string toString() { result = this.getVariable().toString() } + + /** Gets the location of this source variable. */ + Py::Location getLocation() { result = this.getVariable().getScope().getLocation() } + + /** Gets the scope in which this variable lives. */ + Py::Scope getScope() { result = this.getVariable().getScope() } + + /** + * Gets a use of this variable as it appears in the source — a `NameNode` + * that loads or deletes the variable. Mirrors legacy + * `SsaSourceVariable.getASourceUse()`. + */ + Cfg::ControlFlowNode getASourceUse() { + exists(Cfg::NameNode n | result = n | + n.uses(this.getVariable()) or n.deletes(this.getVariable()) + ) + } + + /** + * Gets an implicit use of this variable. The new SSA does not have + * implicit-use refinements, but we keep this for API parity — every + * normal-exit of the variable's scope counts as a sink, ensuring + * variables stay live to scope exit for taint-tracking. + */ + Cfg::ControlFlowNode getAnImplicitUse() { + result.isNormalExit() and result.getScope() = this.getScope() + } + + /** + * Gets a use of this variable — either an explicit source use or an + * implicit use at scope exit. Mirrors legacy `SsaSourceVariable.getAUse()`. + */ + Cfg::ControlFlowNode getAUse() { + result = this.getASourceUse() or result = this.getAnImplicitUse() + } +} + +/** + * Holds if `v` is a non-local read in scope `s`, in the sense that `s` + * uses `v` but does not write it within `s`. This includes globals, + * builtins, and variables captured from an enclosing function scope. + * + * The `Py::Variable` `v` lives in some defining scope (the module for + * globals, an outer function for closures, etc.); the reading scope + * `s` is the scope where the use of `v` occurs. + */ +private predicate nonLocalReadIn(Py::Variable v, Py::Scope s) { + exists(Cfg::NameNode n | + n.uses(v) and + n.getScope() = s and + not exists(Cfg::NameNode def | def.defines(v) and def.getScope() = s) + ) and + // Match legacy ESSA: only create entry defs for variables that have + // at least one defining store somewhere — otherwise the entry def + // represents "nothing reaches here", which is the default anyway and + // introduces no useful flow. (Legacy's `ModuleVariable` required a + // store; this is the closure-aware generalisation.) + exists(Cfg::NameNode store | store.defines(v)) +} + +/** + * Holds if `bb` is the entry basic block of a scope where `v` should + * have an implicit entry definition. This covers: + * - non-local / global / builtin variables read in `s`, and + * - captured variables (defined in an enclosing scope but read in `s`). + * + * Each reading scope gets its own entry def, so a closure variable can + * have multiple entry defs across all functions/methods that read it. + * + * Parameters are *not* included: their bound `Name` is itself a CFG + * node (per the C#-style parameter wiring), so `variableWrite` fires at + * the parameter's natural CFG index. + */ +private predicate hasEntryDefIn(SsaSourceVariable v, CfgImpl::BasicBlock bb) { + exists(Py::Scope s | + nonLocalReadIn(v.getVariable(), s) and + bb = entryBlock(s) + ) +} + +/** + * Gets the entry basic block of scope `s`, where implicit entry + * definitions are placed (at synthetic index `-1`). + */ +private CfgImpl::BasicBlock entryBlock(Py::Scope s) { + exists(CfgImpl::ControlFlowNode entry | + entry instanceof CfgImpl::ControlFlow::EntryNode and + entry.getEnclosingCallable().asScope() = s and + result = entry.getBasicBlock() + ) +} + +/** + * The SSA `InputSig` for Python. References are positional + * `(BasicBlock, int)` pairs into the new CFG. + */ +private module SsaImplInput implements SsaImplCommon::InputSig { + class SourceVariable = SsaSourceVariable; + + predicate variableWrite(CfgImpl::BasicBlock bb, int i, SourceVariable v, boolean certain) { + // Explicit binding at a CFG node — includes assignments, + // parameter Names (wired in via the C# pattern), exception-handler + // `as`-bindings, import aliases, and match-pattern captures. + exists(Cfg::NameNode n | + bb.getNode(i) = n and + n.defines(v.getVariable()) and + certain = true + ) + or + // `del x` — removes the binding. Modelled as a certain write that + // makes any subsequent read invalid. + exists(Cfg::NameNode n | + bb.getNode(i) = n and + n.deletes(v.getVariable()) and + certain = true + ) + or + // Implicit entry definition for non-local / captured / global / + // builtin variables read in some scope. Each reading scope's entry + // block gets one such write, allowing closures: e.g. when `x` is a + // parameter of an outer function and read inside a nested + // function, both scopes get entry defs for `x`. + hasEntryDefIn(v, bb) and + i = -1 and + certain = true + or + // `from X import *` — possibly rebinds every name in the importing + // scope. Modelled as an uncertain write at the import-star's CFG + // position for every variable that lives in (or is referenced + // from) the same scope as the import-star. Mirrors legacy ESSA's + // `ImportStarRefinement` (see `essa/SsaDefinitions.qll`'s + // `import_star_refinement` predicate). The write is uncertain so + // that prior definitions of the variable remain available — the + // shared-SSA `SsaUncertainWrite` merges the new value with the + // immediately preceding definition. + exists(Cfg::ImportStarNode imp | + bb.getNode(i) = imp and + certain = false and + ( + v.getVariable().getScope() = imp.getScope() + or + // Variable is defined in some other scope but referenced in + // the same scope as the import-star (matches legacy clause 2: + // `other.uses(v) and def.getScope() = other.getScope()`). + exists(Cfg::NameNode other | + other.uses(v.getVariable()) and + imp.getScope() = other.getScope() + ) + ) + ) + } + + predicate variableRead(CfgImpl::BasicBlock bb, int i, SourceVariable v, boolean certain) { + // Explicit source use — a `Name` load or a `del x` of the variable. + exists(Cfg::NameNode n | + bb.getNode(i) = n and + n.uses(v.getVariable()) and + certain = true + ) + or + // Synthetic use at the normal exit of the variable's defining scope. + // This keeps every variable live to scope exit so that callers (e.g. + // `module_export` in ImportResolution.qll, or taint-tracking pass-through + // through unread locals) can ask "which definition reaches end of + // scope?". Mirrors legacy ESSA's `SsaSourceVariable.getAUse()` which + // included `getScope().getANormalExit()`. + exists(Cfg::ControlFlowNode exit | + exit.isNormalExit() and + exit.getScope() = v.getVariable().getScope() and + bb.getNode(i) = exit and + certain = true + ) + } +} + +/** + * The shared SSA instantiation for Python. + * + * Members: + * - `Definition` — the union of explicit, uncertain, and phi definitions + * - `WriteDefinition`, `UncertainWriteDefinition`, `PhiNode` + * - the standard SSA predicates (`getAUse`, `getAnUltimateDefinition`, ...). + */ +module Ssa = SsaImplCommon::Make; + +final class Definition = Ssa::Definition; + +final class WriteDefinition = Ssa::WriteDefinition; + +final class UncertainWriteDefinition = Ssa::UncertainWriteDefinition; + +final class PhiNode = Ssa::PhiNode; + +// =========================================================================== +// ESSA-shaped adapter layer +// +// The dataflow library (`python/ql/lib/semmle/python/dataflow/new/`) and +// related modules (`ApiGraphs.qll`, etc.) consume the legacy ESSA API +// (`EssaVariable`, `EssaDefinition`, `AssignmentDefinition`, +// `ScopeEntryDefinition`, `ParameterDefinition`, `WithDefinition`, +// `PhiFunction`, plus the `AdjacentUses` module). To migrate them off +// the legacy CFG, we expose the same API surface on top of the +// shared SSA built above. +// +// This adapter is intentionally narrow: it covers only the predicates +// that new dataflow consumes. The richer legacy ESSA — refinement +// nodes, attribute refinements, edge refinements — stays available +// via `semmle.python.essa.Essa` for points-to / legacy code. +// =========================================================================== +/** + * Gets the CFG node at which a write definition's binding takes place. + * + * For ordinary writes (assignment, deletion, parameter) this is the + * canonical CFG node of the bound Name. For implicit entry definitions + * (synthesised at position `-1` of a scope's entry BB) this is the + * scope's entry node. + */ +private Cfg::ControlFlowNode writeDefNode(Ssa::WriteDefinition def) { + exists(CfgImpl::BasicBlock bb, int i | def.definesAt(_, bb, i) | + i >= 0 and result = bb.getNode(i) + or + i = -1 and result = bb.getNode(0) + ) +} + +/** + * A write definition whose binding has a corresponding CFG node — i.e. + * everything that's not a phi node. Mirrors legacy ESSA's + * `EssaNodeDefinition`. + */ +class EssaNodeDefinition extends Ssa::WriteDefinition { + /** Gets the CFG node where this definition's binding takes place. */ + Cfg::ControlFlowNode getDefiningNode() { result = writeDefNode(this) } + + /** Gets the variable defined here (legacy name). */ + SsaSourceVariable getVariable() { result = this.getSourceVariable() } + + /** Gets the enclosing scope. */ + Py::Scope getScope() { + exists(Cfg::ControlFlowNode n | n = this.getDefiningNode() | result = n.getScope()) + } + + /** + * Holds if this definition defines source variable `v` at CFG node + * `defNode`. Flatter form of `getSourceVariable()` + + * `getDefiningNode()`, matching legacy ESSA's `definedBy`. + */ + predicate definedBy(SsaSourceVariable v, Cfg::ControlFlowNode defNode) { + v = this.getSourceVariable() and defNode = this.getDefiningNode() + } +} + +/** + * An assignment definition: any binding where the value being assigned + * is statically known via `Cfg::DefinitionNode.getValue()`. Includes + * plain assignments, walrus, annotated assignments, augmented + * assignments, import aliases (`import x` / `from m import x [as y]`), + * `with ... as x`, and for-target bindings (where `getValue()` returns + * the iter expression's CFG node). Excludes parameter bindings — + * those are modelled by `ParameterDefinition`. + */ +class AssignmentDefinition extends EssaNodeDefinition { + AssignmentDefinition() { + exists(Cfg::NameNode n | n = this.getDefiningNode() | + exists(n.(Cfg::DefinitionNode).getValue()) and + not n.(Cfg::ControlFlowNode).isParameter() + ) + } + + /** Gets the CFG node for the value being assigned, if statically known. */ + Cfg::ControlFlowNode getValue() { + result = this.getDefiningNode().(Cfg::DefinitionNode).getValue() + } +} + +/** + * A parameter definition — the binding of a parameter name in a + * function's scope. + */ +class ParameterDefinition extends EssaNodeDefinition { + ParameterDefinition() { this.getDefiningNode().isParameter() } + + /** Gets the AST `Parameter` (a `Py::Name` in param context). */ + Py::Name getParameter() { result = this.getDefiningNode().getNode() } +} + +/** + * A definition introduced by a `with ... as x:` clause. + */ +class WithDefinition extends EssaNodeDefinition { + WithDefinition() { + exists(Cfg::NameNode n, Py::With w | + n = this.getDefiningNode() and + w.getOptionalVars() = n.getNode() + ) + } +} + +/** + * An assignment where the LHS is a tuple/list and the RHS is unpacked: + * `a, b = (1, 2)` or `a, *rest = xs`. The SSA def lives at the inner + * `Name` CFG node, but for IterableUnpacking integration we expose + * the enclosing `StarredNode` as the `getDefiningNode()` for `*rest` + * patterns — mirroring legacy ESSA's `multi_assignment_definition`, + * which placed the def at the StarredNode CFG node. + */ +class MultiAssignmentDefinition extends EssaNodeDefinition { + MultiAssignmentDefinition() { + exists(Cfg::NameNode n | n = super.getDefiningNode() | + exists(Py::Assign a, Py::Expr lhs | + a.getATarget() = lhs and + (lhs instanceof Py::Tuple or lhs instanceof Py::List) and + lhs.getASubExpression+() = n.getNode() + ) + or + // For-loop with tuple/list target: `for a, b in xs:` — + // tuple-unpacking semantics applies to the for-target. + exists(Py::For f, Py::Expr lhs | + f.getTarget() = lhs and + (lhs instanceof Py::Tuple or lhs instanceof Py::List) and + lhs.getASubExpression+() = n.getNode() + ) + ) + } + + override Cfg::ControlFlowNode getDefiningNode() { + // Default: the underlying `Name` CFG node (where the SSA def lives). + not exists(Cfg::StarredNode s | + s.getNode().(Py::Starred).getValue() = super.getDefiningNode().getNode() + ) and + result = super.getDefiningNode() + or + // Exception: for `*rest`, expose the enclosing `Starred` CFG node + // so that `IterableUnpacking::iterableUnpackingStarredElementStoreStep` + // can attach the rest-list to it. + exists(Cfg::StarredNode s | + s.getNode().(Py::Starred).getValue() = super.getDefiningNode().getNode() + | + result = s + ) + } +} + +/** + * An implicit entry definition for a non-local / captured / global / + * builtin variable read in a scope but not defined there. + * + * Inherits from `EssaNodeDefinition` and exposes the scope's entry node + * as its defining node (matching legacy ESSA semantics). + */ +class ScopeEntryDefinition extends EssaNodeDefinition { + ScopeEntryDefinition() { + exists(CfgImpl::BasicBlock bb | + this.definesAt(_, bb, -1) and + bb instanceof CfgImpl::Cfg::EntryBasicBlock + ) + } + + /** Gets the enclosing scope (the scope whose entry block this def is in). */ + override Py::Scope getScope() { + exists(CfgImpl::BasicBlock bb | + this.definesAt(_, bb, -1) and + result = bb.getNode(0).(Cfg::ControlFlowNode).getScope() + ) + } +} + +/** A phi node (alias matching legacy naming). */ +class PhiFunction extends PhiNode { + /** + * Gets an input to this phi function (a definition that flows into + * the phi from one of its predecessor blocks). Mirrors legacy + * ESSA's `PhiFunction.getAnInput()`. + */ + Ssa::Definition getAnInput() { Ssa::phiHasInputFromBlock(this, result, _) } +} + +/** Base class for all ESSA definitions (legacy-shaped). */ +class EssaDefinition = Ssa::Definition; + +/** + * An adapter representing a single SSA-defined "variable" — wrapping + * one `Ssa::Definition`. Mirrors legacy `EssaVariable` API. + */ +class EssaVariable extends Ssa::Definition { + /** Gets the underlying SSA definition (legacy name). */ + Ssa::Definition getDefinition() { result = this } + + /** + * Gets a CFG node where this definition is used. Includes regular + * `Name` reads as well as the synthetic scope-exit "use" registered + * via `SsaImplInput::variableRead` — mirrors legacy ESSA's + * `EssaVariable.getAUse()` which inherited the synthetic exit-use + * from `SsaSourceVariable`. + */ + Cfg::ControlFlowNode getAUse() { + exists(CfgImpl::BasicBlock bb, int i | + Ssa::ssaDefReachesRead(this.getSourceVariable(), this, bb, i) and + bb.getNode(i) = result + ) + } + + /** Gets the (textual) name of the underlying variable. */ + string getName() { result = this.getSourceVariable().getVariable().getId() } + + /** Gets the scope in which this variable lives. */ + Py::Scope getScope() { result = this.getSourceVariable().getVariable().getScope() } + + /** Gets an ultimate non-phi ancestor of this definition. */ + EssaVariable getAnUltimateDefinition() { + if this instanceof PhiNode + then + exists(Ssa::Definition input | + Ssa::phiHasInputFromBlock(this, input, _) and + result = input.(EssaVariable).getAnUltimateDefinition() + ) + else result = this + } +} + +/** + * Adjacent use-use and def-use relations exposed by the shared SSA + * library. Provides the same interface as legacy + * `semmle.python.essa.SsaCompute::AdjacentUses`. + */ +module AdjacentUses { + /** Holds if `nodeFrom` and `nodeTo` are adjacent uses of the same SSA variable. */ + predicate adjacentUseUse(Cfg::NameNode nodeFrom, Cfg::NameNode nodeTo) { + exists(SsaSourceVariable v, CfgImpl::BasicBlock bb1, int i1, CfgImpl::BasicBlock bb2, int i2 | + Ssa::adjacentUseUse(bb1, i1, bb2, i2, v, _) and + nodeFrom = bb1.getNode(i1) and + nodeTo = bb2.getNode(i2) + ) + } + + /** Holds if `use` is a first use of definition `def`. */ + predicate firstUse(Ssa::Definition def, Cfg::NameNode use) { + exists(CfgImpl::BasicBlock bb, int i | + Ssa::firstUse(def, bb, i, _) and + use = bb.getNode(i) + ) + } + + /** + * Holds if `use` is any reachable use of definition `def`. Combines + * `firstUse` with transitive use-use adjacency. + */ + predicate useOfDef(Ssa::Definition def, Cfg::NameNode use) { + firstUse(def, use) + or + exists(Cfg::NameNode mid | useOfDef(def, mid) and adjacentUseUse(mid, use)) + } +} diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/TaintTrackingPrivate.qll b/python/ql/lib/semmle/python/dataflow/new/internal/TaintTrackingPrivate.qll index 62f5a76309b4..43f9a9e16fd4 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/TaintTrackingPrivate.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/TaintTrackingPrivate.qll @@ -1,4 +1,6 @@ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg +private import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl private import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.internal.DataFlowPrivate as DataFlowPrivate private import FlowSummaryImpl as FlowSummaryImpl @@ -75,7 +77,7 @@ import Cached * and isn't a big problem in practice. */ predicate concatStep(DataFlow::CfgNode nodeFrom, DataFlow::CfgNode nodeTo) { - exists(BinaryExprNode add | add = nodeTo.getNode() | + exists(Cfg::BinaryExprNode add | add = nodeTo.getNode() | add.getOp() instanceof Add and add.getAnOperand() = nodeFrom.getNode() ) } @@ -84,7 +86,7 @@ predicate concatStep(DataFlow::CfgNode nodeFrom, DataFlow::CfgNode nodeTo) { * Holds if taint can flow from `nodeFrom` to `nodeTo` with a step related to subscripting. */ predicate subscriptStep(DataFlow::CfgNode nodeFrom, DataFlow::CfgNode nodeTo) { - nodeTo.getNode().(SubscriptNode).getObject() = nodeFrom.getNode() + nodeTo.getNode().(Cfg::SubscriptNode).getObject() = nodeFrom.getNode() } /** @@ -100,15 +102,15 @@ predicate stringManipulation(DataFlow::CfgNode nodeFrom, DataFlow::CfgNode nodeT ( call = API::builtin(["str", "bytes", "unicode"]).getACall() or - call.getFunction().asCfgNode().(NameNode).getId() in ["str", "bytes", "unicode"] + call.getFunction().asCfgNode().(Cfg::NameNode).getId() in ["str", "bytes", "unicode"] ) and nodeFrom in [call.getArg(0), call.getArgByName("object")] ) or // String methods. Note that this doesn't recognize `meth = "foo".upper; meth()` - exists(CallNode call, string method_name, ControlFlowNode object | + exists(Cfg::CallNode call, string method_name, Cfg::ControlFlowNode object | call = nodeTo.getNode() and - object = call.getFunction().(AttrNode).getObject(method_name) + object = call.getFunction().(Cfg::AttrNode).getObject(method_name) | nodeFrom.getNode() = object and method_name in [ @@ -139,7 +141,7 @@ predicate stringManipulation(DataFlow::CfgNode nodeFrom, DataFlow::CfgNode nodeT ) or // % formatting - exists(BinaryExprNode fmt | fmt = nodeTo.getNode() | + exists(Cfg::BinaryExprNode fmt | fmt = nodeTo.getNode() | fmt.getOp() instanceof Mod and ( fmt.getLeft() = nodeFrom.getNode() @@ -149,7 +151,7 @@ predicate stringManipulation(DataFlow::CfgNode nodeFrom, DataFlow::CfgNode nodeT ) or // string multiplication -- `"foo" * 10` - exists(BinaryExprNode mult | mult = nodeTo.getNode() | + exists(Cfg::BinaryExprNode mult | mult = nodeTo.getNode() | mult.getOp() instanceof Mult and mult.getLeft() = nodeFrom.getNode() ) @@ -207,8 +209,8 @@ predicate awaitStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { * the variable `f` is tainted if the result of `open("foo")` is tainted. */ predicate asyncWithStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { - exists(With with, ControlFlowNode contextManager, ControlFlowNode var | - var = any(WithDefinition wd).getDefiningNode() + exists(With with, Cfg::ControlFlowNode contextManager, Cfg::ControlFlowNode var | + var = any(SsaImpl::WithDefinition wd).getDefiningNode() | nodeFrom.(DataFlow::CfgNode).getNode() = contextManager and nodeTo.(DataFlow::CfgNode).getNode() = var and diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackingImpl.qll b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackingImpl.qll index 95434b05451d..7fa1d4e573aa 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackingImpl.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackingImpl.qll @@ -2,6 +2,8 @@ import codeql.util.Unit import codeql.typetracking.TypeTracking as Shared import codeql.typetracking.internal.TypeTrackingImpl as SharedImpl private import python +private import semmle.python.controlflow.internal.Cfg as Cfg +private import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl private import semmle.python.internal.CachedStages private import semmle.python.dataflow.new.internal.DataFlowPublic as DataFlowPublic private import semmle.python.dataflow.new.internal.DataFlowPrivate as DataFlowPrivate @@ -94,8 +96,10 @@ private module SummaryTypeTrackerInput implements SummaryTypeTracker::Input { Node returnOf(Node callable, SummaryComponent return) { return = FlowSummaryImpl::Private::SummaryComponent::return() and // `result` should be the return value of a callable expression (lambda or function) referenced by `callable` - result.asCfgNode() = - callable.getALocalSource().asExpr().(CallableExpr).getInnerScope().getAReturnValueFlowNode() + exists(Return ret | + ret.getScope() = callable.getALocalSource().asExpr().(CallableExpr).getInnerScope() and + result.asCfgNode().getNode() = ret.getValue() + ) } // Relating callables to nodes @@ -160,7 +164,7 @@ module TypeTrackingInput implements Shared::TypeTrackingInput { // ignore the flow steps from the synthetic sequence node to the real sequence node, // since we only support one level of content in type-trackers, and the nested // structure requires two levels at least to be useful. - not exists(SequenceNode outer | + not exists(Cfg::SequenceNode outer | outer.getAnElement() = nodeTo.asCfgNode() and IterableUnpacking::iterableUnpackingTupleFlowStep(nodeFrom, nodeTo) ) @@ -259,7 +263,7 @@ module TypeTrackingInput implements Shared::TypeTrackingInput { // Since we only support one level of content in type-trackers we don't actually // support `(aa, ab), (ba, bb) = ...`. Therefore we exclude the read-step from `(aa, // ab)` to `aa` (since it is not needed). - not exists(SequenceNode outer | + not exists(Cfg::SequenceNode outer | outer.getAnElement() = nodeFrom.asCfgNode() and IterableUnpacking::iterableUnpackingTupleFlowStep(_, nodeFrom) ) and @@ -269,7 +273,7 @@ module TypeTrackingInput implements Shared::TypeTrackingInput { IterableUnpacking::iterableUnpackingForReadStep(_, _, seq) and IterableUnpacking::iterableUnpackingConvertingReadStep(seq, _, elem) and IterableUnpacking::iterableUnpackingConvertingStoreStep(elem, _, nodeFrom) and - nodeFrom.asCfgNode() instanceof SequenceNode + nodeFrom.asCfgNode() instanceof Cfg::SequenceNode ) or TypeTrackerSummaryFlow::basicLoadStep(nodeFrom, nodeTo, content) @@ -306,13 +310,15 @@ module TypeTrackingInput implements Shared::TypeTrackingInput { // // nodeFrom is `expr` // nodeTo is entry node for `f` - exists(ScopeEntryDefinition e, SsaSourceVariable var, DefinitionNode def | + exists( + SsaImpl::ScopeEntryDefinition e, SsaImpl::SsaSourceVariable var, Cfg::DefinitionNode def + | e.getSourceVariable() = var and - var.hasDefiningNode(def) + def.getNode() = var.getVariable().getAStore() | nodeTo.(DataFlowPublic::ScopeEntryDefinitionNode).getDefinition() = e and nodeFrom.asCfgNode() = def and - var.getScope().getScope*() = nodeFrom.getScope() + var.getVariable().getScope().getScope*() = nodeFrom.getScope() ) } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/VariableCapture.qll b/python/ql/lib/semmle/python/dataflow/new/internal/VariableCapture.qll index fbe05979328c..2cd2fedb1d7a 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/VariableCapture.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/VariableCapture.qll @@ -3,6 +3,9 @@ overlay[local] module; private import python +private import semmle.python.controlflow.internal.Cfg as Cfg +private import semmle.python.controlflow.internal.AstNodeImpl as CfgImpl +private import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl private import DataFlowPublic private import semmle.python.dataflow.new.internal.DataFlowPrivate private import codeql.dataflow.VariableCapture as Shared @@ -14,10 +17,10 @@ private import codeql.dataflow.VariableCapture as Shared // The first is the main implementation, the second is a performance motivated restriction. // The restriction is to clear any `CapturedVariableContent` before writing a new one // to avoid long access paths (see the link for a nice explanation). -private module CaptureInput implements Shared::InputSig { +private module CaptureInput implements Shared::InputSig { private import python as PY - additional class ExprCfgNode extends ControlFlowNode { + additional class ExprCfgNode extends Cfg::ControlFlowNode { ExprCfgNode() { isExpressionNode(this) } } @@ -25,7 +28,9 @@ private module CaptureInput implements Shared::InputSig; +module Flow = Shared::Flow; private Flow::ClosureNode asClosureNode(Node n) { result = n.(SynthCaptureNode).getSynthesizedCaptureNode() diff --git a/python/ql/lib/semmle/python/dataflow/old/Implementation.qll b/python/ql/lib/semmle/python/dataflow/old/Implementation.qll index 18020c7d9ffa..6e1314ff9e9a 100644 --- a/python/ql/lib/semmle/python/dataflow/old/Implementation.qll +++ b/python/ql/lib/semmle/python/dataflow/old/Implementation.qll @@ -448,8 +448,7 @@ class TaintTrackingImplementation extends string instanceof TaintTracking::Confi context = TNoParam() and src = TTaintTrackingNode_(retval, TNoParam(), path, kind, this) and node.asCfgNode() = call and - retval.asCfgNode() = - any(Return ret | ret.getScope() = pyfunc.getScope()).getValue().getAFlowNode() + retval.asCfgNode().getNode() = any(Return ret | ret.getScope() = pyfunc.getScope()).getValue() ) and edgeLabel = "return" } @@ -471,8 +470,7 @@ class TaintTrackingImplementation extends string instanceof TaintTracking::Confi this.callContexts(call, src, pyfunc, context, callee) and retnode = TTaintTrackingNode_(retval, callee, path, kind, this) and node.asCfgNode() = call and - retval.asCfgNode() = - any(Return ret | ret.getScope() = pyfunc.getScope()).getValue().getAFlowNode() + retval.asCfgNode().getNode() = any(Return ret | ret.getScope() = pyfunc.getScope()).getValue() ) and edgeLabel = "call" } @@ -716,8 +714,10 @@ private class EssaTaintTracking extends string instanceof TaintTracking::Configu src = TTaintTrackingNode_(srcnode, context, path, srckind, this) and path.noAttribute() | - assign.getValue().getAFlowNode() = srcnode.asCfgNode() and - depth = iterable_unpacking_descent(assign.getATarget().getAFlowNode(), defn.getDefiningNode()) and + srcnode.asCfgNode().getNode() = assign.getValue() and + exists(SequenceNode left_parent | left_parent.getNode() = assign.getATarget() | + depth = iterable_unpacking_descent(left_parent, defn.getDefiningNode()) + ) and kind = taint_at_depth(srckind, depth) ) } @@ -964,7 +964,7 @@ private TaintKind taint_at_depth(SequenceKind parent_kind, int depth) { * - with `left_defn` = `*y`, `left_parent` = `((x, *y), ...)`, result = 1 */ int iterable_unpacking_descent(SequenceNode left_parent, ControlFlowNode left_defn) { - exists(Assign a | a.getATarget().getASubExpression*().getAFlowNode() = left_parent) and + exists(Assign a | left_parent.getNode() = a.getATarget().getASubExpression*()) and left_parent.getAnElement() = left_defn and // Handle `a, *b = some_iterable` if left_defn instanceof StarredNode then result = 0 else result = 1 diff --git a/python/ql/lib/semmle/python/essa/SsaDefinitions.qll b/python/ql/lib/semmle/python/essa/SsaDefinitions.qll index 827bee34474e..44d246009d3a 100644 --- a/python/ql/lib/semmle/python/essa/SsaDefinitions.qll +++ b/python/ql/lib/semmle/python/essa/SsaDefinitions.qll @@ -56,7 +56,7 @@ module SsaSource { predicate with_definition(Variable v, ControlFlowNode defn) { exists(With with, Name var | with.getOptionalVars() = var and - var.getAFlowNode() = defn + defn.getNode() = var | var = v.getAStore() ) @@ -67,7 +67,7 @@ module SsaSource { predicate pattern_capture_definition(Variable v, ControlFlowNode defn) { exists(MatchCapturePattern capture, Name var | capture.getVariable() = var and - var.getAFlowNode() = defn + defn.getNode() = var | var = v.getAStore() ) @@ -78,7 +78,7 @@ module SsaSource { predicate pattern_alias_definition(Variable v, ControlFlowNode defn) { exists(MatchAsPattern pattern, Name var | pattern.getAlias() = var and - var.getAFlowNode() = defn + defn.getNode() = var | var = v.getAStore() ) diff --git a/python/ql/lib/semmle/python/frameworks/Bottle.qll b/python/ql/lib/semmle/python/frameworks/Bottle.qll index c03ea3df184e..9714f1967770 100644 --- a/python/ql/lib/semmle/python/frameworks/Bottle.qll +++ b/python/ql/lib/semmle/python/frameworks/Bottle.qll @@ -4,6 +4,7 @@ */ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.Concepts private import semmle.python.ApiGraphs private import semmle.python.dataflow.new.RemoteFlowSources @@ -59,7 +60,7 @@ module Bottle { override Parameter getARoutedParameter() { none() } - override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node } + override Function getARequestHandler() { node.getNode() = result.getADecorator() } } } @@ -73,7 +74,9 @@ module Bottle { /** A response returned by a view callable. */ class BottleReturnResponse extends Http::Server::HttpResponse::Range { BottleReturnResponse() { - this.asCfgNode() = any(View::ViewCallable vc).getAReturnValueFlowNode() + exists(View::ViewCallable vc, Return ret | + ret.getScope() = vc and this.asCfgNode().getNode() = ret.getValue() + ) } override DataFlow::Node getBody() { result = this } @@ -154,9 +157,9 @@ module Bottle { DataFlow::Node value; HeaderWriteSubscript() { - exists(SubscriptNode subscript | + exists(Cfg::SubscriptNode subscript | this.asCfgNode() = subscript and - value.asCfgNode() = subscript.(DefinitionNode).getValue() and + value.asCfgNode() = subscript.(Cfg::DefinitionNode).getValue() and name.asCfgNode() = subscript.getIndex() and subscript.getObject() = headers().asSource().asCfgNode() ) diff --git a/python/ql/lib/semmle/python/frameworks/Django.qll b/python/ql/lib/semmle/python/frameworks/Django.qll index ee0ed4a84dd0..37d5b3357256 100644 --- a/python/ql/lib/semmle/python/frameworks/Django.qll +++ b/python/ql/lib/semmle/python/frameworks/Django.qll @@ -4,6 +4,7 @@ */ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.RemoteFlowSources private import semmle.python.dataflow.new.TaintTracking @@ -1305,7 +1306,7 @@ module PrivateDjango { dict.(DataFlow::MethodCallNode).calls(files, "dict") ) | - this.asCfgNode().(SubscriptNode).getObject() = dict.asCfgNode() + this.asCfgNode().(Cfg::SubscriptNode).getObject() = dict.asCfgNode() or this.(DataFlow::MethodCallNode).calls(dict, "get") ) @@ -1314,7 +1315,7 @@ module PrivateDjango { exists(DataFlow::AttrRead files, DataFlow::MethodCallNode getlistCall | files.accesses(instance(), "FILES") and getlistCall.calls(files, "getlist") and - this.asCfgNode().(SubscriptNode).getObject() = getlistCall.asCfgNode() + this.asCfgNode().(Cfg::SubscriptNode).getObject() = getlistCall.asCfgNode() ) } } @@ -2216,7 +2217,7 @@ module PrivateDjango { DataFlow::Node value; DjangoResponseCookieSubscriptWrite() { - exists(SubscriptNode subscript, DataFlow::AttrRead cookieLookup | + exists(Cfg::SubscriptNode subscript, DataFlow::AttrRead cookieLookup | // To give `this` a value, we need to choose between either LHS or RHS, // and just go with the LHS this.asCfgNode() = subscript @@ -2228,7 +2229,7 @@ module PrivateDjango { | cookieLookup.flowsTo(subscriptObj) ) and - value.asCfgNode() = subscript.(DefinitionNode).getValue() and + value.asCfgNode() = subscript.(Cfg::DefinitionNode).getValue() and index.asCfgNode() = subscript.getIndex() ) } @@ -2249,7 +2250,7 @@ module PrivateDjango { DataFlow::Node value; DjangoResponseHeaderSubscriptWrite() { - exists(SubscriptNode subscript, DataFlow::AttrRead headerLookup | + exists(Cfg::SubscriptNode subscript, DataFlow::AttrRead headerLookup | // To give `this` a value, we need to choose between either LHS or RHS, // and just go with the LHS this.asCfgNode() = subscript @@ -2261,7 +2262,7 @@ module PrivateDjango { | headerLookup.flowsTo(subscriptObj) ) and - value.asCfgNode() = subscript.(DefinitionNode).getValue() and + value.asCfgNode() = subscript.(Cfg::DefinitionNode).getValue() and index.asCfgNode() = subscript.getIndex() ) } @@ -2284,14 +2285,14 @@ module PrivateDjango { DataFlow::Node value; DjangoResponseSubscriptWrite() { - exists(SubscriptNode subscript | + exists(Cfg::SubscriptNode subscript | // To give `this` a value, we need to choose between either LHS or RHS, // and just go with the LHS this.asCfgNode() = subscript | subscript.getObject() = DjangoImpl::DjangoHttp::Response::HttpResponse::instance().asCfgNode() and - value.asCfgNode() = subscript.(DefinitionNode).getValue() and + value.asCfgNode() = subscript.(Cfg::DefinitionNode).getValue() and index.asCfgNode() = subscript.getIndex() ) } @@ -2426,7 +2427,7 @@ module PrivateDjango { /** Gets a reference to the result of calling the `as_view` classmethod of this class. */ private DataFlow::TypeTrackingNode asViewResult(DataFlow::TypeTracker t) { t.start() and - result.asCfgNode().(CallNode).getFunction() = this.asViewRef().asCfgNode() + result.asCfgNode().(Cfg::CallNode).getFunction() = this.asViewRef().asCfgNode() or exists(DataFlow::TypeTracker t2 | result = this.asViewResult(t2).track(t2, t)) } @@ -2872,7 +2873,9 @@ module PrivateDjango { DataFlow::CfgNode { DjangoRedirectViewGetRedirectUrlReturn() { - node = any(GetRedirectUrlFunction f).getAReturnValueFlowNode() + exists(GetRedirectUrlFunction f, Return ret | + ret.getScope() = f and node.getNode() = ret.getValue() + ) } override DataFlow::Node getRedirectLocation() { result = this } diff --git a/python/ql/lib/semmle/python/frameworks/FastApi.qll b/python/ql/lib/semmle/python/frameworks/FastApi.qll index ed28f18a85a9..a8a35f485092 100644 --- a/python/ql/lib/semmle/python/frameworks/FastApi.qll +++ b/python/ql/lib/semmle/python/frameworks/FastApi.qll @@ -4,6 +4,7 @@ */ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.RemoteFlowSources private import semmle.python.dataflow.new.TaintTracking @@ -129,7 +130,7 @@ module FastApi { result in [this.getArg(0), this.getArgByName("path")] } - override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node } + override Function getARequestHandler() { node.getNode() = result.getADecorator() } override string getFramework() { result = "FastAPI" } @@ -309,7 +310,11 @@ module FastApi { FastApiRouteSetup routeSetup; FastApiRequestHandlerReturn() { - node = routeSetup.getARequestHandler().getAReturnValueFlowNode() + exists(Function h, Return ret | + h = routeSetup.getARequestHandler() and + ret.getScope() = h and + node.getNode() = ret.getValue() + ) } override DataFlow::Node getBody() { result = this } @@ -438,7 +443,7 @@ module FastApi { DataFlow::Node value; HeaderSubscriptWrite() { - exists(SubscriptNode subscript, DataFlow::AttrRead headerLookup | + exists(Cfg::SubscriptNode subscript, DataFlow::AttrRead headerLookup | // To give `this` a value, we need to choose between either LHS or RHS, // and just go with the LHS this.asCfgNode() = subscript @@ -447,7 +452,7 @@ module FastApi { exists(DataFlow::Node subscriptObj | subscriptObj.asCfgNode() = subscript.getObject() | headerLookup.flowsTo(subscriptObj) ) and - value.asCfgNode() = subscript.(DefinitionNode).getValue() and + value.asCfgNode() = subscript.(Cfg::DefinitionNode).getValue() and index.asCfgNode() = subscript.getIndex() ) } diff --git a/python/ql/lib/semmle/python/frameworks/Flask.qll b/python/ql/lib/semmle/python/frameworks/Flask.qll index f819e8679075..e2bb161eafe3 100644 --- a/python/ql/lib/semmle/python/frameworks/Flask.qll +++ b/python/ql/lib/semmle/python/frameworks/Flask.qll @@ -371,7 +371,7 @@ module Flask { result in [this.getArg(0), this.getArgByName("rule")] } - override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node } + override Function getARequestHandler() { node.getNode() = result.getADecorator() } } /** @@ -536,7 +536,7 @@ module Flask { FlaskRouteHandlerReturn() { exists(Function routeHandler | routeHandler = any(FlaskRouteSetup rs).getARequestHandler() and - node = routeHandler.getAReturnValueFlowNode() and + exists(Return ret | ret.getScope() = routeHandler and node.getNode() = ret.getValue()) and not this instanceof Flask::Response::InstanceSource ) } diff --git a/python/ql/lib/semmle/python/frameworks/FlaskAdmin.qll b/python/ql/lib/semmle/python/frameworks/FlaskAdmin.qll index a9b90f1d9c4a..64870fd71ac4 100644 --- a/python/ql/lib/semmle/python/frameworks/FlaskAdmin.qll +++ b/python/ql/lib/semmle/python/frameworks/FlaskAdmin.qll @@ -38,7 +38,7 @@ private module FlaskAdmin { result in [this.getArg(0), this.getArgByName("url")] } - override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node } + override Function getARequestHandler() { node.getNode() = result.getADecorator() } } /** @@ -71,7 +71,7 @@ private module FlaskAdmin { override Function getARequestHandler() { exists(Flask::FlaskViewClass cls | - cls.getADecorator().getAFlowNode() = node and + node.getNode() = cls.getADecorator() and result = cls.getARequestHandler() ) } diff --git a/python/ql/lib/semmle/python/frameworks/Gradio.qll b/python/ql/lib/semmle/python/frameworks/Gradio.qll index 11109e150bfd..92aec4bd7732 100644 --- a/python/ql/lib/semmle/python/frameworks/Gradio.qll +++ b/python/ql/lib/semmle/python/frameworks/Gradio.qll @@ -4,6 +4,7 @@ */ import python +private import semmle.python.controlflow.internal.Cfg as Cfg import semmle.python.dataflow.new.RemoteFlowSources import semmle.python.dataflow.new.TaintTracking import semmle.python.ApiGraphs @@ -51,9 +52,9 @@ module Gradio { // limit only to lists of parameters given to `inputs`. ( ( - call.getKeywordParameter("inputs").asSink().asCfgNode() instanceof ListNode + call.getKeywordParameter("inputs").asSink().asCfgNode() instanceof Cfg::ListNode or - call.getParameter(1).asSink().asCfgNode() instanceof ListNode + call.getParameter(1).asSink().asCfgNode() instanceof Cfg::ListNode ) and ( this = call.getKeywordParameter("inputs").getASubscript().getAValueReachingSink() @@ -75,8 +76,8 @@ module Gradio { exists(GradioInput call | this = call.getParameter(0, "fn").getParameter(_).asSource() and // exclude lists of parameters given to `inputs` - not call.getKeywordParameter("inputs").asSink().asCfgNode() instanceof ListNode and - not call.getParameter(1).asSink().asCfgNode() instanceof ListNode + not call.getKeywordParameter("inputs").asSink().asCfgNode() instanceof Cfg::ListNode and + not call.getParameter(1).asSink().asCfgNode() instanceof Cfg::ListNode ) } @@ -105,16 +106,16 @@ module Gradio { // handle cases where there are multiple arguments passed as a list to `inputs` ( ( - node.getKeywordParameter("inputs").asSink().asCfgNode() instanceof ListNode + node.getKeywordParameter("inputs").asSink().asCfgNode() instanceof Cfg::ListNode or - node.getParameter(1).asSink().asCfgNode() instanceof ListNode + node.getParameter(1).asSink().asCfgNode() instanceof Cfg::ListNode ) and exists(int i | nodeTo = node.getParameter(0, "fn").getParameter(i).asSource() | nodeFrom.asCfgNode() = - node.getKeywordParameter("inputs").asSink().asCfgNode().(ListNode).getElement(i) + node.getKeywordParameter("inputs").asSink().asCfgNode().(Cfg::ListNode).getElement(i) or nodeFrom.asCfgNode() = - node.getParameter(1).asSink().asCfgNode().(ListNode).getElement(i) + node.getParameter(1).asSink().asCfgNode().(Cfg::ListNode).getElement(i) ) ) ) diff --git a/python/ql/lib/semmle/python/frameworks/MarkupSafe.qll b/python/ql/lib/semmle/python/frameworks/MarkupSafe.qll index 6e3b630ffa57..a4832b27ba40 100644 --- a/python/ql/lib/semmle/python/frameworks/MarkupSafe.qll +++ b/python/ql/lib/semmle/python/frameworks/MarkupSafe.qll @@ -4,6 +4,7 @@ */ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.TaintTracking private import semmle.python.Concepts @@ -46,7 +47,7 @@ module MarkupSafeModel { /** A direct instantiation of `markupsafe.Markup`. */ private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode { - override CallNode node; + override Cfg::CallNode node; ClassInstantiation() { this = classRef().getACall() } } @@ -64,7 +65,7 @@ module MarkupSafeModel { /** A string concatenation with a `markupsafe.Markup` involved. */ class StringConcat extends Markup::InstanceSource, DataFlow::CfgNode { - override BinaryExprNode node; + override Cfg::BinaryExprNode node; StringConcat() { node.getOp() instanceof Add and @@ -79,7 +80,7 @@ module MarkupSafeModel { /** A %-style string format with `markupsafe.Markup` as the format string. */ class PercentStringFormat extends Markup::InstanceSource, DataFlow::CfgNode { - override BinaryExprNode node; + override Cfg::BinaryExprNode node; PercentStringFormat() { node.getOp() instanceof Mod and diff --git a/python/ql/lib/semmle/python/frameworks/Pycurl.qll b/python/ql/lib/semmle/python/frameworks/Pycurl.qll index 7280eec5f61c..030e6c66f8de 100644 --- a/python/ql/lib/semmle/python/frameworks/Pycurl.qll +++ b/python/ql/lib/semmle/python/frameworks/Pycurl.qll @@ -7,6 +7,7 @@ */ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.Concepts private import semmle.python.ApiGraphs private import semmle.python.frameworks.data.ModelsAsData @@ -56,7 +57,7 @@ module Pycurl { { OutgoingRequestCall() { this = setopt().getACall() and - this.getArg(0).asCfgNode().(AttrNode).getName() = "URL" + this.getArg(0).asCfgNode().(Cfg::AttrNode).getName() = "URL" } override DataFlow::Node getAUrlPart() { @@ -81,7 +82,7 @@ module Pycurl { private class CurlSslCall extends Http::Client::Request::Range instanceof DataFlow::CallCfgNode { CurlSslCall() { this = setopt().getACall() and - this.getArg(0).asCfgNode().(AttrNode).getName() = ["SSL_VERIFYPEER", "SSL_VERIFYHOST"] + this.getArg(0).asCfgNode().(Cfg::AttrNode).getName() = ["SSL_VERIFYPEER", "SSL_VERIFYHOST"] } override DataFlow::Node getAUrlPart() { none() } diff --git a/python/ql/lib/semmle/python/frameworks/Pydantic.qll b/python/ql/lib/semmle/python/frameworks/Pydantic.qll index c3d76835b429..1aa5c9142e6d 100644 --- a/python/ql/lib/semmle/python/frameworks/Pydantic.qll +++ b/python/ql/lib/semmle/python/frameworks/Pydantic.qll @@ -7,6 +7,7 @@ */ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.TaintTracking private import semmle.python.Concepts @@ -93,7 +94,7 @@ module Pydantic { // be a Pydantic model. So `model[0]` will be an overapproximation, but should not // really cause problems (since we don't expect real code to contain such accesses) nodeFrom = instance() and - nodeTo.asCfgNode().(SubscriptNode).getObject() = nodeFrom.asCfgNode() + nodeTo.asCfgNode().(Cfg::SubscriptNode).getObject() = nodeFrom.asCfgNode() } /** diff --git a/python/ql/lib/semmle/python/frameworks/Pyramid.qll b/python/ql/lib/semmle/python/frameworks/Pyramid.qll index 63e19363fe86..2bd72a852536 100644 --- a/python/ql/lib/semmle/python/frameworks/Pyramid.qll +++ b/python/ql/lib/semmle/python/frameworks/Pyramid.qll @@ -166,7 +166,9 @@ module Pyramid { /** A response returned by a view callable. */ private class PyramidReturnResponse extends Http::Server::HttpResponse::Range { PyramidReturnResponse() { - this.asCfgNode() = any(View::ViewCallable vc).getAReturnValueFlowNode() and + exists(View::ViewCallable vc, Return ret | + ret.getScope() = vc and this.asCfgNode().getNode() = ret.getValue() + ) and not this = instance() } diff --git a/python/ql/lib/semmle/python/frameworks/Stdlib.qll b/python/ql/lib/semmle/python/frameworks/Stdlib.qll index 5d3b994880a1..d059c59e9af6 100644 --- a/python/ql/lib/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/lib/semmle/python/frameworks/Stdlib.qll @@ -6,6 +6,7 @@ overlay[local?] module; private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.TaintTracking private import semmle.python.dataflow.new.RemoteFlowSources @@ -1246,7 +1247,7 @@ module StdlibPrivate { /** An additional taint step for calls to `os.path.join` */ private class OsPathJoinCallAdditionalTaintStep extends TaintTracking::AdditionalTaintStep { override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { - exists(CallNode call | + exists(Cfg::CallNode call | nodeTo.asCfgNode() = call and call = OS::OsPath::join().getACall().asCfgNode() and call.getAnArg() = nodeFrom.asCfgNode() @@ -1317,13 +1318,13 @@ module StdlibPrivate { // run, so if we're able to, we only mark the first element as the command // (and not the arguments to the command). // - result.asCfgNode() = arg_args.asCfgNode().(SequenceNode).getElement(0) + result.asCfgNode() = arg_args.asCfgNode().(Cfg::SequenceNode).getElement(0) or // Either the "args" argument is not a sequence (which is valid) or we where // just not able to figure it out. Simply mark the "args" argument as the // command. // - not arg_args.asCfgNode() instanceof SequenceNode and + not arg_args.asCfgNode() instanceof Cfg::SequenceNode and result = arg_args ) ) @@ -1542,7 +1543,7 @@ module StdlibPrivate { * See https://docs.python.org/3/library/functions.html#eval */ private class BuiltinsEvalCall extends CodeExecution::Range, DataFlow::CallCfgNode { - override CallNode node; + override Cfg::CallNode node; BuiltinsEvalCall() { this = API::builtin("eval").getACall() } @@ -1923,7 +1924,7 @@ module StdlibPrivate { nodeFrom = instance().getAValueReachableFromSource() and nodeTo = [getvalueRef(), getfirstRef(), getlistRef()].getAValueReachableFromSource() or - nodeFrom.asCfgNode() = nodeTo.asCfgNode().(CallNode).getFunction() and + nodeFrom.asCfgNode() = nodeTo.asCfgNode().(Cfg::CallNode).getFunction() and ( nodeFrom = getvalueRef().getAValueReachableFromSource() and nodeTo = getvalueResult().asSource() @@ -1939,7 +1940,7 @@ module StdlibPrivate { nodeFrom in [ instance().getAValueReachableFromSource(), fieldList().getAValueReachableFromSource() ] and - nodeTo.asCfgNode().(SubscriptNode).getObject() = nodeFrom.asCfgNode() + nodeTo.asCfgNode().(Cfg::SubscriptNode).getObject() = nodeFrom.asCfgNode() or // Attributes on Field nodeFrom = field().getAValueReachableFromSource() and @@ -2254,8 +2255,8 @@ module StdlibPrivate { DataFlow::CfgNode { WsgirefSimpleServerApplicationReturn() { - exists(WsgirefSimpleServerApplication requestHandler | - node = requestHandler.getAReturnValueFlowNode() + exists(WsgirefSimpleServerApplication requestHandler, Return ret | + ret.getScope() = requestHandler and node.getNode() = ret.getValue() ) } @@ -2337,9 +2338,9 @@ module StdlibPrivate { DataFlow::Node value; HeaderWriteSubscript() { - exists(SubscriptNode subscript | + exists(Cfg::SubscriptNode subscript | this.asCfgNode() = subscript and - value.asCfgNode() = subscript.(DefinitionNode).getValue() and + value.asCfgNode() = subscript.(Cfg::DefinitionNode).getValue() and name.asCfgNode() = subscript.getIndex() and subscript.getObject() = instance().asCfgNode() ) @@ -2681,7 +2682,7 @@ module StdlibPrivate { or // Data injection // Special handling of the `/` operator - exists(BinaryExprNode slash, DataFlow::Node pathOperand, DataFlow::TypeTracker t2 | + exists(Cfg::BinaryExprNode slash, DataFlow::Node pathOperand, DataFlow::TypeTracker t2 | slash.getOp() instanceof Div and pathOperand.asCfgNode() = slash.getAnOperand() and pathlibPath(t2).flowsTo(pathOperand) and @@ -2806,7 +2807,7 @@ module StdlibPrivate { pathlibPath().flowsTo(nodeTo) and ( // Special handling of the `/` operator - exists(BinaryExprNode slash, DataFlow::Node pathOperand | + exists(Cfg::BinaryExprNode slash, DataFlow::Node pathOperand | slash.getOp() instanceof Div and pathOperand.asCfgNode() = slash.getAnOperand() and pathlibPath().flowsTo(pathOperand) @@ -4605,9 +4606,9 @@ module StdlibPrivate { } override predicate propagatesFlow(string input, string output, boolean preservesValue) { - exists(CallNode c, string name, ControlFlowNode n, DataFlow::AttributeContent ac | - c.getFunction().(NameNode).getId() = "replace" or - c.getFunction().(AttrNode).getName() = "replace" + exists(Cfg::CallNode c, string name, Cfg::ControlFlowNode n, DataFlow::AttributeContent ac | + c.getFunction().(Cfg::NameNode).getId() = "replace" or + c.getFunction().(Cfg::AttrNode).getName() = "replace" | n = c.getArgByName(name) and ac.getAttribute() = name and @@ -5151,10 +5152,10 @@ module StdlibPrivate { * See https://docs.python.org/3.9/library/stdtypes.html#str.startswith */ private class StartswithCall extends Path::SafeAccessCheck::Range { - StartswithCall() { this.(CallNode).getFunction().(AttrNode).getName() = "startswith" } + StartswithCall() { this.(Cfg::CallNode).getFunction().(Cfg::AttrNode).getName() = "startswith" } - override predicate checks(ControlFlowNode node, boolean branch) { - node = this.(CallNode).getFunction().(AttrNode).getObject() and + override predicate checks(Cfg::ControlFlowNode node, boolean branch) { + node = this.(Cfg::CallNode).getFunction().(Cfg::AttrNode).getObject() and branch = true } } diff --git a/python/ql/lib/semmle/python/frameworks/Stdlib/Urllib.qll b/python/ql/lib/semmle/python/frameworks/Stdlib/Urllib.qll index 6b5764e55925..af670c009b6c 100644 --- a/python/ql/lib/semmle/python/frameworks/Stdlib/Urllib.qll +++ b/python/ql/lib/semmle/python/frameworks/Stdlib/Urllib.qll @@ -8,6 +8,7 @@ */ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.Concepts private import semmle.python.ApiGraphs private import semmle.python.security.dataflow.UrlRedirectCustomizations @@ -91,7 +92,7 @@ private module Urllib { * A read of the `netloc` attribute of a parsed URL as returned by `urllib.parse.urlparse`, * which is being checked in a way that is relevant for URL redirection vulnerabilities. */ - private predicate netlocCheck(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) { + private predicate netlocCheck(DataFlow::GuardNode g, Cfg::ControlFlowNode node, boolean branch) { exists(DataFlow::CallCfgNode urlParseCall, DataFlow::AttrRead netlocRead | urlParseCall = getUrlParseCall() and netlocRead = urlParseCall.getAnAttributeRead("netloc") and diff --git a/python/ql/lib/semmle/python/frameworks/Tornado.qll b/python/ql/lib/semmle/python/frameworks/Tornado.qll index 61cf7df316e7..3e57cdd3108a 100644 --- a/python/ql/lib/semmle/python/frameworks/Tornado.qll +++ b/python/ql/lib/semmle/python/frameworks/Tornado.qll @@ -4,6 +4,7 @@ */ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.RemoteFlowSources private import semmle.python.dataflow.new.TaintTracking @@ -72,9 +73,9 @@ module Tornado { DataFlow::Node value; TornadoHeaderSubscriptWrite() { - exists(SubscriptNode subscript | + exists(Cfg::SubscriptNode subscript | subscript.getObject() = instance().asCfgNode() and - value.asCfgNode() = subscript.(DefinitionNode).getValue() and + value.asCfgNode() = subscript.(Cfg::DefinitionNode).getValue() and index.asCfgNode() = subscript.getIndex() and this.asCfgNode() = subscript ) @@ -422,7 +423,7 @@ module Tornado { // be able to do something more structured for providing modeling of the members // of a container-object. exists(DataFlow::AttrRead files | files.accesses(instance(), "cookies") | - this.asCfgNode().(SubscriptNode).getObject() = files.asCfgNode() + this.asCfgNode().(Cfg::SubscriptNode).getObject() = files.asCfgNode() or this.(DataFlow::MethodCallNode).calls(files, "get") ) @@ -479,20 +480,20 @@ module Tornado { // routing // --------------------------------------------------------------------------- /** Gets a sequence that defines a number of route rules */ - SequenceNode routeSetupRuleList() { - exists(CallNode call | + Cfg::SequenceNode routeSetupRuleList() { + exists(Cfg::CallNode call | call = any(TornadoModule::Web::Application::ClassInstantiation c).asCfgNode() | result in [call.getArg(0), call.getArgByName("handlers")] ) or - exists(CallNode call | + exists(Cfg::CallNode call | call.getFunction() = TornadoModule::Web::Application::add_handlers().asCfgNode() | result in [call.getArg(1), call.getArgByName("host_handlers")] ) or - result = routeSetupRuleList().getElement(_).(TupleNode).getElement(1) + result = routeSetupRuleList().getElement(_).(Cfg::TupleNode).getElement(1) } /** A tornado route setup. */ @@ -515,12 +516,12 @@ module Tornado { /** A route setup using a tuple. */ private class TornadoTupleRouteSetup extends TornadoRouteSetup, DataFlow::CfgNode { - override TupleNode node; + override Cfg::TupleNode node; TornadoTupleRouteSetup() { node = routeSetupRuleList().getElement(_) and count(node.getElement(_)) = 2 and - not node.getElement(1) instanceof SequenceNode + not node.getElement(1) instanceof Cfg::SequenceNode } override DataFlow::Node getUrlPatternArg() { result.asCfgNode() = node.getElement(0) } diff --git a/python/ql/lib/semmle/python/frameworks/Twisted.qll b/python/ql/lib/semmle/python/frameworks/Twisted.qll index 60aedd8fb582..6d32bf42ef11 100644 --- a/python/ql/lib/semmle/python/frameworks/Twisted.qll +++ b/python/ql/lib/semmle/python/frameworks/Twisted.qll @@ -182,7 +182,9 @@ private module Twisted { DataFlow::CfgNode { TwistedResourceRenderMethodReturn() { - this.asCfgNode() = any(TwistedResourceRenderMethod meth).getAReturnValueFlowNode() + exists(TwistedResourceRenderMethod meth, Return ret | + ret.getScope() = meth and this.asCfgNode().getNode() = ret.getValue() + ) } override DataFlow::Node getBody() { result = this } diff --git a/python/ql/lib/semmle/python/frameworks/Werkzeug.qll b/python/ql/lib/semmle/python/frameworks/Werkzeug.qll index d9150c8cfecd..d88171275da2 100644 --- a/python/ql/lib/semmle/python/frameworks/Werkzeug.qll +++ b/python/ql/lib/semmle/python/frameworks/Werkzeug.qll @@ -6,6 +6,7 @@ */ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.TaintTracking private import semmle.python.ApiGraphs @@ -221,9 +222,9 @@ module Werkzeug { DataFlow::Node value; HeaderWriteSubscript() { - exists(SubscriptNode subscript | + exists(Cfg::SubscriptNode subscript | this.asCfgNode() = subscript and - value.asCfgNode() = subscript.(DefinitionNode).getValue() and + value.asCfgNode() = subscript.(Cfg::DefinitionNode).getValue() and name.asCfgNode() = subscript.getIndex() and subscript.getObject() = instance().asCfgNode() ) diff --git a/python/ql/lib/semmle/python/frameworks/Yaml.qll b/python/ql/lib/semmle/python/frameworks/Yaml.qll index 670fad75e6e3..8c5601f5a71d 100644 --- a/python/ql/lib/semmle/python/frameworks/Yaml.qll +++ b/python/ql/lib/semmle/python/frameworks/Yaml.qll @@ -8,6 +8,7 @@ */ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.DataFlow private import semmle.python.Concepts private import semmle.python.ApiGraphs @@ -28,7 +29,7 @@ private module Yaml { * See https://pyyaml.org/wiki/PyYAMLDocumentation (you will have to scroll down). */ private class YamlLoadCall extends Decoding::Range, DataFlow::CallCfgNode { - override CallNode node; + override Cfg::CallNode node; string func_name; YamlLoadCall() { diff --git a/python/ql/lib/semmle/python/frameworks/Yarl.qll b/python/ql/lib/semmle/python/frameworks/Yarl.qll index a1c602e6016b..670075764332 100644 --- a/python/ql/lib/semmle/python/frameworks/Yarl.qll +++ b/python/ql/lib/semmle/python/frameworks/Yarl.qll @@ -4,6 +4,7 @@ */ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.TaintTracking private import semmle.python.Concepts @@ -111,7 +112,7 @@ module Yarl { } private predicate yarlUrlIsAbsoluteCall( - DataFlow::GuardNode g, ControlFlowNode node, boolean branch + DataFlow::GuardNode g, Cfg::ControlFlowNode node, boolean branch ) { exists(ClassInstantiation instance, DataFlow::MethodCallNode call | call.calls(instance, "is_absolute") and diff --git a/python/ql/lib/semmle/python/frameworks/internal/SubclassFinder.qll b/python/ql/lib/semmle/python/frameworks/internal/SubclassFinder.qll index 7916a54afcb5..831535e3b19d 100644 --- a/python/ql/lib/semmle/python/frameworks/internal/SubclassFinder.qll +++ b/python/ql/lib/semmle/python/frameworks/internal/SubclassFinder.qll @@ -11,6 +11,7 @@ private import semmle.python.dataflow.new.internal.ImportResolution private import semmle.python.ApiGraphs private import semmle.python.filters.Tests private import semmle.python.Module +private import semmle.python.controlflow.internal.Cfg as Cfg // very much inspired by the draft at https://github.com/github/codeql/pull/5632 module NotExposed { @@ -206,7 +207,7 @@ module NotExposed { string relevantName, Location loc ) { loc = mod.getLocation() and - exists(API::Node relevantClass, ControlFlowNode value | + exists(API::Node relevantClass, Cfg::ControlFlowNode value | relevantClass = newOrExistingModeling(spec).getASubclass*() and ImportResolution::module_export(mod, relevantName, def) and value = relevantClass.getAValueReachableFromSource().asCfgNode() and diff --git a/python/ql/lib/semmle/python/internal/CachedStages.qll b/python/ql/lib/semmle/python/internal/CachedStages.qll index 7379cc51372f..c1bc44945c8b 100644 --- a/python/ql/lib/semmle/python/internal/CachedStages.qll +++ b/python/ql/lib/semmle/python/internal/CachedStages.qll @@ -77,7 +77,7 @@ module Stages { or exists(any(AstExtended::AstNode n).getParentNode()) or - exists(any(AstExtended::AstNode n).getAFlowNode()) + exists(PyFlow::ControlFlowNode cfg, AstExtended::AstNode n | cfg.getNode() = n) or exists(any(PyFlow::BasicBlock b).getImmediateDominator()) or diff --git a/python/ql/lib/semmle/python/objects/Callables.qll b/python/ql/lib/semmle/python/objects/Callables.qll index 357779859bb8..c42393cc3c62 100644 --- a/python/ql/lib/semmle/python/objects/Callables.qll +++ b/python/ql/lib/semmle/python/objects/Callables.qll @@ -56,8 +56,9 @@ abstract class CallableObjectInternal extends ObjectInternal { /** A Python function. */ class PythonFunctionObjectInternal extends CallableObjectInternal, TPythonFunctionObject { override Function getScope() { - exists(CallableExpr expr | - this = TPythonFunctionObject(expr.getAFlowNode()) and + exists(CallableExpr expr, ControlFlowNode exprCfg | + exprCfg.getNode() = expr and + this = TPythonFunctionObject(exprCfg) and result = expr.getInnerScope() ) } @@ -160,10 +161,11 @@ class PythonFunctionObjectInternal extends CallableObjectInternal, TPythonFuncti } private BasicBlock blockReturningNone(Function func) { - exists(Return ret | + exists(Return ret, ControlFlowNode ret_ | not exists(ret.getValue()) and ret.getScope() = func and - result = ret.getAFlowNode().getBasicBlock() + ret_.getNode() = ret and + result = ret_.getBasicBlock() ) } diff --git a/python/ql/lib/semmle/python/objects/Classes.qll b/python/ql/lib/semmle/python/objects/Classes.qll index 998651a93bfd..2d30fb56ae31 100644 --- a/python/ql/lib/semmle/python/objects/Classes.qll +++ b/python/ql/lib/semmle/python/objects/Classes.qll @@ -113,8 +113,9 @@ abstract class ClassObjectInternal extends ObjectInternal { class PythonClassObjectInternal extends ClassObjectInternal, TPythonClassObject { /** Gets the scope for this Python class */ Class getScope() { - exists(ClassExpr expr | - this = TPythonClassObject(expr.getAFlowNode()) and + exists(ClassExpr expr, ControlFlowNode exprCfg | + exprCfg.getNode() = expr and + this = TPythonClassObject(exprCfg) and result = expr.getInnerScope() ) } diff --git a/python/ql/lib/semmle/python/objects/TObject.qll b/python/ql/lib/semmle/python/objects/TObject.qll index cfa8cb5aa07b..dd191467c19f 100644 --- a/python/ql/lib/semmle/python/objects/TObject.qll +++ b/python/ql/lib/semmle/python/objects/TObject.qll @@ -387,7 +387,7 @@ private PythonClassObjectInternal abcMetaClassObject() { private predicate neither_class_nor_static_method(Function f) { not exists(f.getADecorator()) or - exists(ControlFlowNode deco | deco = f.getADecorator().getAFlowNode() | + exists(ControlFlowNode deco | deco.getNode() = f.getADecorator() | exists(ObjectInternal o | PointsToInternal::pointsTo(deco, _, o, _) | o != ObjectInternal::staticMethod() and o != ObjectInternal::classMethod() diff --git a/python/ql/lib/semmle/python/pointsto/PointsTo.qll b/python/ql/lib/semmle/python/pointsto/PointsTo.qll index 8cc400c33b7b..41c6dafa46a4 100644 --- a/python/ql/lib/semmle/python/pointsto/PointsTo.qll +++ b/python/ql/lib/semmle/python/pointsto/PointsTo.qll @@ -711,7 +711,7 @@ private module InterModulePointsTo { ControlFlowNode f, PointsToContext context, ObjectInternal value, ControlFlowNode origin ) { exists(string name, ImportExpr i | - i.getAFlowNode() = f and + f.getNode() = i and i.getImportedModuleName() = name and PointsToInternal::module_imported_as(value, name) and origin = f and @@ -2118,8 +2118,9 @@ module Types { result.getBuiltin() = cls.getBuiltin().getBaseClass() and n = 0 or exists(Class pycls | pycls = cls.(PythonClassObjectInternal).getScope() | - exists(ObjectInternal base | - PointsToInternal::pointsTo(pycls.getBase(n).getAFlowNode(), _, base, _) + exists(ObjectInternal base, ControlFlowNode baseNode | + baseNode.getNode() = pycls.getBase(n) and + PointsToInternal::pointsTo(baseNode, _, base, _) | result = base and base != ObjectInternal::unknown() or @@ -2223,7 +2224,10 @@ module Types { } private ControlFlowNode decorator_call_callee(PythonClassObjectInternal cls) { - result = cls.getScope().getADecorator().getAFlowNode().(CallNode).getFunction() + exists(CallNode deco | + deco.getNode() = cls.getScope().getADecorator() and + result = deco.getFunction() + ) } private boolean has_six_add_metaclass(PythonClassObjectInternal cls) { @@ -2262,7 +2266,7 @@ module Types { } private EssaVariable metaclass_var(Class cls) { - result.getASourceUse() = cls.getMetaClass().getAFlowNode() + result.getASourceUse().getNode() = cls.getMetaClass() or major_version() = 2 and not exists(cls.getMetaClass()) and diff --git a/python/ql/lib/semmle/python/regexp/internal/ParseRegExp.qll b/python/ql/lib/semmle/python/regexp/internal/ParseRegExp.qll index d91c4bbd78c0..23031c30772c 100644 --- a/python/ql/lib/semmle/python/regexp/internal/ParseRegExp.qll +++ b/python/ql/lib/semmle/python/regexp/internal/ParseRegExp.qll @@ -3,6 +3,7 @@ */ import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.DataFlow private import semmle.python.Concepts as Concepts private import semmle.python.regex @@ -78,7 +79,7 @@ private module FindRegexMode { t.start() and exists(API::Node flag | flag_name = canonical_name(flag) and result = flag.asSource()) or - exists(BinaryExprNode binop, DataFlow::Node operand | + exists(Cfg::BinaryExprNode binop, DataFlow::Node operand | operand.getALocalSource() = re_flag_tracker(flag_name, t.continue()) and operand.asCfgNode() = binop.getAnOperand() and (binop.getOp() instanceof BitOr or binop.getOp() instanceof Add) and diff --git a/python/ql/lib/semmle/python/security/dataflow/ExceptionInfo.qll b/python/ql/lib/semmle/python/security/dataflow/ExceptionInfo.qll index e389dd3dd4d1..f7d87f17402e 100644 --- a/python/ql/lib/semmle/python/security/dataflow/ExceptionInfo.qll +++ b/python/ql/lib/semmle/python/security/dataflow/ExceptionInfo.qll @@ -3,6 +3,7 @@ import python import semmle.python.dataflow.new.DataFlow private import semmle.python.ApiGraphs +private import semmle.python.controlflow.internal.Cfg as Cfg /** * INTERNAL: Do not use. @@ -29,7 +30,7 @@ private class TracebackFunctionCall extends ExceptionInfo, DataFlow::CallCfgNode private class CaughtException extends ExceptionInfo { CaughtException() { this.asExpr() = any(ExceptStmt s).getName() and - this.asCfgNode() = any(EssaNodeDefinition def).getDefiningNode() + this.asCfgNode().(Cfg::NameNode).defines(_) } } diff --git a/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryCustomizations.qll index e3f18170f630..bca40652403a 100644 --- a/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryCustomizations.qll @@ -11,6 +11,7 @@ private import semmle.python.dataflow.new.RemoteFlowSources private import semmle.python.dataflow.new.BarrierGuards private import semmle.python.ApiGraphs private import semmle.python.frameworks.data.internal.ApiGraphModels +private import semmle.python.controlflow.internal.Cfg as Cfg /** * Provides default sources, sinks and sanitizers for detecting @@ -95,7 +96,7 @@ module ServerSideRequestForgery { class StringConstructionAsFullUrlControlSanitizer extends FullUrlControlSanitizer { StringConstructionAsFullUrlControlSanitizer() { // string concat - exists(BinaryExprNode add | + exists(Cfg::BinaryExprNode add | add.getOp() instanceof Add and add.getRight() = this.asCfgNode() and not add.getLeft().getNode().(StringLiteral).getText().toLowerCase() in [ @@ -104,7 +105,7 @@ module ServerSideRequestForgery { ) or // % formatting - exists(BinaryExprNode fmt | + exists(Cfg::BinaryExprNode fmt | fmt.getOp() instanceof Mod and fmt.getRight() = this.asCfgNode() and // detecting %-formatting is not super easy, so we simplify it to only handle @@ -155,7 +156,9 @@ module ServerSideRequestForgery { } } - private predicate stringRestriction(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) { + private predicate stringRestriction( + DataFlow::GuardNode g, Cfg::ControlFlowNode node, boolean branch + ) { exists(DataFlow::MethodCallNode call, DataFlow::Node strNode | call.asCfgNode() = g and strNode.asCfgNode() = node | diff --git a/python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll index 2dbe2c542aee..7dcf74d94c4e 100644 --- a/python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/TarSlipCustomizations.qll @@ -9,6 +9,7 @@ private import semmle.python.dataflow.new.DataFlow private import semmle.python.Concepts private import semmle.python.dataflow.new.BarrierGuards private import semmle.python.ApiGraphs +private import semmle.python.controlflow.internal.Cfg as Cfg /** * Provides default sources, sinks and sanitizers for detecting @@ -139,8 +140,8 @@ module TarSlip { * where `` is any function matching `"%path"`. * `info` is assumed to be a `TarInfo` instance. */ - predicate tarFileInfoSanitizer(DataFlow::GuardNode g, ControlFlowNode tarInfo, boolean branch) { - exists(CallNode call, AttrNode attr | + predicate tarFileInfoSanitizer(DataFlow::GuardNode g, Cfg::ControlFlowNode tarInfo, boolean branch) { + exists(Cfg::CallNode call, Cfg::AttrNode attr | g = call and // We must test the name of the tar info object. attr = call.getAnArg() and @@ -148,9 +149,9 @@ module TarSlip { attr.getObject() = tarInfo | // The assumption that any test that matches %path is a sanitizer might be too broad. - call.getAChild*().(AttrNode).getName().matches("%path") + call.getAChild*().(Cfg::AttrNode).getName().matches("%path") or - call.getAChild*().(NameNode).getId().matches("%path") + call.getAChild*().(Cfg::NameNode).getId().matches("%path") ) and branch = false } diff --git a/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll index 75a638fc3a42..38b05ecf05d1 100644 --- a/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll @@ -5,6 +5,7 @@ */ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.DataFlow private import semmle.python.Concepts private import semmle.python.ApiGraphs @@ -111,7 +112,7 @@ module UrlRedirect { // Url redirection is a problem only if the user controls the prefix of the URL. // TODO: This is a copy of the taint-sanitizer from the old points-to query, which doesn't // cover formatting. - exists(BinaryExprNode string_concat | string_concat.getOp() instanceof Add | + exists(Cfg::BinaryExprNode string_concat | string_concat.getOp() instanceof Add | string_concat.getRight() = this.asCfgNode() ) } diff --git a/python/ql/lib/semmle/python/types/ClassObject.qll b/python/ql/lib/semmle/python/types/ClassObject.qll index 8607141f16f9..1ed271467ef6 100644 --- a/python/ql/lib/semmle/python/types/ClassObject.qll +++ b/python/ql/lib/semmle/python/types/ClassObject.qll @@ -181,7 +181,7 @@ class ClassObject extends Object { ) } - ControlFlowNode declaredMetaClass() { result = this.getPyClass().getMetaClass().getAFlowNode() } + ControlFlowNode declaredMetaClass() { result.getNode() = this.getPyClass().getMetaClass() } /** Has type inference failed to compute the full class hierarchy for this class for the reason given. */ predicate failedInference(string reason) { Types::failedInference(this.theClass(), reason) } @@ -195,8 +195,9 @@ class ClassObject extends Object { * It is guaranteed that getProbableSingletonInstance() returns at most one Object for each ClassObject. */ Object getProbableSingletonInstance() { - exists(ControlFlowNodeWithPointsTo use, Expr origin | - use.refersTo(result, this, origin.getAFlowNode()) + exists(ControlFlowNodeWithPointsTo use, Expr origin, ControlFlowNode origin_ | + origin_.getNode() = origin and + use.refersTo(result, this, origin_) | this.hasStaticallyUniqueInstance() and /* Ensure that original expression will be executed only one. */ diff --git a/python/ql/lib/semmle/python/types/Exceptions.qll b/python/ql/lib/semmle/python/types/Exceptions.qll index ea2f20e67aab..0b388ca184a4 100644 --- a/python/ql/lib/semmle/python/types/Exceptions.qll +++ b/python/ql/lib/semmle/python/types/Exceptions.qll @@ -427,7 +427,7 @@ class ExceptFlowNodeWithPointsTo extends ExceptFlowNode { } private ControlFlowNodeWithPointsTo element_from_tuple_objectapi(Object tuple) { - exists(Tuple t | t = tuple.getOrigin() and result = t.getAnElt().getAFlowNode()) + exists(Tuple t | t = tuple.getOrigin() and result.getNode() = t.getAnElt()) } /** diff --git a/python/ql/lib/semmle/python/types/Extensions.qll b/python/ql/lib/semmle/python/types/Extensions.qll index 3cde11977d05..5a3a442b1f80 100644 --- a/python/ql/lib/semmle/python/types/Extensions.qll +++ b/python/ql/lib/semmle/python/types/Extensions.qll @@ -36,8 +36,8 @@ class RangeIterationVariableFact extends PointsToExtension { RangeIterationVariableFact() { exists(For f, ControlFlowNode iterable | iterable.getBasicBlock().dominates(this.(ControlFlowNode).getBasicBlock()) and - f.getIter().getAFlowNode() = iterable and - f.getTarget().getAFlowNode() = this and + iterable.getNode() = f.getIter() and + this.(ControlFlowNode).getNode() = f.getTarget() and exists(ObjectInternal range | PointsTo::pointsTo(iterable, _, range, _) and range.getClass() = ObjectInternal::builtin("range") diff --git a/python/ql/lib/semmle/python/types/FunctionObject.qll b/python/ql/lib/semmle/python/types/FunctionObject.qll index 7ff0f37ca9a3..d03f08623009 100644 --- a/python/ql/lib/semmle/python/types/FunctionObject.qll +++ b/python/ql/lib/semmle/python/types/FunctionObject.qll @@ -170,7 +170,7 @@ class PyFunctionObject extends FunctionObject { predicate unconditionallyReturnsParameter(int n) { exists(SsaVariable pvar | exists(Parameter p | p = this.getFunction().getArg(n) | - p.asName().getAFlowNode() = pvar.getDefinition() + pvar.getDefinition().getNode() = p.asName() ) and exists(NameNode rval | rval = pvar.getAUse() and diff --git a/python/ql/lib/semmle/python/types/Object.qll b/python/ql/lib/semmle/python/types/Object.qll index 4c88f46dde87..0dbd7de10f5b 100644 --- a/python/ql/lib/semmle/python/types/Object.qll +++ b/python/ql/lib/semmle/python/types/Object.qll @@ -337,7 +337,7 @@ class TupleObject extends SequenceObject { or this instanceof TupleNode or - exists(Function func | func.getVararg().getAFlowNode() = this) + exists(Function func | this.(ControlFlowNode).getNode() = func.getVararg()) } } @@ -352,7 +352,9 @@ module TupleObject { } class NonEmptyTupleObject extends TupleObject { - NonEmptyTupleObject() { exists(Function func | func.getVararg().getAFlowNode() = this) } + NonEmptyTupleObject() { + exists(Function func | this.(ControlFlowNode).getNode() = func.getVararg()) + } override boolean booleanValue() { result = true } } diff --git a/python/ql/lib/utils/test/dataflow/MaximalFlowTest.qll b/python/ql/lib/utils/test/dataflow/MaximalFlowTest.qll index cbd3b4c6aa51..f9b1b1c676d2 100644 --- a/python/ql/lib/utils/test/dataflow/MaximalFlowTest.qll +++ b/python/ql/lib/utils/test/dataflow/MaximalFlowTest.qll @@ -1,4 +1,5 @@ import python +private import semmle.python.controlflow.internal.Cfg as Cfg import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.internal.DataFlowPrivate import FlowTest @@ -23,7 +24,7 @@ import MakeTest> module MaximalFlowsConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node node) { exists(node.getLocation().getFile().getRelativePath()) and - not node.asCfgNode() instanceof CallNode and + not node.asCfgNode() instanceof Cfg::CallNode and not node.asCfgNode().getNode() instanceof Return and not node instanceof DataFlow::ParameterNode and not node instanceof DataFlow::PostUpdateNode and @@ -34,9 +35,9 @@ module MaximalFlowsConfig implements DataFlow::ConfigSig { predicate isSink(DataFlow::Node node) { exists(node.getLocation().getFile().getRelativePath()) and - not any(CallNode c).getArg(_) = node.asCfgNode() and + not any(Cfg::CallNode c).getArg(_) = node.asCfgNode() and not isArgumentNode(node, _, _) and - not node.asCfgNode().(NameNode).getId().matches("SINK%") and + not node.asCfgNode().(Cfg::NameNode).getId().matches("SINK%") and not DataFlow::localFlowStep(node, _) } } diff --git a/python/ql/lib/utils/test/dataflow/NormalDataflowTest.qll b/python/ql/lib/utils/test/dataflow/NormalDataflowTest.qll index 696b43d5f038..6c27cdc25239 100644 --- a/python/ql/lib/utils/test/dataflow/NormalDataflowTest.qll +++ b/python/ql/lib/utils/test/dataflow/NormalDataflowTest.qll @@ -1,4 +1,5 @@ import python +private import semmle.python.controlflow.internal.Cfg as Cfg import utils.test.dataflow.FlowTest import utils.test.dataflow.testConfig private import semmle.python.dataflow.new.internal.PrintNode @@ -19,7 +20,7 @@ query predicate missingAnnotationOnSink(Location location, string error, string TestConfig::isSink(sink) and // note: we only care about `SINK` and not `SINK_F`, so we have to reconstruct manually. exists(DataFlow::CallCfgNode call | - call.getFunction().asCfgNode().(NameNode).getId() = "SINK" and + call.getFunction().asCfgNode().(Cfg::NameNode).getId() = "SINK" and (sink = call.getArg(_) or sink = call.getArgByName(_)) ) and location = sink.getLocation() and diff --git a/python/ql/lib/utils/test/dataflow/NormalTaintTrackingTest.qll b/python/ql/lib/utils/test/dataflow/NormalTaintTrackingTest.qll index 753f8f61e137..df2f35c48fe0 100644 --- a/python/ql/lib/utils/test/dataflow/NormalTaintTrackingTest.qll +++ b/python/ql/lib/utils/test/dataflow/NormalTaintTrackingTest.qll @@ -1,4 +1,5 @@ import python +private import semmle.python.controlflow.internal.Cfg as Cfg import utils.test.dataflow.FlowTest import utils.test.dataflow.testTaintConfig private import semmle.python.dataflow.new.internal.PrintNode @@ -18,7 +19,7 @@ query predicate missingAnnotationOnSink(Location location, string error, string exists(DataFlow::Node sink | exists(DataFlow::CallCfgNode call | // note: we only care about `SINK` and not `SINK_F`, so we have to reconstruct manually. - call.getFunction().asCfgNode().(NameNode).getId() = "SINK" and + call.getFunction().asCfgNode().(Cfg::NameNode).getId() = "SINK" and (sink = call.getArg(_) or sink = call.getArgByName(_)) ) and location = sink.getLocation() and diff --git a/python/ql/lib/utils/test/dataflow/RoutingTest.qll b/python/ql/lib/utils/test/dataflow/RoutingTest.qll index e7ac4e872470..ffa52dbd550f 100644 --- a/python/ql/lib/utils/test/dataflow/RoutingTest.qll +++ b/python/ql/lib/utils/test/dataflow/RoutingTest.qll @@ -1,4 +1,5 @@ import python +private import semmle.python.controlflow.internal.Cfg as Cfg import semmle.python.dataflow.new.DataFlow import utils.test.InlineExpectationsTest private import semmle.python.dataflow.new.internal.PrintNode @@ -49,7 +50,7 @@ private string fromValue(DataFlow::Node fromNode) { pragma[inline] private string fromFunc(DataFlow::ArgumentNode fromNode) { - result = fromNode.getCall().getNode().(CallNode).getFunction().getNode().(Name).getId() + result = fromNode.getCall().getNode().(Cfg::CallNode).getFunction().getNode().(Name).getId() } pragma[inline] diff --git a/python/ql/lib/utils/test/dataflow/UnresolvedCalls.qll b/python/ql/lib/utils/test/dataflow/UnresolvedCalls.qll index a4dfb07ee90f..b6067257f979 100644 --- a/python/ql/lib/utils/test/dataflow/UnresolvedCalls.qll +++ b/python/ql/lib/utils/test/dataflow/UnresolvedCalls.qll @@ -1,15 +1,17 @@ import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.internal.PrintNode private import semmle.python.dataflow.new.internal.DataFlowPrivate as DataFlowPrivate private import semmle.python.ApiGraphs import utils.test.InlineExpectationsTest signature module UnresolvedCallExpectationsSig { - predicate unresolvedCall(CallNode call); + predicate unresolvedCall(Cfg::CallNode call); } module DefaultUnresolvedCallExpectations implements UnresolvedCallExpectationsSig { - predicate unresolvedCall(CallNode call) { + predicate unresolvedCall(Cfg::CallNode call) { + Cfg::isCanonicalAstNodeRepresentative(call) and not exists(DataFlowPrivate::DataFlowCall dfc | exists(dfc.getCallable()) and dfc.getNode() = call ) and @@ -24,7 +26,7 @@ module MakeUnresolvedCallExpectations { predicate hasActualResult(Location location, string element, string tag, string value) { exists(location.getFile().getRelativePath()) and - exists(CallNode call | Impl::unresolvedCall(call) | + exists(Cfg::CallNode call | Impl::unresolvedCall(call) | location = call.getLocation() and tag = "unresolved_call" and value = prettyExpr(call.getNode()) and diff --git a/python/ql/lib/utils/test/dataflow/testConfig.qll b/python/ql/lib/utils/test/dataflow/testConfig.qll index 552180eeaaf3..cfdf1e1a7c92 100644 --- a/python/ql/lib/utils/test/dataflow/testConfig.qll +++ b/python/ql/lib/utils/test/dataflow/testConfig.qll @@ -21,11 +21,12 @@ */ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg import semmle.python.dataflow.new.DataFlow module TestConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node node) { - node.(DataFlow::CfgNode).getNode().(NameNode).getId() = "SOURCE" + node.(DataFlow::CfgNode).getNode().(Cfg::NameNode).getId() = "SOURCE" or node.(DataFlow::CfgNode).getNode().getNode().(StringLiteral).getS() = "source" or @@ -37,7 +38,7 @@ module TestConfig implements DataFlow::ConfigSig { predicate isSink(DataFlow::Node node) { exists(DataFlow::CallCfgNode call | - call.getFunction().asCfgNode().(NameNode).getId() in ["SINK", "SINK_F"] and + call.getFunction().asCfgNode().(Cfg::NameNode).getId() in ["SINK", "SINK_F"] and (node = call.getArg(_) or node = call.getArgByName(_)) and not node = call.getArgByName("not_present_at_runtime") ) diff --git a/python/ql/lib/utils/test/dataflow/testTaintConfig.qll b/python/ql/lib/utils/test/dataflow/testTaintConfig.qll index c9770600eeb4..b784042901e0 100644 --- a/python/ql/lib/utils/test/dataflow/testTaintConfig.qll +++ b/python/ql/lib/utils/test/dataflow/testTaintConfig.qll @@ -21,12 +21,13 @@ */ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg import semmle.python.dataflow.new.DataFlow import semmle.python.dataflow.new.TaintTracking module TestConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node node) { - node.(DataFlow::CfgNode).getNode().(NameNode).getId() = "SOURCE" + node.(DataFlow::CfgNode).getNode().(Cfg::NameNode).getId() = "SOURCE" or node.(DataFlow::CfgNode).getNode().getNode().(StringLiteral).getS() = "source" or @@ -37,8 +38,8 @@ module TestConfig implements DataFlow::ConfigSig { } predicate isSink(DataFlow::Node node) { - exists(CallNode call | - call.getFunction().(NameNode).getId() in ["SINK", "SINK_F"] and + exists(Cfg::CallNode call | + call.getFunction().(Cfg::NameNode).getId() in ["SINK", "SINK_F"] and node.(DataFlow::CfgNode).getNode() = call.getAnArg() ) } diff --git a/python/ql/src/Classes/ClassAttributes.qll b/python/ql/src/Classes/ClassAttributes.qll index 4063bc7042ef..58f8ae5dffc9 100644 --- a/python/ql/src/Classes/ClassAttributes.qll +++ b/python/ql/src/Classes/ClassAttributes.qll @@ -48,9 +48,11 @@ class CheckClass extends ClassObject { self_dict = sub.getObject() or /* Indirect assignment via temporary variable */ - exists(SsaVariable v | - v.getAUse() = sub.getObject().getAFlowNode() and - v.getDefinition().(DefinitionNode).getValue() = self_dict.getAFlowNode() + exists(SsaVariable v, ControlFlowNode subObjCfg, ControlFlowNode selfDictCfg | + subObjCfg.getNode() = sub.getObject() and selfDictCfg.getNode() = self_dict + | + v.getAUse() = subObjCfg and + v.getDefinition().(DefinitionNode).getValue() = selfDictCfg ) ) and a.getATarget() = sub and @@ -62,9 +64,10 @@ class CheckClass extends ClassObject { pragma[nomagic] private predicate monkeyPatched(string name) { - exists(Attribute a | + exists(Attribute a, ControlFlowNode objCfg | + objCfg.getNode() = a.getObject() and a.getCtx() instanceof Store and - PointsTo::points_to(a.getObject().getAFlowNode(), _, this, _, _) and + PointsTo::points_to(objCfg, _, this, _, _) and a.getName() = name ) } @@ -84,9 +87,9 @@ class CheckClass extends ClassObject { } predicate interestingUndefined(SelfAttributeRead a) { - exists(string name | name = a.getName() | + exists(string name, ControlFlowNode aCfg | name = a.getName() and aCfg.getNode() = a | this.interestingContext(a, name) and - not this.definedInBlock(a.getAFlowNode().getBasicBlock(), name) + not this.definedInBlock(aCfg.getBasicBlock(), name) ) } @@ -109,8 +112,9 @@ class CheckClass extends ClassObject { pragma[nomagic] private predicate definitionInBlock(BasicBlock b, string name) { - exists(SelfAttributeStore sa | - sa.getAFlowNode().getBasicBlock() = b and + exists(SelfAttributeStore sa, ControlFlowNode saCfg | + saCfg.getNode() = sa and + saCfg.getBasicBlock() = b and sa.getName() = name and sa.getClass() = this.getPyClass() ) diff --git a/python/ql/src/Exceptions/CatchingBaseException.ql b/python/ql/src/Exceptions/CatchingBaseException.ql index 79174488760b..24d56f38afce 100644 --- a/python/ql/src/Exceptions/CatchingBaseException.ql +++ b/python/ql/src/Exceptions/CatchingBaseException.ql @@ -15,7 +15,9 @@ import python import semmle.python.ApiGraphs -predicate doesnt_reraise(ExceptStmt ex) { ex.getAFlowNode().getBasicBlock().reachesExit() } +predicate doesnt_reraise(ExceptStmt ex) { + exists(ControlFlowNode exCfg | exCfg.getNode() = ex | exCfg.getBasicBlock().reachesExit()) +} predicate catches_base_exception(ExceptStmt ex) { ex.getType() = API::builtin("BaseException").getAValueReachableFromSource().asExpr() diff --git a/python/ql/src/Exceptions/UnguardedNextInGenerator.ql b/python/ql/src/Exceptions/UnguardedNextInGenerator.ql index a6969218fddc..437a3f208df7 100644 --- a/python/ql/src/Exceptions/UnguardedNextInGenerator.ql +++ b/python/ql/src/Exceptions/UnguardedNextInGenerator.ql @@ -12,6 +12,8 @@ import python private import semmle.python.ApiGraphs +private import semmle.python.controlflow.internal.Cfg as Cfg +private import semmle.python.Flow as Flow API::Node iter() { result = API::builtin("iter") } @@ -19,17 +21,17 @@ API::Node next() { result = API::builtin("next") } API::Node stopIteration() { result = API::builtin("StopIteration") } -predicate call_to_iter(CallNode call, EssaVariable sequence) { - call = iter().getACall().asCfgNode() and +predicate call_to_iter(Flow::CallNode call, EssaVariable sequence) { + call.getNode() = iter().getACall().asCfgNode().(Cfg::CallNode).getNode() and call.getArg(0) = sequence.getAUse() } -predicate call_to_next(CallNode call, ControlFlowNode iter) { - call = next().getACall().asCfgNode() and +predicate call_to_next(Flow::CallNode call, Flow::ControlFlowNode iter) { + call.getNode() = next().getACall().asCfgNode().(Cfg::CallNode).getNode() and call.getArg(0) = iter } -predicate call_to_next_has_default(CallNode call) { +predicate call_to_next_has_default(Flow::CallNode call) { exists(call.getArg(1)) or exists(call.getArgByName("default")) } @@ -49,14 +51,14 @@ predicate iter_not_exhausted(EssaVariable iterator) { ) } -predicate stop_iteration_handled(CallNode call) { +predicate stop_iteration_handled(Flow::CallNode call) { exists(Try t | t.containsInScope(call.getNode()) and t.getAHandler().getType() = stopIteration().getAValueReachableFromSource().asExpr() ) } -from CallNode call +from Flow::CallNode call where call_to_next(call, _) and not call_to_next_has_default(call) and diff --git a/python/ql/src/Expressions/CallArgs.qll b/python/ql/src/Expressions/CallArgs.qll index 709915afbc61..43782a903dcb 100644 --- a/python/ql/src/Expressions/CallArgs.qll +++ b/python/ql/src/Expressions/CallArgs.qll @@ -116,7 +116,7 @@ FunctionValue get_function_or_initializer(Value func_or_cls) { predicate illegally_named_parameter_objectapi(Call call, Object func, string name) { not func.isC() and name = call.getANamedArgumentName() and - call.getAFlowNode() = get_a_call_objectapi(func) and + exists(ControlFlowNode callCfg | callCfg.getNode() = call | callCfg = get_a_call_objectapi(func)) and not get_function_or_initializer_objectapi(func).isLegalArgumentName(name) } @@ -124,7 +124,7 @@ predicate illegally_named_parameter_objectapi(Call call, Object func, string nam predicate illegally_named_parameter(Call call, Value func, string name) { not func.isBuiltin() and name = call.getANamedArgumentName() and - call.getAFlowNode() = get_a_call(func) and + exists(ControlFlowNode callCfg | callCfg.getNode() = call | callCfg = get_a_call(func)) and not get_function_or_initializer(func).isLegalArgumentName(name) } @@ -146,7 +146,9 @@ predicate too_few_args_objectapi(Call call, Object callable, int limit) { call = func.getAMethodCall().getNode() and limit = func.minParameters() - 1 or callable instanceof ClassObject and - call.getAFlowNode() = get_a_call_objectapi(callable) and + exists(ControlFlowNode callCfg | callCfg.getNode() = call | + callCfg = get_a_call_objectapi(callable) + ) and limit = func.minParameters() - 1 ) } @@ -172,7 +174,7 @@ predicate too_few_args(Call call, Value callable, int limit) { call = func.getAMethodCall().getNode() and limit = func.minParameters() - 1 or callable instanceof ClassValue and - call.getAFlowNode() = get_a_call(callable) and + exists(ControlFlowNode callCfg | callCfg.getNode() = call | callCfg = get_a_call(callable)) and limit = func.minParameters() - 1 ) } @@ -191,7 +193,9 @@ predicate too_many_args_objectapi(Call call, Object callable, int limit) { call = func.getAMethodCall().getNode() and limit = func.maxParameters() - 1 or callable instanceof ClassObject and - call.getAFlowNode() = get_a_call_objectapi(callable) and + exists(ControlFlowNode callCfg | callCfg.getNode() = call | + callCfg = get_a_call_objectapi(callable) + ) and limit = func.maxParameters() - 1 ) and positional_arg_count_for_call_objectapi(call, callable) > limit @@ -211,7 +215,7 @@ predicate too_many_args(Call call, Value callable, int limit) { call = func.getAMethodCall().getNode() and limit = func.maxParameters() - 1 or callable instanceof ClassValue and - call.getAFlowNode() = get_a_call(callable) and + exists(ControlFlowNode callCfg | callCfg.getNode() = call | callCfg = get_a_call(callable)) and limit = func.maxParameters() - 1 ) and positional_arg_count_for_call(call, callable) > limit diff --git a/python/ql/src/Expressions/DuplicateKeyInDictionaryLiteral.ql b/python/ql/src/Expressions/DuplicateKeyInDictionaryLiteral.ql index 166eae635fad..0c55a2ece587 100644 --- a/python/ql/src/Expressions/DuplicateKeyInDictionaryLiteral.ql +++ b/python/ql/src/Expressions/DuplicateKeyInDictionaryLiteral.ql @@ -36,11 +36,15 @@ where exists(string s | dict_key(d, k1, s) and dict_key(d, k2, s) and k1 != k2) and ( exists(BasicBlock b, int i1, int i2 | - k1.getAFlowNode() = b.getNode(i1) and - k2.getAFlowNode() = b.getNode(i2) and + b.getNode(i1).getNode() = k1 and + b.getNode(i2).getNode() = k2 and i1 < i2 ) or - k1.getAFlowNode().getBasicBlock().strictlyDominates(k2.getAFlowNode().getBasicBlock()) + exists(ControlFlowNode k1Cfg, ControlFlowNode k2Cfg | + k1Cfg.getNode() = k1 and k2Cfg.getNode() = k2 + | + k1Cfg.getBasicBlock().strictlyDominates(k2Cfg.getBasicBlock()) + ) ) select k1, "Dictionary key " + repr(k1) + " is subsequently $@.", k2, "overwritten" diff --git a/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll b/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll index d98286d85faf..a5e0379685a3 100644 --- a/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll +++ b/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll @@ -98,16 +98,18 @@ private predicate brace_pair(PossibleAdvancedFormatString fmt, int start, int en } private predicate advanced_format_call(Call format_expr, PossibleAdvancedFormatString fmt, int args) { - exists(CallNode call | call = format_expr.getAFlowNode() | + exists(CallNode call, ControlFlowNode fmtCfg | + call.getNode() = format_expr and fmtCfg.getNode() = fmt + | call.getFunction().(ControlFlowNodeWithPointsTo).pointsTo(Value::named("format")) and - call.getArg(0).(ControlFlowNodeWithPointsTo).pointsTo(_, fmt.getAFlowNode()) and + call.getArg(0).(ControlFlowNodeWithPointsTo).pointsTo(_, fmtCfg) and args = count(format_expr.getAnArg()) - 1 or call.getFunction() .(AttrNode) .getObject("format") .(ControlFlowNodeWithPointsTo) - .pointsTo(_, fmt.getAFlowNode()) and + .pointsTo(_, fmtCfg) and args = count(format_expr.getAnArg()) ) } diff --git a/python/ql/src/Expressions/IncorrectComparisonUsingIs.ql b/python/ql/src/Expressions/IncorrectComparisonUsingIs.ql index fa0ca14669f6..a7336c625472 100644 --- a/python/ql/src/Expressions/IncorrectComparisonUsingIs.ql +++ b/python/ql/src/Expressions/IncorrectComparisonUsingIs.ql @@ -15,7 +15,7 @@ import python /** Holds if the comparison `comp` uses `is` or `is not` (represented as `op`) to compare its `left` and `right` arguments. */ predicate comparison_using_is(Compare comp, ControlFlowNode left, Cmpop op, ControlFlowNode right) { - exists(CompareNode fcomp | fcomp = comp.getAFlowNode() | + exists(CompareNode fcomp | fcomp.getNode() = comp | fcomp.operands(left, op, right) and (op instanceof Is or op instanceof IsNot) ) diff --git a/python/ql/src/Expressions/IsComparisons.qll b/python/ql/src/Expressions/IsComparisons.qll index cb052ceca765..ee49f6c3337a 100644 --- a/python/ql/src/Expressions/IsComparisons.qll +++ b/python/ql/src/Expressions/IsComparisons.qll @@ -5,7 +5,7 @@ private import LegacyPointsTo /** Holds if the comparison `comp` uses `is` or `is not` (represented as `op`) to compare its `left` and `right` arguments. */ predicate comparison_using_is(Compare comp, ControlFlowNode left, Cmpop op, ControlFlowNode right) { - exists(CompareNode fcomp | fcomp = comp.getAFlowNode() | + exists(CompareNode fcomp | fcomp.getNode() = comp | fcomp.operands(left, op, right) and (op instanceof Is or op instanceof IsNot) ) diff --git a/python/ql/src/Expressions/TruncatedDivision.ql b/python/ql/src/Expressions/TruncatedDivision.ql index c731a21f7d26..d63ac056d3c2 100644 --- a/python/ql/src/Expressions/TruncatedDivision.ql +++ b/python/ql/src/Expressions/TruncatedDivision.ql @@ -19,7 +19,7 @@ where // Only relevant for Python 2, as all later versions implement true division major_version() = 2 and exists(BinaryExprNode bin, Value lval, Value rval | - bin = div.getAFlowNode() and + bin.getNode() = div and bin.getNode().getOp() instanceof Div and bin.getLeft().(ControlFlowNodeWithPointsTo).pointsTo(lval, left) and lval.getClass() = ClassValue::int_() and diff --git a/python/ql/src/Expressions/UseofApply.ql b/python/ql/src/Expressions/UseofApply.ql index f1068eca837c..6fa5c9817224 100644 --- a/python/ql/src/Expressions/UseofApply.ql +++ b/python/ql/src/Expressions/UseofApply.ql @@ -11,8 +11,9 @@ import python private import semmle.python.ApiGraphs +private import semmle.python.controlflow.internal.Cfg as Cfg -from CallNode call +from Cfg::CallNode call where major_version() = 2 and call = API::builtin("apply").getACall().asCfgNode() diff --git a/python/ql/src/Functions/ExplicitReturnInInit.ql b/python/ql/src/Functions/ExplicitReturnInInit.ql index f1300afbfd0a..25fc799fafae 100644 --- a/python/ql/src/Functions/ExplicitReturnInInit.ql +++ b/python/ql/src/Functions/ExplicitReturnInInit.ql @@ -19,7 +19,9 @@ where exists(Function init | init.isInitMethod() and r.getScope() = init) and r.getValue() = rv and not rv.pointsTo(Value::none_()) and - not exists(FunctionValue f | f.getACall() = rv.getAFlowNode() | f.neverReturns()) and + not exists(FunctionValue f, ControlFlowNode rvCfg | rvCfg.getNode() = rv | + f.getACall() = rvCfg and f.neverReturns() + ) and // to avoid double reporting, don't trigger if returning result from other __init__ function not exists(Attribute meth | meth = rv.(Call).getFunc() | meth.getName() = "__init__") select r, "Explicit return in __init__ method." diff --git a/python/ql/src/Functions/ReturnValueIgnored.ql b/python/ql/src/Functions/ReturnValueIgnored.ql index 3716b989d891..83af6304cb30 100644 --- a/python/ql/src/Functions/ReturnValueIgnored.ql +++ b/python/ql/src/Functions/ReturnValueIgnored.ql @@ -69,7 +69,12 @@ where returns_meaningful_value(callee) and not wrapped_in_try_except(call) and exists(int unused | - unused = count(ExprStmt e | e.getValue().getAFlowNode() = callee.getACall()) and + unused = + count(ExprStmt e | + exists(ControlFlowNode eValCfg | eValCfg.getNode() = e.getValue() | + eValCfg = callee.getACall() + ) + ) and total = count(callee.getACall()) | percentage_used = (100.0 * (total - unused) / total).floor() diff --git a/python/ql/src/Functions/SignatureOverriddenMethod.ql b/python/ql/src/Functions/SignatureOverriddenMethod.ql index 15b3fa706401..7ecced29c60f 100644 --- a/python/ql/src/Functions/SignatureOverriddenMethod.ql +++ b/python/ql/src/Functions/SignatureOverriddenMethod.ql @@ -15,6 +15,7 @@ import python import semmle.python.dataflow.new.DataFlow import semmle.python.dataflow.new.internal.DataFlowDispatch +private import semmle.python.controlflow.internal.Cfg as Cfg import codeql.util.Option /** Holds if `base` is overridden by `sub` */ @@ -143,7 +144,7 @@ predicate ignore(Function f) { /** Gets a function that `call` may resolve to. */ Function resolveCall(Call call) { - exists(DataFlowCall dfc | call = dfc.getNode().(CallNode).getNode() | + exists(DataFlowCall dfc | call = dfc.getNode().(Cfg::CallNode).getNode() | result = viableCallable(dfc).(DataFlowFunction).getScope() ) } diff --git a/python/ql/src/Resources/FileNotAlwaysClosedQuery.qll b/python/ql/src/Resources/FileNotAlwaysClosedQuery.qll index 9d91e4f523c2..c5c4795eeccf 100644 --- a/python/ql/src/Resources/FileNotAlwaysClosedQuery.qll +++ b/python/ql/src/Resources/FileNotAlwaysClosedQuery.qll @@ -3,6 +3,7 @@ import python import semmle.python.dataflow.new.internal.DataFlowDispatch import semmle.python.ApiGraphs +private import semmle.python.controlflow.internal.Cfg as Cfg /** A CFG node where a file is opened. */ abstract class FileOpenSource extends DataFlow::CfgNode { } @@ -64,12 +65,14 @@ abstract class FileClose extends DataFlow::CfgNode { } } -private predicate bbSuccessor(BasicBlock src, BasicBlock sink) { sink = src.getASuccessor() } +private predicate bbSuccessor(Cfg::BasicBlock src, Cfg::BasicBlock sink) { + sink = src.getASuccessor() +} -private predicate bbReachableStrict(BasicBlock src, BasicBlock sink) = +private predicate bbReachableStrict(Cfg::BasicBlock src, Cfg::BasicBlock sink) = fastTC(bbSuccessor/2)(src, sink) -private predicate bbReachableRefl(BasicBlock src, BasicBlock sink) { +private predicate bbReachableRefl(Cfg::BasicBlock src, Cfg::BasicBlock sink) { bbReachableStrict(src, sink) or src = sink } diff --git a/python/ql/src/Resources/FileOpen.qll b/python/ql/src/Resources/FileOpen.qll index dd952e732d42..1daecb6d0334 100644 --- a/python/ql/src/Resources/FileOpen.qll +++ b/python/ql/src/Resources/FileOpen.qll @@ -138,12 +138,12 @@ predicate function_opens_file(FunctionValue f) { f = Value::named("open") or exists(EssaVariable v, Return ret | ret.getScope() = f.getScope() | - ret.getValue().getAFlowNode() = v.getAUse() and + v.getNode() = ret.getValue().getAUse() and var_is_open(v, _) ) or exists(Return ret, FunctionValue callee | ret.getScope() = f.getScope() | - ret.getValue().getAFlowNode() = callee.getACall() and + callee.getNode() = ret.getValue().getACall() and function_opens_file(callee) ) } diff --git a/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIs.qll b/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIs.qll index 9c12845a0cad..1aa1e74cf132 100644 --- a/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIs.qll +++ b/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIs.qll @@ -10,6 +10,7 @@ private import semmle.python.dataflow.new.RemoteFlowSources private import semmle.python.ApiGraphs private import semmle.python.dataflow.new.internal.DataFlowPrivate as DataFlowPrivate private import semmle.python.dataflow.new.internal.TaintTrackingPrivate as TaintTrackingPrivate +private import semmle.python.controlflow.internal.Cfg as Cfg /** * An external API that is considered "safe" from a security perspective. @@ -71,7 +72,7 @@ string apiNodeToStringRepr(API::Node node) { ) } -predicate resolvedCall(CallNode call) { +predicate resolvedCall(Cfg::CallNode call) { DataFlowPrivate::resolveCall(call, _, _) or DataFlowPrivate::resolveClassCall(call, _) } diff --git a/python/ql/src/Security/CWE-079/Jinja2WithoutEscaping.ql b/python/ql/src/Security/CWE-079/Jinja2WithoutEscaping.ql index fd03ba433a10..fb5ef283e65a 100644 --- a/python/ql/src/Security/CWE-079/Jinja2WithoutEscaping.ql +++ b/python/ql/src/Security/CWE-079/Jinja2WithoutEscaping.ql @@ -14,6 +14,7 @@ import python import semmle.python.dataflow.new.DataFlow import semmle.python.ApiGraphs +private import semmle.python.controlflow.internal.Cfg as Cfg /* * Jinja 2 Docs: @@ -36,8 +37,8 @@ private API::Node jinja2EnvironmentOrTemplate() { from API::CallNode call where call = jinja2EnvironmentOrTemplate().getACall() and - not exists(call.asCfgNode().(CallNode).getNode().getStarargs()) and - not exists(call.asCfgNode().(CallNode).getNode().getKwargs()) and + not exists(call.asCfgNode().(Cfg::CallNode).getNode().getStarargs()) and + not exists(call.asCfgNode().(Cfg::CallNode).getNode().getKwargs()) and ( not exists(call.getArgByName("autoescape")) or diff --git a/python/ql/src/Security/CWE-327/PyOpenSSL.qll b/python/ql/src/Security/CWE-327/PyOpenSSL.qll index e2571195bfa0..7ce3276598c8 100644 --- a/python/ql/src/Security/CWE-327/PyOpenSSL.qll +++ b/python/ql/src/Security/CWE-327/PyOpenSSL.qll @@ -5,6 +5,7 @@ private import python private import semmle.python.ApiGraphs +private import semmle.python.controlflow.internal.Cfg as Cfg import TlsLibraryModel class PyOpenSslContextCreation extends ContextCreation, DataFlow::CallCfgNode { @@ -37,10 +38,10 @@ class ConnectionCall extends ConnectionCreation, DataFlow::CallCfgNode { // This cannot be used to unrestrict, // see https://www.pyopenssl.org/en/stable/api/ssl.html#OpenSSL.SSL.Context.set_options class SetOptionsCall extends ProtocolRestriction, DataFlow::CallCfgNode { - SetOptionsCall() { node.getFunction().(AttrNode).getName() = "set_options" } + SetOptionsCall() { node.getFunction().(Cfg::AttrNode).getName() = "set_options" } override DataFlow::CfgNode getContext() { - result.getNode() = node.getFunction().(AttrNode).getObject() + result.getNode() = node.getFunction().(Cfg::AttrNode).getObject() } override ProtocolVersion getRestriction() { diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll index c3fd0366436d..5f35590bacf5 100644 --- a/python/ql/src/Security/CWE-327/Ssl.qll +++ b/python/ql/src/Security/CWE-327/Ssl.qll @@ -5,6 +5,7 @@ private import python private import semmle.python.ApiGraphs +private import semmle.python.controlflow.internal.Cfg as Cfg import TlsLibraryModel class SslContextCreation extends ContextCreation, DataFlow::CallCfgNode { @@ -53,7 +54,7 @@ class OptionsAugOr extends ProtocolRestriction, DataFlow::CfgNode { ProtocolVersion restriction; OptionsAugOr() { - exists(AugAssign aa, AttrNode attr, Expr flag | + exists(AugAssign aa, Cfg::AttrNode attr, Expr flag | aa.getOperation().getOp() instanceof BitOr and aa.getTarget() = attr.getNode() and attr.getName() = "options" and @@ -80,7 +81,7 @@ class OptionsAugAndNot extends ProtocolUnrestriction, DataFlow::CfgNode { ProtocolVersion restriction; OptionsAugAndNot() { - exists(AugAssign aa, AttrNode attr, Expr flag, UnaryExpr notFlag | + exists(AugAssign aa, Cfg::AttrNode attr, Expr flag, UnaryExpr notFlag | aa.getOperation().getOp() instanceof BitAnd and aa.getTarget() = attr.getNode() and attr.getName() = "options" and diff --git a/python/ql/src/Security/CWE-798/HardcodedCredentials.ql b/python/ql/src/Security/CWE-798/HardcodedCredentials.ql index 1e7b4452a9a6..28b0e5da8c54 100644 --- a/python/ql/src/Security/CWE-798/HardcodedCredentials.ql +++ b/python/ql/src/Security/CWE-798/HardcodedCredentials.ql @@ -19,6 +19,7 @@ import semmle.python.filters.Tests private import semmle.python.dataflow.new.internal.DataFlowDispatch as DataFlowDispatch private import semmle.python.dataflow.new.internal.Builtins::Builtins as Builtins private import semmle.python.frameworks.data.ModelsAsData +private import semmle.python.controlflow.internal.Cfg as Cfg bindingset[char, fraction] predicate fewer_characters_than(StringLiteral str, string char, float fraction) { @@ -48,7 +49,7 @@ predicate capitalized_word(StringLiteral str) { str.getText().regexpMatch("[A-Z] predicate format_string(StringLiteral str) { str.getText().matches("%{%}%") } -predicate maybeCredential(ControlFlowNode f) { +predicate maybeCredential(Cfg::ControlFlowNode f) { /* A string that is not too short and unlikely to be text or an identifier. */ exists(StringLiteral str | str = f.getNode() | /* At least 10 characters */ @@ -94,9 +95,9 @@ class CredentialSink extends DataFlow::Node { this.(DataFlow::ArgumentNode).argumentOf(_, pos) ) or - exists(Keyword k | k.getArg() = name and k.getValue().getAFlowNode() = this.asCfgNode()) + exists(Keyword k | k.getArg() = name and this.asCfgNode().getNode() = k.getValue()) or - exists(CompareNode cmp, NameNode n | n.getId() = name | + exists(Cfg::CompareNode cmp, Cfg::NameNode n | n.getId() = name | cmp.operands(this.asCfgNode(), any(Eq eq), n) or cmp.operands(n, any(Eq eq), this.asCfgNode()) diff --git a/python/ql/src/Statements/IterableStringOrSequence.ql b/python/ql/src/Statements/IterableStringOrSequence.ql index d1c4a507f0d1..ad8b6beab290 100644 --- a/python/ql/src/Statements/IterableStringOrSequence.ql +++ b/python/ql/src/Statements/IterableStringOrSequence.ql @@ -25,7 +25,7 @@ from For loop, ControlFlowNodeWithPointsTo iter, Value str, Value seq, ControlFlowNode seq_origin, ControlFlowNode str_origin where - loop.getIter().getAFlowNode() = iter and + iter.getNode() = loop.getIter() and iter.pointsTo(str, str_origin) and iter.pointsTo(seq, seq_origin) and has_string_type(str) and diff --git a/python/ql/src/Statements/ModificationOfLocals.ql b/python/ql/src/Statements/ModificationOfLocals.ql index f32ddcf78849..29e08f80776c 100644 --- a/python/ql/src/Statements/ModificationOfLocals.ql +++ b/python/ql/src/Statements/ModificationOfLocals.ql @@ -13,24 +13,25 @@ import python private import semmle.python.ApiGraphs +private import semmle.python.controlflow.internal.Cfg as Cfg -predicate originIsLocals(ControlFlowNode n) { +predicate originIsLocals(Cfg::ControlFlowNode n) { API::builtin("locals").getReturn().getAValueReachableFromSource().asCfgNode() = n } -predicate modification_of_locals(ControlFlowNode f) { - originIsLocals(f.(SubscriptNode).getObject()) and +predicate modification_of_locals(Cfg::ControlFlowNode f) { + originIsLocals(f.(Cfg::SubscriptNode).getObject()) and (f.isStore() or f.isDelete()) or - exists(string mname, AttrNode attr | - attr = f.(CallNode).getFunction() and + exists(string mname, Cfg::AttrNode attr | + attr = f.(Cfg::CallNode).getFunction() and originIsLocals(attr.getObject(mname)) | mname in ["pop", "popitem", "update", "clear"] ) } -from AstNode a, ControlFlowNode f +from AstNode a, Cfg::ControlFlowNode f where modification_of_locals(f) and a = f.getNode() and diff --git a/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql b/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql index c4deb4e64277..a9c5a5fbbd98 100644 --- a/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql +++ b/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql @@ -15,7 +15,7 @@ import python predicate loop_variable_ssa(For f, Variable v, SsaVariable s) { - f.getTarget().getAFlowNode() = s.getDefinition() and v = s.getVariable() + s.getDefinition().getNode() = f.getTarget() and v = s.getVariable() } predicate variableUsedInNestedLoops(For inner, For outer, Variable v, Name n) { diff --git a/python/ql/src/Statements/NonIteratorInForLoop.ql b/python/ql/src/Statements/NonIteratorInForLoop.ql index f8e6e51b55ff..b0cbc71130d0 100644 --- a/python/ql/src/Statements/NonIteratorInForLoop.ql +++ b/python/ql/src/Statements/NonIteratorInForLoop.ql @@ -16,7 +16,7 @@ private import LegacyPointsTo from For loop, ControlFlowNodeWithPointsTo iter, Value v, ClassValue t, ControlFlowNode origin where - loop.getIter().getAFlowNode() = iter and + iter.getNode() = loop.getIter() and iter.pointsTo(_, v, origin) and v.getClass() = t and not t.isIterable() and diff --git a/python/ql/src/Statements/SideEffectInAssert.ql b/python/ql/src/Statements/SideEffectInAssert.ql index 7ac96030c04e..c58d3947424e 100644 --- a/python/ql/src/Statements/SideEffectInAssert.ql +++ b/python/ql/src/Statements/SideEffectInAssert.ql @@ -14,6 +14,7 @@ import python private import semmle.python.ApiGraphs +private import semmle.python.controlflow.internal.Cfg as Cfg predicate func_with_side_effects(Expr e) { exists(string name | name = e.(Attribute).getName() or name = e.(Name).getId() | @@ -24,11 +25,13 @@ predicate func_with_side_effects(Expr e) { } predicate call_with_side_effect(Call e) { - e.getAFlowNode() = - API::moduleImport("subprocess") - .getMember(["call", "check_call", "check_output"]) - .getACall() - .asCfgNode() + exists(Cfg::ControlFlowNode eCfg | eCfg.getNode() = e | + eCfg = + API::moduleImport("subprocess") + .getMember(["call", "check_call", "check_output"]) + .getACall() + .asCfgNode() + ) } predicate probable_side_effect(Expr e) { diff --git a/python/ql/src/Statements/UseOfExit.ql b/python/ql/src/Statements/UseOfExit.ql index 2310a839f67b..88a4f1ff7774 100644 --- a/python/ql/src/Statements/UseOfExit.ql +++ b/python/ql/src/Statements/UseOfExit.ql @@ -13,8 +13,9 @@ import python private import semmle.python.ApiGraphs +private import semmle.python.controlflow.internal.Cfg as Cfg -from CallNode call, string name +from Cfg::CallNode call, string name where name = ["exit", "quit"] and call = API::builtin(name).getACall().asCfgNode() diff --git a/python/ql/src/Variables/Definition.qll b/python/ql/src/Variables/Definition.qll index be8c9490788c..9bd7130957b6 100644 --- a/python/ql/src/Variables/Definition.qll +++ b/python/ql/src/Variables/Definition.qll @@ -133,7 +133,11 @@ class ListComprehensionDeclaration extends ListComp { major_version() = 2 and this.getIterationVariable(_).getId() = result.getId() and result.getScope() = this.getScope() and - this.getAFlowNode().strictlyReaches(result.getAFlowNode()) and + exists(ControlFlowNode thisCfg, ControlFlowNode resultCfg | + thisCfg.getNode() = this and resultCfg.getNode() = result + | + thisCfg.strictlyReaches(resultCfg) + ) and result.isUse() } diff --git a/python/ql/src/Variables/LeakingListComprehension.ql b/python/ql/src/Variables/LeakingListComprehension.ql index 9b98fb43a313..a9baa21661da 100644 --- a/python/ql/src/Variables/LeakingListComprehension.ql +++ b/python/ql/src/Variables/LeakingListComprehension.ql @@ -13,18 +13,21 @@ import python import Definition -from ListComprehensionDeclaration l, Name use, Name defn +from + ListComprehensionDeclaration l, Name use, Name defn, ControlFlowNode lCfg, ControlFlowNode useCfg where use = l.getALeakedVariableUse() and defn = l.getDefinition() and - l.getAFlowNode().strictlyReaches(use.getAFlowNode()) and + lCfg.getNode() = l and + useCfg.getNode() = use and + lCfg.strictlyReaches(useCfg) and /* Make sure we aren't in a loop, as the variable may be redefined */ - not use.getAFlowNode().strictlyReaches(l.getAFlowNode()) and + not useCfg.strictlyReaches(lCfg) and not l.contains(use) and not use.deletes(_) and not exists(SsaVariable v | - v.getAUse() = use.getAFlowNode() and - not v.getDefinition().strictlyDominates(l.getAFlowNode()) + v.getAUse() = useCfg and + not v.getDefinition().strictlyDominates(lCfg) ) select use, use.getId() + " may have a different value in Python 3, as the $@ will not be in scope.", defn, diff --git a/python/ql/src/Variables/Loop.qll b/python/ql/src/Variables/Loop.qll index c7749fe476bf..e7c189cac354 100644 --- a/python/ql/src/Variables/Loop.qll +++ b/python/ql/src/Variables/Loop.qll @@ -26,8 +26,11 @@ private Stmt loop_probably_defines(Variable v) { /** Holds if the variable used by `use` is probably defined in a loop */ predicate probably_defined_in_loop(Name use) { - exists(Stmt loop | loop = loop_probably_defines(use.getVariable()) | - loop.getAFlowNode().strictlyReaches(use.getAFlowNode()) + exists(Stmt loop, ControlFlowNode loopCfg, ControlFlowNode useCfg | + loop = loop_probably_defines(use.getVariable()) and + loopCfg.getNode() = loop and + useCfg.getNode() = use and + loopCfg.strictlyReaches(useCfg) ) } diff --git a/python/ql/src/Variables/MultiplyDefined.ql b/python/ql/src/Variables/MultiplyDefined.ql index 3c26ff0b1eb1..ce8b5b316c21 100644 --- a/python/ql/src/Variables/MultiplyDefined.ql +++ b/python/ql/src/Variables/MultiplyDefined.ql @@ -24,8 +24,8 @@ predicate multiply_defined(AstNode asgn1, AstNode asgn2, Variable v) { forex(Definition def, Definition redef | def.getVariable() = v and - def = asgn1.getAFlowNode() and - redef = asgn2.getAFlowNode() + def.getNode() = asgn1 and + redef.getNode() = asgn2 | def.isUnused() and def.getARedef() = redef and diff --git a/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariable.ql b/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariable.ql index d252742d67c2..f74fd4970ee4 100644 --- a/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariable.ql +++ b/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariable.ql @@ -88,7 +88,9 @@ predicate implicit_repeat(For f) { * E.g. gets `x` from `{ y for y in x }`. */ ControlFlowNode get_comp_iterable(For f) { - exists(Comp c | c.getFunction().getStmt(0) = f | c.getAFlowNode().getAPredecessor() = result) + exists(Comp c, ControlFlowNode cCfg | + c.getFunction().getStmt(0) = f and cCfg.getNode() = c and cCfg.getAPredecessor() = result + ) } from For f, Variable v, string msg diff --git a/python/ql/src/Variables/Undefined.qll b/python/ql/src/Variables/Undefined.qll index 42437a81340b..b320c2040b2d 100644 --- a/python/ql/src/Variables/Undefined.qll +++ b/python/ql/src/Variables/Undefined.qll @@ -19,9 +19,10 @@ private predicate loop_entry_variables(EssaVariable pred, EssaVariable succ) { private predicate loop_entry_edge(BasicBlock pred, BasicBlock loop) { pred = loop.getAPredecessor() and pred = loop.getImmediateDominator() and - exists(Stmt s | + exists(Stmt s, ControlFlowNode sCfg | loop_probably_executes_at_least_once(s) and - s.getAFlowNode().getBasicBlock() = loop + sCfg.getNode() = s and + sCfg.getBasicBlock() = loop ) } diff --git a/python/ql/src/Variables/UndefinedGlobal.ql b/python/ql/src/Variables/UndefinedGlobal.ql index 404ac64aa5a0..0c54b444ce30 100644 --- a/python/ql/src/Variables/UndefinedGlobal.ql +++ b/python/ql/src/Variables/UndefinedGlobal.ql @@ -27,7 +27,7 @@ predicate guarded_against_name_error(Name u) { | globals.getFunc().(Name).getId() = "globals" and guard.controls(controlled, _) and - controlled.contains(u.getAFlowNode()) + exists(ControlFlowNode uCfg | uCfg.getNode() = u | controlled.contains(uCfg)) ) } @@ -101,18 +101,18 @@ predicate undefined_use(Name u) { } private predicate first_use_in_a_block(Name use) { - exists(GlobalVariable v, BasicBlock b, int i | - i = min(int j | b.getNode(j).getNode() = v.getALoad()) and b.getNode(i) = use.getAFlowNode() + exists(GlobalVariable v, BasicBlock b, int i, ControlFlowNode useCfg | useCfg.getNode() = use | + i = min(int j | b.getNode(j).getNode() = v.getALoad()) and b.getNode(i) = useCfg ) } predicate first_undefined_use(Name use) { undefined_use(use) and - exists(GlobalVariable v | v.getALoad() = use | + exists(GlobalVariable v, ControlFlowNode useCfg | v.getALoad() = use and useCfg.getNode() = use | first_use_in_a_block(use) and not exists(ControlFlowNode other | other.getNode() = v.getALoad() and - other.getBasicBlock().strictlyDominates(use.getAFlowNode().getBasicBlock()) + other.getBasicBlock().strictlyDominates(useCfg.getBasicBlock()) ) ) } diff --git a/python/ql/src/Variables/UndefinedPlaceHolder.ql b/python/ql/src/Variables/UndefinedPlaceHolder.ql index 29f9b3a1a510..9fa0cc7eaaae 100644 --- a/python/ql/src/Variables/UndefinedPlaceHolder.ql +++ b/python/ql/src/Variables/UndefinedPlaceHolder.ql @@ -18,8 +18,8 @@ private import semmle.python.types.ImportTime /* Local variable part */ predicate initialized_as_local(PlaceHolder use) { - exists(SsaVariableWithPointsTo l, Function f | - f = use.getScope() and l.getAUse() = use.getAFlowNode() + exists(SsaVariableWithPointsTo l, Function f, ControlFlowNode useCfg | + f = use.getScope() and useCfg.getNode() = use and l.getAUse() = useCfg | l.getVariable() instanceof LocalVariable and not l.maybeUndefined() diff --git a/python/ql/src/Variables/UnusedModuleVariable.ql b/python/ql/src/Variables/UnusedModuleVariable.ql index 24d6559d6fea..0443c3388c85 100644 --- a/python/ql/src/Variables/UnusedModuleVariable.ql +++ b/python/ql/src/Variables/UnusedModuleVariable.ql @@ -54,7 +54,7 @@ predicate unused_global(Name unused, GlobalVariable v) { u.uses(v) | // That is reachable from this definition, directly - defn.strictlyReaches(u.getAFlowNode()) + exists(ControlFlowNode uCfg | uCfg.getNode() = u | defn.strictlyReaches(uCfg)) or // indirectly defn.getBasicBlock().reachesExit() and u.getScope() != unused.getScope() diff --git a/python/ql/src/analysis/CrossProjectDefinitions.qll b/python/ql/src/analysis/CrossProjectDefinitions.qll index 64b30f566f15..61e12a09ec6b 100644 --- a/python/ql/src/analysis/CrossProjectDefinitions.qll +++ b/python/ql/src/analysis/CrossProjectDefinitions.qll @@ -48,15 +48,17 @@ class Symbol extends TSymbol { AstNode find() { this = TModule(result) or - exists(Symbol s, string name | this = TMember(s, name) | + exists(Symbol s, string name, ControlFlowNode resultCfg | + this = TMember(s, name) and resultCfg.getNode() = result + | exists(ClassObject cls | s.resolvesTo() = cls and - cls.attributeRefersTo(name, _, result.getAFlowNode()) + cls.attributeRefersTo(name, _, resultCfg) ) or exists(ModuleObject m | s.resolvesTo() = m and - m.attributeRefersTo(name, _, result.getAFlowNode()) + m.attributeRefersTo(name, _, resultCfg) ) ) } diff --git a/python/ql/src/analysis/ImportFailure.ql b/python/ql/src/analysis/ImportFailure.ql index 71967e6e04f7..760a3693d6ea 100644 --- a/python/ql/src/analysis/ImportFailure.ql +++ b/python/ql/src/analysis/ImportFailure.ql @@ -80,10 +80,11 @@ class VersionGuard extends ConditionBlock { VersionGuard() { this.getLastNode() instanceof VersionTest } } -from ImportExpr ie +from ImportExpr ie, ControlFlowNode ieCfg where + ieCfg.getNode() = ie and not ie.(ExprWithPointsTo).refersTo(_) and - exists(Context c | c.appliesTo(ie.getAFlowNode())) and + exists(Context c | c.appliesTo(ieCfg)) and not ok_to_fail(ie) and - not exists(VersionGuard guard | guard.controls(ie.getAFlowNode().getBasicBlock(), _)) + not exists(VersionGuard guard | guard.controls(ieCfg.getBasicBlock(), _)) select ie, "Unable to resolve import of '" + ie.getImportedModuleName() + "'." diff --git a/python/ql/src/analysis/KeyPointsToFailure.ql b/python/ql/src/analysis/KeyPointsToFailure.ql index f07e8638f385..e42e5ac0bdd4 100644 --- a/python/ql/src/analysis/KeyPointsToFailure.ql +++ b/python/ql/src/analysis/KeyPointsToFailure.ql @@ -11,13 +11,13 @@ import python import semmle.python.pointsto.PointsTo predicate points_to_failure(Expr e) { - exists(ControlFlowNode f | f = e.getAFlowNode() | not PointsTo::pointsTo(f, _, _, _)) + exists(ControlFlowNode f | f.getNode() = e | not PointsTo::pointsTo(f, _, _, _)) } predicate key_points_to_failure(Expr e) { points_to_failure(e) and not points_to_failure(e.getASubExpression()) and - not exists(SsaVariable ssa | ssa.getAUse() = e.getAFlowNode() | + not exists(SsaVariable ssa, ControlFlowNode eCfg | eCfg.getNode() = e and ssa.getAUse() = eCfg | points_to_failure(ssa.getAnUltimateDefinition().getDefinition().getNode()) ) and not exists(Assign a | a.getATarget() = e) diff --git a/python/ql/src/analysis/PointsToFailure.ql b/python/ql/src/analysis/PointsToFailure.ql index fee1e80d2f77..8d46cbd90952 100644 --- a/python/ql/src/analysis/PointsToFailure.ql +++ b/python/ql/src/analysis/PointsToFailure.ql @@ -12,5 +12,5 @@ import python private import LegacyPointsTo from Expr e -where exists(ControlFlowNodeWithPointsTo f | f = e.getAFlowNode() | not f.refersTo(_)) +where exists(ControlFlowNodeWithPointsTo f | f.getNode() = e | not f.refersTo(_)) select e, "Expression does not 'point-to' any object." diff --git a/python/ql/src/experimental/Security/CWE-022bis/TarSlipImprov.ql b/python/ql/src/experimental/Security/CWE-022bis/TarSlipImprov.ql index 42c0bc170fd9..5f49cb218805 100755 --- a/python/ql/src/experimental/Security/CWE-022bis/TarSlipImprov.ql +++ b/python/ql/src/experimental/Security/CWE-022bis/TarSlipImprov.ql @@ -21,6 +21,7 @@ import semmle.python.ApiGraphs import semmle.python.dataflow.new.internal.Attributes import semmle.python.dataflow.new.BarrierGuards import semmle.python.dataflow.new.RemoteFlowSources +private import semmle.python.controlflow.internal.Cfg as Cfg /** * Handle those three cases of Tarfile opens: @@ -75,8 +76,8 @@ private module TarSlipImprovConfig implements DataFlow::ConfigSig { call = atfo.getReturn().getMember("extractall").getACall() and arg = call.getArgByName("members") and if - arg.asCfgNode() instanceof NameConstantNode or - arg.asCfgNode() instanceof ListNode + arg.asCfgNode() instanceof Cfg::NameConstantNode or + arg.asCfgNode() instanceof Cfg::ListNode then sink = call.getObject() else if arg.(MethodCallNode).getMethodName() = "getmembers" diff --git a/python/ql/src/experimental/Security/CWE-340/TokenBuiltFromUUID.ql b/python/ql/src/experimental/Security/CWE-340/TokenBuiltFromUUID.ql index ab5a4243a746..f43b718289de 100644 --- a/python/ql/src/experimental/Security/CWE-340/TokenBuiltFromUUID.ql +++ b/python/ql/src/experimental/Security/CWE-340/TokenBuiltFromUUID.ql @@ -16,6 +16,7 @@ import python import semmle.python.dataflow.new.DataFlow import semmle.python.ApiGraphs import semmle.python.dataflow.new.TaintTracking +private import semmle.python.controlflow.internal.Cfg as Cfg class PredictableResultSource extends DataFlow::Node { PredictableResultSource() { @@ -32,7 +33,9 @@ class PredictableResultSource extends DataFlow::Node { class TokenAssignmentValueSink extends DataFlow::Node { TokenAssignmentValueSink() { exists(string name | name.toLowerCase().matches(["%token", "%code"]) | - exists(DefinitionNode n | n.getValue() = this.asCfgNode() | name = n.(NameNode).getId()) + exists(Cfg::DefinitionNode n | n.getValue() = this.asCfgNode() | + name = n.(Cfg::NameNode).getId() + ) or exists(DataFlow::AttrWrite aw | aw.getValue() = this | name = aw.getAttributeName()) ) diff --git a/python/ql/src/experimental/Security/CWE-346/CorsBypass.ql b/python/ql/src/experimental/Security/CWE-346/CorsBypass.ql index 01e661cb0bbf..5ce58869a890 100644 --- a/python/ql/src/experimental/Security/CWE-346/CorsBypass.ql +++ b/python/ql/src/experimental/Security/CWE-346/CorsBypass.ql @@ -11,25 +11,25 @@ import python import semmle.python.ApiGraphs import semmle.python.dataflow.new.TaintTracking -import semmle.python.Flow import semmle.python.dataflow.new.RemoteFlowSources +private import semmle.python.controlflow.internal.Cfg as Cfg /** * Returns true if the control flow node may be useful in the current context. * * Ideally for more completeness, we should alert on every `startswith` call and every remote flow source which gets partailly checked. But, as this can lead to lots of FPs, we apply heuristics to filter some calls. This predicate provides logic for this filteration. */ -private predicate maybeInteresting(ControlFlowNode c) { +private predicate maybeInteresting(Cfg::ControlFlowNode c) { // Check if the name of the variable which calls the function matches the heuristic. // This would typically occur at the sink. // This should deal with cases like // `origin.startswith("bla")` - heuristics(c.(CallNode).getFunction().(AttrNode).getObject().(NameNode).getId()) + heuristics(c.(Cfg::CallNode).getFunction().(Cfg::AttrNode).getObject().(Cfg::NameNode).getId()) or // Check if the name of the variable passed as an argument to the functions matches the heuristic. This would typically occur at the sink. // This should deal with cases like // `bla.startswith(origin)` - heuristics(c.(CallNode).getArg(0).(NameNode).getId()) + heuristics(c.(Cfg::CallNode).getArg(0).(Cfg::NameNode).getId()) or // Check if the value gets written to any interesting variable. This would typically occur at the source. // This should deal with cases like @@ -37,8 +37,10 @@ private predicate maybeInteresting(ControlFlowNode c) { exists(Variable v | heuristics(v.getId()) | c.getASuccessor*().getNode() = v.getAStore()) } -private class StringStartswithCall extends ControlFlowNode { - StringStartswithCall() { this.(CallNode).getFunction().(AttrNode).getName() = "startswith" } +private class StringStartswithCall extends Cfg::ControlFlowNode { + StringStartswithCall() { + this.(Cfg::CallNode).getFunction().(Cfg::AttrNode).getName() = "startswith" + } } bindingset[s] @@ -66,8 +68,8 @@ module CorsBypassConfig implements DataFlow::ConfigSig { predicate isSink(DataFlow::Node node) { exists(StringStartswithCall s | - node.asCfgNode() = s.(CallNode).getArg(0) or - node.asCfgNode() = s.(CallNode).getFunction().(AttrNode).getObject() + node.asCfgNode() = s.(Cfg::CallNode).getArg(0) or + node.asCfgNode() = s.(Cfg::CallNode).getFunction().(Cfg::AttrNode).getObject() ) } diff --git a/python/ql/src/experimental/Security/CWE-770/UnicodeDoS.ql b/python/ql/src/experimental/Security/CWE-770/UnicodeDoS.ql index 61cdd34920de..84627456310b 100644 --- a/python/ql/src/experimental/Security/CWE-770/UnicodeDoS.ql +++ b/python/ql/src/experimental/Security/CWE-770/UnicodeDoS.ql @@ -16,6 +16,7 @@ import semmle.python.Concepts import semmle.python.dataflow.new.TaintTracking import semmle.python.dataflow.new.internal.DataFlowPublic import semmle.python.dataflow.new.RemoteFlowSources +private import semmle.python.controlflow.internal.Cfg as Cfg // The Unicode compatibility normalization calls from unicodedata, unidecode, pyunormalize // and textnorm modules. The use of argIdx is to constraint the argument being normalized. @@ -52,8 +53,8 @@ class UnicodeCompatibilityNormalize extends API::CallNode { DataFlow::Node getPathArg() { result = this.getArg(argIdx) } } -predicate underAValue(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) { - exists(CompareNode cn | cn = g | +predicate underAValue(DataFlow::GuardNode g, Cfg::ControlFlowNode node, boolean branch) { + exists(Cfg::CompareNode cn | cn = g | exists(API::CallNode lenCall, Cmpop op, Node n | lenCall = n.getALocalSource() and ( diff --git a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll index 64da6b8d799a..ecc029ca8a78 100644 --- a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll +++ b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll @@ -9,6 +9,7 @@ import semmle.python.ApiGraphs import semmle.python.dataflow.new.TaintTracking import semmle.python.frameworks.Stdlib import semmle.python.dataflow.new.RemoteFlowSources +private import semmle.python.controlflow.internal.Cfg as Cfg /** * Handle those three cases of Tarfile opens: @@ -111,8 +112,8 @@ module UnsafeUnpackConfig implements DataFlow::ConfigSig { call = atfo.getReturn().getMember("extractall").getACall() and arg = call.getArgByName("members") and if - arg.asCfgNode() instanceof NameConstantNode or - arg.asCfgNode() instanceof ListNode + arg.asCfgNode() instanceof Cfg::NameConstantNode or + arg.asCfgNode() instanceof Cfg::ListNode then sink = call.getObject() else if arg.(MethodCallNode).getMethodName() = "getmembers" diff --git a/python/ql/src/experimental/semmle/python/security/injection/CsvInjection.qll b/python/ql/src/experimental/semmle/python/security/injection/CsvInjection.qll index 859f6d1e5e80..d789007b8d50 100644 --- a/python/ql/src/experimental/semmle/python/security/injection/CsvInjection.qll +++ b/python/ql/src/experimental/semmle/python/security/injection/CsvInjection.qll @@ -4,6 +4,7 @@ import semmle.python.dataflow.new.DataFlow import semmle.python.dataflow.new.TaintTracking import semmle.python.dataflow.new.BarrierGuards import semmle.python.dataflow.new.RemoteFlowSources +private import semmle.python.controlflow.internal.Cfg as Cfg /** * A taint-tracking configuration for tracking untrusted user input used in file read. @@ -21,7 +22,7 @@ private module CsvInjectionConfig implements DataFlow::ConfigSig { predicate observeDiffInformedIncrementalMode() { any() } } -private predicate startsWithCheck(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) { +private predicate startsWithCheck(DataFlow::GuardNode g, Cfg::ControlFlowNode node, boolean branch) { exists(DataFlow::MethodCallNode mc | g = mc.asCfgNode() and mc.calls(_, "startswith") and diff --git a/python/ql/src/meta/analysis-quality/CallGraphQuality.qll b/python/ql/src/meta/analysis-quality/CallGraphQuality.qll index 679b39ddc8d4..1ac0896064b5 100644 --- a/python/ql/src/meta/analysis-quality/CallGraphQuality.qll +++ b/python/ql/src/meta/analysis-quality/CallGraphQuality.qll @@ -6,6 +6,7 @@ import python import meta.MetaMetrics private import LegacyPointsTo +private import semmle.python.controlflow.internal.Cfg as Cfg newtype TTarget = TFunction(Function func) or @@ -50,7 +51,7 @@ class TargetClass extends Target, TClass { * A call that is (possibly) relevant for analysis quality. * See `IgnoredFile` for details on what is excluded. */ -class RelevantCall extends CallNode { +class RelevantCall extends Cfg::CallNode { RelevantCall() { not this.getLocation().getFile() instanceof IgnoredFile } } @@ -60,7 +61,7 @@ module PointsToBasedCallGraph { class ResolvableCall extends RelevantCall { Value targetValue; - ResolvableCall() { targetValue.getACall() = this } + ResolvableCall() { targetValue.getACall().getNode() = this.getNode() } /** Gets a resolved target of this call. */ Target getTarget() { diff --git a/python/ql/src/meta/analysis-quality/TTCallGraph.ql b/python/ql/src/meta/analysis-quality/TTCallGraph.ql index bdd634951919..4487095a6fb6 100644 --- a/python/ql/src/meta/analysis-quality/TTCallGraph.ql +++ b/python/ql/src/meta/analysis-quality/TTCallGraph.ql @@ -8,8 +8,9 @@ import python import CallGraphQuality +private import semmle.python.controlflow.internal.Cfg as Cfg -from CallNode call, Target target +from Cfg::CallNode call, Target target where target.isRelevant() and call.(TypeTrackingBasedCallGraph::ResolvableCall).getTarget() = target diff --git a/python/ql/src/meta/analysis-quality/TTCallGraphMissing.ql b/python/ql/src/meta/analysis-quality/TTCallGraphMissing.ql index bb28c5bf804f..188f483689f5 100644 --- a/python/ql/src/meta/analysis-quality/TTCallGraphMissing.ql +++ b/python/ql/src/meta/analysis-quality/TTCallGraphMissing.ql @@ -8,8 +8,9 @@ import python import CallGraphQuality +private import semmle.python.controlflow.internal.Cfg as Cfg -from CallNode call, Target target +from Cfg::CallNode call, Target target where target.isRelevant() and call.(PointsToBasedCallGraph::ResolvableCall).getTarget() = target and diff --git a/python/ql/src/meta/analysis-quality/TTCallGraphNew.ql b/python/ql/src/meta/analysis-quality/TTCallGraphNew.ql index b9f1df54b3b2..198007d07759 100644 --- a/python/ql/src/meta/analysis-quality/TTCallGraphNew.ql +++ b/python/ql/src/meta/analysis-quality/TTCallGraphNew.ql @@ -8,8 +8,9 @@ import python import CallGraphQuality +private import semmle.python.controlflow.internal.Cfg as Cfg -from CallNode call, Target target +from Cfg::CallNode call, Target target where target.isRelevant() and not call.(PointsToBasedCallGraph::ResolvableCall).getTarget() = target and diff --git a/python/ql/src/meta/analysis-quality/TTCallGraphNewAmbiguous.ql b/python/ql/src/meta/analysis-quality/TTCallGraphNewAmbiguous.ql index 702541ca16d4..b14644f6585a 100644 --- a/python/ql/src/meta/analysis-quality/TTCallGraphNewAmbiguous.ql +++ b/python/ql/src/meta/analysis-quality/TTCallGraphNewAmbiguous.ql @@ -8,8 +8,9 @@ import python import CallGraphQuality +private import semmle.python.controlflow.internal.Cfg as Cfg -from CallNode call, Target target +from Cfg::CallNode call, Target target where target.isRelevant() and not call.(PointsToBasedCallGraph::ResolvableCall).getTarget() = target and diff --git a/python/ql/src/meta/analysis-quality/TTCallGraphOverview.ql b/python/ql/src/meta/analysis-quality/TTCallGraphOverview.ql index 5a789d1be90b..19660f92ec55 100644 --- a/python/ql/src/meta/analysis-quality/TTCallGraphOverview.ql +++ b/python/ql/src/meta/analysis-quality/TTCallGraphOverview.ql @@ -6,12 +6,13 @@ import python import CallGraphQuality +private import semmle.python.controlflow.internal.Cfg as Cfg from string tag, int c where tag = "SHARED" and c = - count(CallNode call, Target target | + count(Cfg::CallNode call, Target target | target.isRelevant() and call.(PointsToBasedCallGraph::ResolvableCall).getTarget() = target and call.(TypeTrackingBasedCallGraph::ResolvableCall).getTarget() = target @@ -19,7 +20,7 @@ where or tag = "NEW" and c = - count(CallNode call, Target target | + count(Cfg::CallNode call, Target target | target.isRelevant() and not call.(PointsToBasedCallGraph::ResolvableCall).getTarget() = target and call.(TypeTrackingBasedCallGraph::ResolvableCall).getTarget() = target @@ -27,7 +28,7 @@ where or tag = "MISSING" and c = - count(CallNode call, Target target | + count(Cfg::CallNode call, Target target | target.isRelevant() and call.(PointsToBasedCallGraph::ResolvableCall).getTarget() = target and not call.(TypeTrackingBasedCallGraph::ResolvableCall).getTarget() = target diff --git a/python/ql/src/meta/analysis-quality/TTCallGraphShared.ql b/python/ql/src/meta/analysis-quality/TTCallGraphShared.ql index d44d1ac497ff..f8275b5a965b 100644 --- a/python/ql/src/meta/analysis-quality/TTCallGraphShared.ql +++ b/python/ql/src/meta/analysis-quality/TTCallGraphShared.ql @@ -8,8 +8,9 @@ import python import CallGraphQuality +private import semmle.python.controlflow.internal.Cfg as Cfg -from CallNode call, Target target +from Cfg::CallNode call, Target target where target.isRelevant() and call.(PointsToBasedCallGraph::ResolvableCall).getTarget() = target and diff --git a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll index c7aef20c09dd..d4cdc388b14f 100644 --- a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll +++ b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll @@ -6,6 +6,7 @@ private import python private import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.BarrierGuards +private import semmle.python.controlflow.internal.Cfg as Cfg /** * Provides default sources, sinks and sanitizers for detecting @@ -76,7 +77,7 @@ module ModificationOfParameterWithDefault { boolean nonEmpty; MutableDefaultValue() { - nonEmpty = mutableDefaultValue(this.asCfgNode().(NameNode).getNode()) and + nonEmpty = mutableDefaultValue(this.asCfgNode().(Cfg::NameNode).getNode()) and // Ignore sources inside the standard library. These are unlikely to be true positives. exists(this.getLocation().getFile().getRelativePath()) } @@ -125,13 +126,13 @@ module ModificationOfParameterWithDefault { class Mutation extends Sink { Mutation() { // assignment to a subscript (includes slices) - exists(DefinitionNode d | d.(SubscriptNode).getObject() = this.asCfgNode()) + exists(Cfg::DefinitionNode d | d.(Cfg::SubscriptNode).getObject() = this.asCfgNode()) or // deletion of a subscript - exists(DeletionNode d | d.getTarget().(SubscriptNode).getObject() = this.asCfgNode()) + exists(Cfg::DeletionNode d | d.(Cfg::SubscriptNode).getObject() = this.asCfgNode()) or // augmented assignment to the value - exists(AugAssign a | a.getTarget().getAFlowNode() = this.asCfgNode()) + exists(AugAssign a | this.asCfgNode().getNode() = a.getTarget()) or // modifying function call exists(DataFlow::CallCfgNode c, DataFlow::AttrRead a | c.getFunction() = a | @@ -141,54 +142,33 @@ module ModificationOfParameterWithDefault { } } - // This to reimplement some of the functionality of the DataFlow::BarrierGuard - private import semmle.python.essa.SsaCompute - /** - * A data-flow node that is known to be either truthy or falsey. - * - * It handles the cases `if x` and `if not x`. + * Holds if `g` validates `node` as truthy when evaluating to `branch`. * - * For example, in the following code, `this` will be the `x` that is printed, - * which we will know is truthy: - * - * ```py - * if x: - * print(x) - * ``` + * The new shared CFG's `GuardNode`/`outcomeOfGuard` already unwraps + * `not x` wrappers, so we only need the direct case: a guard `g` + * controls a block where the guarded value (also `g`) is known to + * have the matching truthiness for the taken branch. */ - private class MustBe extends DataFlow::Node { - boolean truthy; - - MustBe() { - exists(DataFlow::GuardNode guard, NameNode guarded, boolean branch | - // case: if x - guard = guarded and - branch = truthy - or - // case: if not x - guard.(UnaryExprNode).getNode().getOp() instanceof Not and - guarded = guard.(UnaryExprNode).getOperand() and - branch = truthy.booleanNot() - | - // guard controls this - guard.controlsBlock(this.asCfgNode().getBasicBlock(), branch) and - // there is a definition tying the guarded value to this - exists(EssaDefinition def | - AdjacentUses::useOfDef(def, this.asCfgNode()) and - AdjacentUses::useOfDef(def, guarded) - ) - ) - } + private predicate truthinessGuard(DataFlow::GuardNode g, Cfg::ControlFlowNode node, boolean branch) { + node = g and branch in [true, false] } /** Simple guard detecting truthy values. */ - private class MustBeTruthy extends MustBe, MustBeNonEmpty { - MustBeTruthy() { truthy = true } + private class MustBeTruthy extends MustBeNonEmpty { + MustBeTruthy() { + this = DataFlow::BarrierGuard::getABarrierNode() and + // truthy = true branch + exists(DataFlow::GuardNode g | g.controlsBlock(this.asCfgNode().getBasicBlock(), true)) + } } /** Simple guard detecting falsey values. */ - private class MustBeFalsey extends MustBe, MustBeEmpty { - MustBeFalsey() { truthy = false } + private class MustBeFalsey extends MustBeEmpty { + MustBeFalsey() { + this = DataFlow::BarrierGuard::getABarrierNode() and + // truthy = false branch + exists(DataFlow::GuardNode g | g.controlsBlock(this.asCfgNode().getBasicBlock(), false)) + } } } diff --git a/python/ql/test/experimental/meta/InlineTaintTest.qll b/python/ql/test/experimental/meta/InlineTaintTest.qll index 525775d5106c..5c20a9913beb 100644 --- a/python/ql/test/experimental/meta/InlineTaintTest.qll +++ b/python/ql/test/experimental/meta/InlineTaintTest.qll @@ -10,6 +10,7 @@ */ import python +private import semmle.python.controlflow.internal.Cfg as Cfg import semmle.python.dataflow.new.DataFlow import semmle.python.dataflow.new.TaintTracking import semmle.python.dataflow.new.RemoteFlowSources @@ -19,14 +20,14 @@ private import semmle.python.Concepts DataFlow::Node shouldBeTainted() { exists(DataFlow::CallCfgNode call | - call.getFunction().asCfgNode().(NameNode).getId() = "ensure_tainted" and + call.getFunction().asCfgNode().(Cfg::NameNode).getId() = "ensure_tainted" and result in [call.getArg(_), call.getArgByName(_)] ) } DataFlow::Node shouldNotBeTainted() { exists(DataFlow::CallCfgNode call | - call.getFunction().asCfgNode().(NameNode).getId() = "ensure_not_tainted" and + call.getFunction().asCfgNode().(Cfg::NameNode).getId() = "ensure_not_tainted" and result in [call.getArg(_), call.getArgByName(_)] ) } @@ -36,13 +37,13 @@ DataFlow::Node shouldNotBeTainted() { module Conf { module TestTaintTrackingConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { - source.asCfgNode().(NameNode).getId() in [ + source.asCfgNode().(Cfg::NameNode).getId() in [ "TAINTED_STRING", "TAINTED_BYTES", "TAINTED_LIST", "TAINTED_DICT" ] or // User defined sources - exists(CallNode call | - call.getFunction().(NameNode).getId() = "taint" and + exists(Cfg::CallNode call | + call.getFunction().(Cfg::NameNode).getId() = "taint" and source.(DataFlow::CfgNode).getNode() = call.getAnArg() ) or diff --git a/python/ql/test/library-tests/ApiGraphs/py3/ModuleImportWithDots.expected b/python/ql/test/library-tests/ApiGraphs/py3/ModuleImportWithDots.expected index a6798920cdee..1b0d0a0cf3ad 100644 --- a/python/ql/test/library-tests/ApiGraphs/py3/ModuleImportWithDots.expected +++ b/python/ql/test/library-tests/ApiGraphs/py3/ModuleImportWithDots.expected @@ -1,5 +1,5 @@ moduleImportWithDots doesntFullyWork works -| test.py:25:6:25:18 | ControlFlowNode for Attribute() | -| test.py:28:10:28:17 | ControlFlowNode for method() | +| test.py:25:6:25:18 | After Attribute() | +| test.py:28:10:28:17 | After method() | diff --git a/python/ql/test/library-tests/ApiGraphs/py3/test_crosstalk.expected b/python/ql/test/library-tests/ApiGraphs/py3/test_crosstalk.expected index 58698e5ec9d6..24c2b88cb4aa 100644 --- a/python/ql/test/library-tests/ApiGraphs/py3/test_crosstalk.expected +++ b/python/ql/test/library-tests/ApiGraphs/py3/test_crosstalk.expected @@ -1,2 +1,2 @@ -| test_crosstalk.py:8:16:8:18 | ControlFlowNode for f() | bar | -| test_crosstalk.py:13:16:13:18 | ControlFlowNode for g() | baz | +| test_crosstalk.py:8:16:8:18 | After f() | bar | +| test_crosstalk.py:13:16:13:18 | After g() | baz | diff --git a/python/ql/test/library-tests/ControlFlow/bindings/BindingsTest.expected b/python/ql/test/library-tests/ControlFlow/bindings/BindingsTest.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/ControlFlow/bindings/BindingsTest.ql b/python/ql/test/library-tests/ControlFlow/bindings/BindingsTest.ql new file mode 100644 index 000000000000..a507878911b1 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/bindings/BindingsTest.ql @@ -0,0 +1,32 @@ +/** + * Phase -1 of the dataflow CFG migration: verifies that every variable + * binding visible to the AST (`Name.defines(v)`) corresponds to a CFG node + * in the new CFG (`semmle.python.controlflow.internal.AstNodeImpl`). + * + * The expected tag is `cfgdefines=`. Each binding annotation in the + * test sources looks like `# $ cfgdefines=x` for a binding currently + * covered by the new CFG, or `# $ MISSING: cfgdefines=x` for a binding + * that is known to be uncovered (a "red" test case that should be + * green-flipped once the corresponding `cfg-ext-*` extension lands). + */ + +import python +import semmle.python.controlflow.internal.AstNodeImpl as CfgImpl +import utils.test.InlineExpectationsTest + +module CfgBindingsTest implements TestSig { + string getARelevantTag() { result = "cfgdefines" } + + predicate hasActualResult(Location location, string element, string tag, string value) { + exists(Name n, Variable v, CfgImpl::ControlFlowNode cfg | + n.defines(v) and + cfg.getAstNode().asExpr() = n and + location = n.getLocation() and + element = n.toString() and + tag = "cfgdefines" and + value = v.getId() + ) + } +} + +import MakeTest diff --git a/python/ql/test/library-tests/ControlFlow/bindings/annassign.py b/python/ql/test/library-tests/ControlFlow/bindings/annassign.py new file mode 100644 index 000000000000..7a9ae3ab6c79 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/bindings/annassign.py @@ -0,0 +1,13 @@ +# Annotated assignment (PEP 526). Both with and without an initializer. + +a: int = 1 # $ cfgdefines=a +b: str = "hi" # $ cfgdefines=b + +# Annotation without value: the AST records `c` as defined, +# and the new CFG now visits it via the AnnAssignStmt wrapper. +c: int # $ cfgdefines=c + +class K: # $ cfgdefines=K + field: int = 0 # $ cfgdefines=field + + diff --git a/python/ql/test/library-tests/ControlFlow/bindings/compound.py b/python/ql/test/library-tests/ControlFlow/bindings/compound.py new file mode 100644 index 000000000000..cb2f36f12ffe --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/bindings/compound.py @@ -0,0 +1,14 @@ +# Compound (tuple/list) assignment targets — actually wired in the new CFG. + +a, b = (1, 2) # $ cfgdefines=a cfgdefines=b +[c, d] = [3, 4] # $ cfgdefines=c cfgdefines=d + +# Nested unpacking. +(e, (f, g)) = (1, (2, 3)) # $ cfgdefines=e cfgdefines=f cfgdefines=g + +# Star unpacking. +h, *i = [1, 2, 3] # $ cfgdefines=h cfgdefines=i + +# Chained assignment with compound target. +j = k, l = (5, 6) # $ cfgdefines=j cfgdefines=k cfgdefines=l + diff --git a/python/ql/test/library-tests/ControlFlow/bindings/comprehension.py b/python/ql/test/library-tests/ControlFlow/bindings/comprehension.py new file mode 100644 index 000000000000..6b5f722c1f7e --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/bindings/comprehension.py @@ -0,0 +1,21 @@ +# Comprehension and `for` loop targets — wired in the new CFG. +# Comprehensions are nested function scopes with a synthetic `.0` parameter +# bound to the iterable. + +# Bare-name `for` target. +for i in range(3): # $ cfgdefines=i + pass + +# Compound `for` target. +for k, v in [(1, 2)]: # $ cfgdefines=k cfgdefines=v + pass + +# Comprehension targets. +_ = [x for x in range(3)] # $ cfgdefines=_ cfgdefines=x cfgdefines=.0 +_ = {y: z for y, z in []} # $ cfgdefines=_ cfgdefines=y cfgdefines=z cfgdefines=.0 +_ = (a for a in []) # $ cfgdefines=_ cfgdefines=a cfgdefines=.0 + +# Nested comprehensions. +_ = [b for c in [] for b in c] # $ cfgdefines=_ cfgdefines=c cfgdefines=b cfgdefines=.0 + + diff --git a/python/ql/test/library-tests/ControlFlow/bindings/dead_under_no_raise.py b/python/ql/test/library-tests/ControlFlow/bindings/dead_under_no_raise.py new file mode 100644 index 000000000000..dbfb857b5360 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/bindings/dead_under_no_raise.py @@ -0,0 +1,52 @@ +# Dead bindings under the "no expressions raise" CFG abstraction. +# +# The new CFG does not currently model raise edges from arbitrary +# expressions. As a consequence, code that is only reachable through +# exception flow is (correctly) classified as dead and has no CFG node. +# Variable bindings in dead code do not need CFG nodes - SSA / dataflow +# over dead code is moot. +# +# These tests act as a regression guard: the bindings below intentionally +# have no `cfgdefines=` annotations. If raise modelling is later added, +# the BindingsTest infrastructure will surface the new CFG nodes as +# unexpected results, and this file will need to be revisited. + + +def f(obj): # $ cfgdefines=f cfgdefines=obj + try: + return len(obj) + except TypeError: + pass + + # The first try's body always returns; its except handler does not + # raise or otherwise transfer control, so under "no expressions + # raise" the only paths out of the try-statement are dead. Everything + # below is unreachable. + try: + hint = type(obj).__length_hint__ + except AttributeError: + return None + return hint + + +def g(): # $ cfgdefines=g + try: + raise Exception("inner") + except: + raise Exception("outer") + else: + # Unreachable: the inner try body always raises, so the `else:` + # clause never runs. + hit_inner_else = True + + +def h(cache, key): # $ cfgdefines=h cfgdefines=cache cfgdefines=key + try: + return cache[key] + except KeyError: + pass + + # Same pattern as `f`: dead under "no expressions raise". + value = compute(key) + cache[key] = value + return value diff --git a/python/ql/test/library-tests/ControlFlow/bindings/decorated.py b/python/ql/test/library-tests/ControlFlow/bindings/decorated.py new file mode 100644 index 000000000000..9b93c166acec --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/bindings/decorated.py @@ -0,0 +1,30 @@ +# Decorated `def`/`class` — wired in the new CFG. + + +def deco(f): # $ cfgdefines=deco cfgdefines=f + return f + + +@deco +def decorated_func(): # $ cfgdefines=decorated_func + pass + + +@deco +class DecoratedClass: # $ cfgdefines=DecoratedClass + pass + + +# Stacked decorators. +@deco +@deco +def doubly(): # $ cfgdefines=doubly + pass + + +# Inside a class body. +class Outer: # $ cfgdefines=Outer + @staticmethod + def inner(): # $ cfgdefines=inner + pass + diff --git a/python/ql/test/library-tests/ControlFlow/bindings/except_handler.py b/python/ql/test/library-tests/ControlFlow/bindings/except_handler.py new file mode 100644 index 000000000000..57b6c99fe9b6 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/bindings/except_handler.py @@ -0,0 +1,19 @@ +# Exception-handler name bindings. These are already wired in the new +# CFG provided the try body can raise; `raise` statements are reliably +# treated as exception sources. + +try: + raise ValueError("oops") +except ValueError as e: # $ cfgdefines=e + pass + +try: + raise TypeError("oops") +except (TypeError, KeyError) as err: # $ cfgdefines=err + pass + +# Exception groups (Python 3.11+). +try: + raise ValueError("oops") +except* ValueError as eg: # $ cfgdefines=eg + pass diff --git a/python/ql/test/library-tests/ControlFlow/bindings/imports.py b/python/ql/test/library-tests/ControlFlow/bindings/imports.py new file mode 100644 index 000000000000..c8834b5332a0 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/bindings/imports.py @@ -0,0 +1,14 @@ +# Import aliases — all bound names below are now reachable via the new +# CFG's `ImportStmt` wrapper. + +import os # $ cfgdefines=os +import os.path # $ cfgdefines=os +import os as o # $ cfgdefines=o +from os import path # $ cfgdefines=path +from os import path as p # $ cfgdefines=p +from os import sep, linesep # $ cfgdefines=sep cfgdefines=linesep +from os import ( + getcwd, # $ cfgdefines=getcwd + getcwdb, # $ cfgdefines=getcwdb +) + diff --git a/python/ql/test/library-tests/ControlFlow/bindings/match_pattern.py b/python/ql/test/library-tests/ControlFlow/bindings/match_pattern.py new file mode 100644 index 000000000000..0868a2680d0a --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/bindings/match_pattern.py @@ -0,0 +1,24 @@ +# Match-statement pattern bindings — wired in the new CFG. + +def f(subject): # $ cfgdefines=f cfgdefines=subject + match subject: + case x: # $ cfgdefines=x + pass + case [a, b]: # $ cfgdefines=a cfgdefines=b + pass + case {"k": v}: # $ cfgdefines=v + pass + case Point(p, q): # $ cfgdefines=p cfgdefines=q + pass + case [_, *rest]: # $ cfgdefines=rest + pass + case (1 | 2) as n: # $ cfgdefines=n + pass + + +class Point: # $ cfgdefines=Point + __match_args__ = ("x", "y") # $ cfgdefines=__match_args__ + x: int # $ cfgdefines=x + y: int # $ cfgdefines=y + + diff --git a/python/ql/test/library-tests/ControlFlow/bindings/parameters.py b/python/ql/test/library-tests/ControlFlow/bindings/parameters.py new file mode 100644 index 000000000000..7fe5e01e4c4b --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/bindings/parameters.py @@ -0,0 +1,42 @@ +# Function parameters. + +def positional(a, b): # $ cfgdefines=positional cfgdefines=a cfgdefines=b + pass + + +def with_default(x=1, y=2): # $ cfgdefines=with_default cfgdefines=x cfgdefines=y + pass + + +def with_vararg(*args): # $ cfgdefines=with_vararg cfgdefines=args + pass + + +def with_kwarg(**kwargs): # $ cfgdefines=with_kwarg cfgdefines=kwargs + pass + + +def with_kwonly(*, k1, k2=5): # $ cfgdefines=with_kwonly cfgdefines=k1 cfgdefines=k2 + pass + + +def kitchen_sink(a, b=2, *args, k1, k2=5, **kw): # $ cfgdefines=kitchen_sink cfgdefines=a cfgdefines=b cfgdefines=args cfgdefines=k1 cfgdefines=k2 cfgdefines=kw + pass + + +# Methods get `self` / `cls`. +class C: # $ cfgdefines=C + def method(self, x): # $ cfgdefines=method cfgdefines=self cfgdefines=x + pass + + @classmethod + def cmethod(cls, x): # $ cfgdefines=cmethod cfgdefines=cls cfgdefines=x + pass + + +# Lambda parameter. +_ = lambda p: p + 1 # $ cfgdefines=_ cfgdefines=p + +# PEP 570 positional-only. +def pos_only(a, b, /, c): # $ cfgdefines=pos_only cfgdefines=a cfgdefines=b cfgdefines=c + pass diff --git a/python/ql/test/library-tests/ControlFlow/bindings/simple.py b/python/ql/test/library-tests/ControlFlow/bindings/simple.py new file mode 100644 index 000000000000..51cb7d828c91 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/bindings/simple.py @@ -0,0 +1,14 @@ +# Simple bindings that should already work in the new CFG. +# No MISSING annotations expected. + +x = 1 # $ cfgdefines=x +y = x + 1 # $ cfgdefines=y + +def f(): # $ cfgdefines=f + pass + +class C: # $ cfgdefines=C + pass + +# Re-assignment. +x = 2 # $ cfgdefines=x diff --git a/python/ql/test/library-tests/ControlFlow/bindings/type_params.py b/python/ql/test/library-tests/ControlFlow/bindings/type_params.py new file mode 100644 index 000000000000..2bd34dc3f0ee --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/bindings/type_params.py @@ -0,0 +1,21 @@ +# PEP 695 type parameters (Python 3.12+). + +# PEP 695 type-param names on `def`/`class` bind in an annotation scope +# that nests the function/class body — they have no CFG node in the +# enclosing scope (matching the legacy CFG). +def func[T](x: T) -> T: # $ cfgdefines=func cfgdefines=x + return x + + +class Box[T]: # $ cfgdefines=Box + item: T # $ cfgdefines=item + + +# Multi-parameter, with bound and variadics. +def multi[T: int, *Ts, **P](x: T, *args: *Ts, **kwargs: P.kwargs) -> T: # $ cfgdefines=multi cfgdefines=x cfgdefines=args cfgdefines=kwargs + return x + + +# `type` statement (PEP 695). +type Alias[T] = list[T] # $ cfgdefines=Alias cfgdefines=T + diff --git a/python/ql/test/library-tests/ControlFlow/bindings/walrus_starred.py b/python/ql/test/library-tests/ControlFlow/bindings/walrus_starred.py new file mode 100644 index 000000000000..5c0c1bd83191 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/bindings/walrus_starred.py @@ -0,0 +1,14 @@ +# Walrus and starred-target edge cases — wired in the new CFG. + +# Walrus in expression context. +if (y := 5) > 0: # $ cfgdefines=y + pass + +# Walrus in a comprehension. The comprehension introduces a synthetic +# `.0` parameter bound to the iterable. +_ = [w for _ in range(3) if (w := 1)] # $ cfgdefines=_ cfgdefines=w cfgdefines=.0 + +# Starred target in a Tuple LHS. +*head, tail = [1, 2, 3] # $ cfgdefines=head cfgdefines=tail + + diff --git a/python/ql/test/library-tests/ControlFlow/bindings/with_stmt.py b/python/ql/test/library-tests/ControlFlow/bindings/with_stmt.py new file mode 100644 index 000000000000..5fffe46c5d40 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/bindings/with_stmt.py @@ -0,0 +1,21 @@ +# `with cm() as x:` bindings — wired in the new CFG. + +class CM: # $ cfgdefines=CM + def __enter__(self): return self # $ cfgdefines=__enter__ cfgdefines=self + def __exit__(self, *a): pass # $ cfgdefines=__exit__ cfgdefines=self cfgdefines=a + +with CM() as x: # $ cfgdefines=x + pass + +# Multiple items. +with CM() as a, CM() as b: # $ cfgdefines=a cfgdefines=b + pass + +# Parenthesised form (Python 3.10+). +with (CM() as p, CM() as q): # $ cfgdefines=p cfgdefines=q + pass + +# Compound target in `with`. +with CM() as (m, n): # $ cfgdefines=m cfgdefines=n + pass + diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/AllLiveReachable.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/AllLiveReachable.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/AllLiveReachable.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/AllLiveReachable.ql new file mode 100644 index 000000000000..de44daa3e2c2 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/AllLiveReachable.ql @@ -0,0 +1,19 @@ +/** + * Checks that every live (non-dead) annotation in the test function's + * own scope is reachable from the function entry in the CFG. + * Annotations in nested scopes (generators, async, lambdas, comprehensions) + * have separate CFGs and are excluded from this check. + */ + +import python +import TimerUtils +import OldCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils +private import Utils::CfgTests + +from TimerCfgNode a, TestFunction f +where allLiveReachable(a, f) +select a, "Unreachable live annotation; entry of $@ does not reach this node", f, f.getName() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/AnnotationHasCfgNode.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/AnnotationHasCfgNode.expected new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/AnnotationHasCfgNode.expected @@ -0,0 +1 @@ + diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/AnnotationHasCfgNode.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/AnnotationHasCfgNode.ql new file mode 100644 index 000000000000..5311d118576b --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/AnnotationHasCfgNode.ql @@ -0,0 +1,16 @@ +/** + * Checks that every timer annotation has a corresponding CFG node. + */ + +import python +import TimerUtils +import OldCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils::CfgTests + +from TimerAnnotation ann +where annotationWithoutCfgNode(ann) +select ann, "Annotation in $@ has no CFG node", ann.getTestFunction(), + ann.getTestFunction().getName() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/BasicBlockAnnotationGap.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/BasicBlockAnnotationGap.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/BasicBlockAnnotationGap.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/BasicBlockAnnotationGap.ql new file mode 100644 index 000000000000..0a2b08ff3fdd --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/BasicBlockAnnotationGap.ql @@ -0,0 +1,23 @@ +/** + * Checks that within a basic block, if a node is annotated then its + * successor is also annotated (or excluded). A gap in annotations + * within a basic block indicates a missing annotation, since there + * are no branches to justify the gap. + * + * Nodes with exceptional successors are excluded, as the exception + * edge leaves the basic block and the normal successor may be dead. + */ + +import python +import TimerUtils +import OldCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils +private import Utils::CfgTests + +from TimerCfgNode a, CfgNode succ +where basicBlockAnnotationGap(a, succ) +select a, "Annotated node followed by unannotated $@ in the same basic block", succ, + succ.getNode().toString() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/BasicBlockOrdering.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/BasicBlockOrdering.expected new file mode 100644 index 000000000000..80fa3350282f --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/BasicBlockOrdering.expected @@ -0,0 +1,13 @@ +| test_boolean.py:9:10:9:43 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:9:59:9:59 | IntegerLiteral | timestamp 2 | test_boolean.py:9:19:9:19 | IntegerLiteral | timestamp 0 | +| test_boolean.py:15:10:15:43 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:15:50:15:50 | IntegerLiteral | timestamp 1 | test_boolean.py:15:20:15:20 | IntegerLiteral | timestamp 0 | +| test_boolean.py:21:10:21:42 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:21:49:21:49 | IntegerLiteral | timestamp 1 | test_boolean.py:21:19:21:19 | IntegerLiteral | timestamp 0 | +| test_boolean.py:27:10:27:43 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:27:59:27:59 | IntegerLiteral | timestamp 2 | test_boolean.py:27:20:27:20 | IntegerLiteral | timestamp 0 | +| test_boolean.py:40:10:40:61 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:40:86:40:86 | IntegerLiteral | timestamp 3 | test_boolean.py:40:16:40:16 | IntegerLiteral | timestamp 0 | +| test_boolean.py:46:10:46:61 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:46:86:46:86 | IntegerLiteral | timestamp 3 | test_boolean.py:46:16:46:16 | IntegerLiteral | timestamp 0 | +| test_boolean.py:52:10:52:95 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:52:120:52:120 | IntegerLiteral | timestamp 4 | test_boolean.py:52:20:52:20 | IntegerLiteral | timestamp 0 | +| test_boolean.py:52:10:52:95 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:52:120:52:120 | IntegerLiteral | timestamp 4 | test_boolean.py:52:63:52:63 | IntegerLiteral | timestamp 2 | +| test_boolean.py:52:11:52:47 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:52:63:52:63 | IntegerLiteral | timestamp 2 | test_boolean.py:52:20:52:20 | IntegerLiteral | timestamp 0 | +| test_boolean.py:64:10:64:52 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:64:59:64:59 | IntegerLiteral | timestamp 6 | test_boolean.py:64:17:64:17 | IntegerLiteral | timestamp 0 | +| test_boolean.py:64:10:64:52 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:64:59:64:59 | IntegerLiteral | timestamp 6 | test_boolean.py:64:27:64:27 | IntegerLiteral | timestamp 2 | +| test_boolean.py:76:10:76:51 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:76:58:76:58 | IntegerLiteral | timestamp 6 | test_boolean.py:76:17:76:17 | IntegerLiteral | timestamp 0 | +| test_boolean.py:76:10:76:51 | ControlFlowNode for BoolExpr | Basic block ordering: $@ appears before $@ | test_boolean.py:76:58:76:58 | IntegerLiteral | timestamp 6 | test_boolean.py:76:27:76:27 | IntegerLiteral | timestamp 2 | diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/BasicBlockOrdering.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/BasicBlockOrdering.ql new file mode 100644 index 000000000000..30697f1403e2 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/BasicBlockOrdering.ql @@ -0,0 +1,18 @@ +/** + * Checks that within a single basic block, annotations appear in + * increasing minimum-timestamp order. + */ + +import python +import TimerUtils +import OldCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils +private import Utils::CfgTests + +from TimerCfgNode a, TimerCfgNode b, int minA, int minB +where basicBlockOrdering(a, b, minA, minB) +select a, "Basic block ordering: $@ appears before $@", a.getTimestampExpr(minA), + "timestamp " + minA, b.getTimestampExpr(minB), "timestamp " + minB diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/ConsecutiveTimestamps.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/ConsecutiveTimestamps.expected new file mode 100644 index 000000000000..e8071c044213 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/ConsecutiveTimestamps.expected @@ -0,0 +1,9 @@ +| test_boolean.py:9:26:9:27 | IntegerLiteral | $@ in $@ has no consecutive successor (expected 2) | test_boolean.py:9:33:9:33 | IntegerLiteral | Timestamp 1 | test_boolean.py:7:1:7:27 | Function test_and_both_sides | test_and_both_sides | +| test_boolean.py:15:10:15:14 | False | $@ in $@ has no consecutive successor (expected 1) | test_boolean.py:15:20:15:20 | IntegerLiteral | Timestamp 0 | test_boolean.py:13:1:13:30 | Function test_and_short_circuit | test_and_short_circuit | +| test_boolean.py:21:10:21:13 | True | $@ in $@ has no consecutive successor (expected 1) | test_boolean.py:21:19:21:19 | IntegerLiteral | Timestamp 0 | test_boolean.py:19:1:19:29 | Function test_or_short_circuit | test_or_short_circuit | +| test_boolean.py:27:26:27:27 | IntegerLiteral | $@ in $@ has no consecutive successor (expected 2) | test_boolean.py:27:33:27:33 | IntegerLiteral | Timestamp 1 | test_boolean.py:25:1:25:26 | Function test_or_both_sides | test_or_both_sides | +| test_boolean.py:40:45:40:45 | IntegerLiteral | $@ in $@ has no consecutive successor (expected 3) | test_boolean.py:40:51:40:51 | IntegerLiteral | Timestamp 2 | test_boolean.py:38:1:38:24 | Function test_chained_and | test_chained_and | +| test_boolean.py:46:44:46:45 | IntegerLiteral | $@ in $@ has no consecutive successor (expected 3) | test_boolean.py:46:51:46:51 | IntegerLiteral | Timestamp 2 | test_boolean.py:44:1:44:23 | Function test_chained_or | test_chained_or | +| test_boolean.py:52:11:52:47 | BoolExpr | $@ in $@ has no consecutive successor (expected 3) | test_boolean.py:52:63:52:63 | IntegerLiteral | Timestamp 2 | test_boolean.py:50:1:50:25 | Function test_mixed_and_or | test_mixed_and_or | +| test_boolean.py:52:27:52:31 | False | $@ in $@ has no consecutive successor (expected 2) | test_boolean.py:52:37:52:37 | IntegerLiteral | Timestamp 1 | test_boolean.py:50:1:50:25 | Function test_mixed_and_or | test_mixed_and_or | +| test_boolean.py:52:78:52:79 | IntegerLiteral | $@ in $@ has no consecutive successor (expected 4) | test_boolean.py:52:85:52:85 | IntegerLiteral | Timestamp 3 | test_boolean.py:50:1:50:25 | Function test_mixed_and_or | test_mixed_and_or | diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/ConsecutiveTimestamps.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/ConsecutiveTimestamps.ql new file mode 100644 index 000000000000..709fd5665ea4 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/ConsecutiveTimestamps.ql @@ -0,0 +1,26 @@ +/** + * Checks that consecutive annotated nodes have consecutive timestamps: + * for each annotation with timestamp `a`, some CFG node for that annotation + * must have a next annotation containing `a + 1`. + * + * Handles CFG splitting (e.g., finally blocks duplicated for normal/exceptional + * flow) by checking that at least one split has the required successor. + * + * Only applies to functions where all annotations are in the function's + * own scope (excludes tests with generators, async, comprehensions, or + * lambdas that have annotations in nested scopes). + */ + +import python +import TimerUtils +import OldCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils +private import Utils::CfgTests + +from TimerAnnotation ann, int a +where consecutiveTimestamps(ann, a) +select ann, "$@ in $@ has no consecutive successor (expected " + (a + 1) + ")", + ann.getTimestampExpr(a), "Timestamp " + a, ann.getTestFunction(), ann.getTestFunction().getName() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/ContiguousTimestamps.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/ContiguousTimestamps.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/ContiguousTimestamps.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/ContiguousTimestamps.ql new file mode 100644 index 000000000000..456ebf447dad --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/ContiguousTimestamps.ql @@ -0,0 +1,18 @@ +/** + * Checks that timestamps form a contiguous sequence {0, 1, ..., max} + * within each test function. Every integer in the range must appear + * in at least one annotation (live or dead). + */ + +import python +import TimerUtils + +from TestFunction f, int missing, int maxTs, TimerAnnotation maxAnn +where + maxTs = max(TimerAnnotation a | a.getTestFunction() = f | a.getATimestamp()) and + maxAnn.getTestFunction() = f and + maxAnn.getATimestamp() = maxTs and + missing = [0 .. maxTs] and + not exists(TimerAnnotation a | a.getTestFunction() = f and a.getATimestamp() = missing) +select f, "Missing timestamp " + missing + " (max is $@)", maxAnn.getTimestampExpr(maxTs), + maxTs.toString() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/MissingAnnotations.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/MissingAnnotations.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/MissingAnnotations.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/MissingAnnotations.ql new file mode 100644 index 000000000000..51f324e9399c --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/MissingAnnotations.ql @@ -0,0 +1,15 @@ +/** + * Finds expressions in test functions that lack a timer annotation + * and are not part of the timer mechanism or otherwise excluded. + * An empty result means every annotatable expression is covered. + */ + +import python +import TimerUtils + +from TestFunction f, Expr e +where + e.getScope().getEnclosingScope*() = f and + not isTimerMechanism(e, f) and + not isUnannotatable(e) +select e, "Missing annotation in $@", f, f.getName() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NeverReachable.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/NeverReachable.expected new file mode 100644 index 000000000000..874a7dfb0960 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NeverReachable.expected @@ -0,0 +1,2 @@ +| test_match.py:159:13:159:13 | IntegerLiteral | Node annotated with t.never is reachable in $@ | test_match.py:151:1:151:42 | Function test_match_exhaustive_return_first | test_match_exhaustive_return_first | +| test_match.py:172:13:172:13 | IntegerLiteral | Node annotated with t.never is reachable in $@ | test_match.py:164:1:164:45 | Function test_match_exhaustive_return_wildcard | test_match_exhaustive_return_wildcard | diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NeverReachable.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/NeverReachable.ql new file mode 100644 index 000000000000..b09a936a0a40 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NeverReachable.ql @@ -0,0 +1,18 @@ +/** + * Checks that expressions annotated with `t.never` either have no CFG + * node, or if they do, that the node is not reachable from its scope's + * entry (including within the same basic block). + */ + +import python +import TimerUtils +import OldCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils::CfgTests + +from TimerAnnotation ann +where neverReachable(ann) +select ann, "Node annotated with t.never is reachable in $@", ann.getTestFunction(), + ann.getTestFunction().getName() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgAllLiveReachable.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgAllLiveReachable.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgAllLiveReachable.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgAllLiveReachable.ql new file mode 100644 index 000000000000..75f02d14a9cb --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgAllLiveReachable.ql @@ -0,0 +1,14 @@ +/** New-CFG version of AllLiveReachable. */ + +import python +import TimerUtils +import NewCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils +private import Utils::CfgTests + +from TimerCfgNode a, TestFunction f +where allLiveReachable(a, f) +select a, "Unreachable live annotation; entry of $@ does not reach this node", f, f.getName() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgAnnotationHasCfgNode.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgAnnotationHasCfgNode.expected new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgAnnotationHasCfgNode.expected @@ -0,0 +1 @@ + diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgAnnotationHasCfgNode.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgAnnotationHasCfgNode.ql new file mode 100644 index 000000000000..4b1d82e27e67 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgAnnotationHasCfgNode.ql @@ -0,0 +1,18 @@ +/** + * New-CFG version of AnnotationHasCfgNode. + * + * Checks that every timer annotation has a corresponding CFG node. + */ + +import python +import TimerUtils +import NewCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils::CfgTests + +from TimerAnnotation ann +where annotationWithoutCfgNode(ann) +select ann, "Annotation in $@ has no CFG node", ann.getTestFunction(), + ann.getTestFunction().getName() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgBasicBlockAnnotationGap.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgBasicBlockAnnotationGap.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgBasicBlockAnnotationGap.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgBasicBlockAnnotationGap.ql new file mode 100644 index 000000000000..80dd759a3651 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgBasicBlockAnnotationGap.ql @@ -0,0 +1,26 @@ +/** + * New-CFG version of BasicBlockAnnotationGap. + * + * Original: + * Checks that within a basic block, if a node is annotated then its + * successor is also annotated (or excluded). A gap in annotations + * within a basic block indicates a missing annotation, since there + * are no branches to justify the gap. + * + * Nodes with exceptional successors are excluded, as the exception + * edge leaves the basic block and the normal successor may be dead. + */ + +import python +import TimerUtils +import NewCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils +private import Utils::CfgTests + +from TimerCfgNode a, CfgNode succ +where basicBlockAnnotationGap(a, succ) +select a, "Annotated node followed by unannotated $@ in the same basic block", succ, + succ.getNode().toString() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgBasicBlockOrdering.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgBasicBlockOrdering.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgBasicBlockOrdering.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgBasicBlockOrdering.ql new file mode 100644 index 000000000000..f06d08d937e3 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgBasicBlockOrdering.ql @@ -0,0 +1,21 @@ +/** + * New-CFG version of BasicBlockOrdering. + * + * Original: + * Checks that within a single basic block, annotations appear in + * increasing minimum-timestamp order. + */ + +import python +import TimerUtils +import NewCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils +private import Utils::CfgTests + +from TimerCfgNode a, TimerCfgNode b, int minA, int minB +where basicBlockOrdering(a, b, minA, minB) +select a, "Basic block ordering: $@ appears before $@", a.getTimestampExpr(minA), + "timestamp " + minA, b.getTimestampExpr(minB), "timestamp " + minB diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgBranchTimestamps.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgBranchTimestamps.expected new file mode 100644 index 000000000000..89a93f41a01b --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgBranchTimestamps.expected @@ -0,0 +1,276 @@ +| test_assert_raise.py:51:20:51:53 | After BinaryExpr() | Timestamp 3 on true/false branch is missing a dead() annotation on the false successor in $@ | test_assert_raise.py:48:1:48:25 | Function test_bare_reraise | test_bare_reraise | +| test_assert_raise.py:51:20:51:53 | After BinaryExpr() | Timestamp 3 on true/false branch is missing a dead() annotation on the true successor in $@ | test_assert_raise.py:48:1:48:25 | Function test_bare_reraise | test_bare_reraise | +| test_assert_raise.py:51:20:51:53 | After BinaryExpr() | Timestamp 4 on true/false branch is missing a dead() annotation on the false successor in $@ | test_assert_raise.py:48:1:48:25 | Function test_bare_reraise | test_bare_reraise | +| test_assert_raise.py:51:20:51:53 | After BinaryExpr() | Timestamp 4 on true/false branch is missing a dead() annotation on the true successor in $@ | test_assert_raise.py:48:1:48:25 | Function test_bare_reraise | test_bare_reraise | +| test_loops.py:10:12:10:52 | After Compare | Timestamp 4 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:8:1:8:23 | Function test_while_loop | test_while_loop | +| test_loops.py:10:12:10:52 | After Compare | Timestamp 4 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:8:1:8:23 | Function test_while_loop | test_while_loop | +| test_loops.py:10:12:10:52 | After Compare | Timestamp 10 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:8:1:8:23 | Function test_while_loop | test_while_loop | +| test_loops.py:10:12:10:52 | After Compare | Timestamp 10 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:8:1:8:23 | Function test_while_loop | test_while_loop | +| test_loops.py:10:12:10:52 | After Compare | Timestamp 16 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:8:1:8:23 | Function test_while_loop | test_while_loop | +| test_loops.py:10:12:10:52 | After Compare | Timestamp 16 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:8:1:8:23 | Function test_while_loop | test_while_loop | +| test_loops.py:10:12:10:52 | After Compare | Timestamp 22 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:8:1:8:23 | Function test_while_loop | test_while_loop | +| test_loops.py:10:12:10:52 | After Compare | Timestamp 22 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:8:1:8:23 | Function test_while_loop | test_while_loop | +| test_loops.py:19:12:19:46 | After Compare | Timestamp 4 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:17:1:17:24 | Function test_while_break | test_while_break | +| test_loops.py:19:12:19:46 | After Compare | Timestamp 4 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:17:1:17:24 | Function test_while_break | test_while_break | +| test_loops.py:19:12:19:46 | After Compare | Timestamp 13 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:17:1:17:24 | Function test_while_break | test_while_break | +| test_loops.py:19:12:19:46 | After Compare | Timestamp 13 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:17:1:17:24 | Function test_while_break | test_while_break | +| test_loops.py:19:12:19:46 | After Compare | Timestamp 22 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:17:1:17:24 | Function test_while_break | test_while_break | +| test_loops.py:19:12:19:46 | After Compare | Timestamp 22 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:17:1:17:24 | Function test_while_break | test_while_break | +| test_loops.py:19:12:19:46 | After Compare | Timestamp 25 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:17:1:17:24 | Function test_while_break | test_while_break | +| test_loops.py:19:12:19:46 | After Compare | Timestamp 25 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:17:1:17:24 | Function test_while_break | test_while_break | +| test_loops.py:20:13:20:48 | After Compare | Timestamp 7 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:17:1:17:24 | Function test_while_break | test_while_break | +| test_loops.py:20:13:20:48 | After Compare | Timestamp 7 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:17:1:17:24 | Function test_while_break | test_while_break | +| test_loops.py:20:13:20:48 | After Compare | Timestamp 16 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:17:1:17:24 | Function test_while_break | test_while_break | +| test_loops.py:20:13:20:48 | After Compare | Timestamp 16 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:17:1:17:24 | Function test_while_break | test_while_break | +| test_loops.py:20:13:20:48 | After Compare | Timestamp 25 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:17:1:17:24 | Function test_while_break | test_while_break | +| test_loops.py:20:13:20:48 | After Compare | Timestamp 25 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:17:1:17:24 | Function test_while_break | test_while_break | +| test_loops.py:31:12:31:54 | After Compare | Timestamp 5 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:28:1:28:27 | Function test_while_continue | test_while_continue | +| test_loops.py:31:12:31:54 | After Compare | Timestamp 5 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:28:1:28:27 | Function test_while_continue | test_while_continue | +| test_loops.py:31:12:31:54 | After Compare | Timestamp 17 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:28:1:28:27 | Function test_while_continue | test_while_continue | +| test_loops.py:31:12:31:54 | After Compare | Timestamp 17 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:28:1:28:27 | Function test_while_continue | test_while_continue | +| test_loops.py:31:12:31:54 | After Compare | Timestamp 26 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:28:1:28:27 | Function test_while_continue | test_while_continue | +| test_loops.py:31:12:31:54 | After Compare | Timestamp 26 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:28:1:28:27 | Function test_while_continue | test_while_continue | +| test_loops.py:31:12:31:54 | After Compare | Timestamp 38 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:28:1:28:27 | Function test_while_continue | test_while_continue | +| test_loops.py:31:12:31:54 | After Compare | Timestamp 38 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:28:1:28:27 | Function test_while_continue | test_while_continue | +| test_loops.py:33:13:33:48 | After Compare | Timestamp 2 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:28:1:28:27 | Function test_while_continue | test_while_continue | +| test_loops.py:33:13:33:48 | After Compare | Timestamp 2 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:28:1:28:27 | Function test_while_continue | test_while_continue | +| test_loops.py:33:13:33:48 | After Compare | Timestamp 11 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:28:1:28:27 | Function test_while_continue | test_while_continue | +| test_loops.py:33:13:33:48 | After Compare | Timestamp 11 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:28:1:28:27 | Function test_while_continue | test_while_continue | +| test_loops.py:33:13:33:48 | After Compare | Timestamp 14 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:28:1:28:27 | Function test_while_continue | test_while_continue | +| test_loops.py:33:13:33:48 | After Compare | Timestamp 14 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:28:1:28:27 | Function test_while_continue | test_while_continue | +| test_loops.py:33:13:33:48 | After Compare | Timestamp 23 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:28:1:28:27 | Function test_while_continue | test_while_continue | +| test_loops.py:33:13:33:48 | After Compare | Timestamp 23 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:28:1:28:27 | Function test_while_continue | test_while_continue | +| test_loops.py:33:13:33:48 | After Compare | Timestamp 32 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:28:1:28:27 | Function test_while_continue | test_while_continue | +| test_loops.py:33:13:33:48 | After Compare | Timestamp 32 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:28:1:28:27 | Function test_while_continue | test_while_continue | +| test_loops.py:33:13:33:48 | After Compare | Timestamp 35 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:28:1:28:27 | Function test_while_continue | test_while_continue | +| test_loops.py:33:13:33:48 | After Compare | Timestamp 35 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:28:1:28:27 | Function test_while_continue | test_while_continue | +| test_loops.py:43:12:43:44 | After Compare | Timestamp 4 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:41:1:41:23 | Function test_while_else | test_while_else | +| test_loops.py:43:12:43:44 | After Compare | Timestamp 4 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:41:1:41:23 | Function test_while_else | test_while_else | +| test_loops.py:43:12:43:44 | After Compare | Timestamp 10 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:41:1:41:23 | Function test_while_else | test_while_else | +| test_loops.py:43:12:43:44 | After Compare | Timestamp 10 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:41:1:41:23 | Function test_while_else | test_while_else | +| test_loops.py:43:12:43:44 | After Compare | Timestamp 16 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:41:1:41:23 | Function test_while_else | test_while_else | +| test_loops.py:43:12:43:44 | After Compare | Timestamp 16 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:41:1:41:23 | Function test_while_else | test_while_else | +| test_loops.py:53:12:53:38 | After Compare | Timestamp 4 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:51:1:51:29 | Function test_while_else_break | test_while_else_break | +| test_loops.py:53:12:53:38 | After Compare | Timestamp 4 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:51:1:51:29 | Function test_while_else_break | test_while_else_break | +| test_loops.py:53:12:53:38 | After Compare | Timestamp 13 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:51:1:51:29 | Function test_while_else_break | test_while_else_break | +| test_loops.py:53:12:53:38 | After Compare | Timestamp 13 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:51:1:51:29 | Function test_while_else_break | test_while_else_break | +| test_loops.py:54:13:54:40 | After Compare | Timestamp 7 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:51:1:51:29 | Function test_while_else_break | test_while_else_break | +| test_loops.py:54:13:54:40 | After Compare | Timestamp 7 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:51:1:51:29 | Function test_while_else_break | test_while_else_break | +| test_loops.py:54:13:54:40 | After Compare | Timestamp 16 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:51:1:51:29 | Function test_while_else_break | test_while_else_break | +| test_loops.py:54:13:54:40 | After Compare | Timestamp 16 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:51:1:51:29 | Function test_while_else_break | test_while_else_break | +| test_loops.py:65:14:65:43 | After List | Timestamp 4 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:64:1:64:21 | Function test_for_list | test_for_list | +| test_loops.py:65:14:65:43 | After List | Timestamp 4 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:64:1:64:21 | Function test_for_list | test_for_list | +| test_loops.py:65:14:65:43 | After List | Timestamp 5 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:64:1:64:21 | Function test_for_list | test_for_list | +| test_loops.py:65:14:65:43 | After List | Timestamp 5 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:64:1:64:21 | Function test_for_list | test_for_list | +| test_loops.py:65:14:65:43 | After List | Timestamp 6 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:64:1:64:21 | Function test_for_list | test_for_list | +| test_loops.py:65:14:65:43 | After List | Timestamp 6 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:64:1:64:21 | Function test_for_list | test_for_list | +| test_loops.py:65:14:65:43 | After List | Timestamp 7 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:64:1:64:21 | Function test_for_list | test_for_list | +| test_loops.py:65:14:65:43 | After List | Timestamp 7 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:64:1:64:21 | Function test_for_list | test_for_list | +| test_loops.py:66:9:66:9 | x | Timestamp 4 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:64:1:64:21 | Function test_for_list | test_for_list | +| test_loops.py:66:9:66:9 | x | Timestamp 4 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:64:1:64:21 | Function test_for_list | test_for_list | +| test_loops.py:66:9:66:9 | x | Timestamp 5 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:64:1:64:21 | Function test_for_list | test_for_list | +| test_loops.py:66:9:66:9 | x | Timestamp 5 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:64:1:64:21 | Function test_for_list | test_for_list | +| test_loops.py:66:9:66:9 | x | Timestamp 6 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:64:1:64:21 | Function test_for_list | test_for_list | +| test_loops.py:66:9:66:9 | x | Timestamp 6 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:64:1:64:21 | Function test_for_list | test_for_list | +| test_loops.py:66:9:66:9 | x | Timestamp 7 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:64:1:64:21 | Function test_for_list | test_for_list | +| test_loops.py:66:9:66:9 | x | Timestamp 7 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:64:1:64:21 | Function test_for_list | test_for_list | +| test_loops.py:73:14:73:37 | After BinaryExpr() | Timestamp 3 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:72:1:72:22 | Function test_for_range | test_for_range | +| test_loops.py:73:14:73:37 | After BinaryExpr() | Timestamp 3 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:72:1:72:22 | Function test_for_range | test_for_range | +| test_loops.py:73:14:73:37 | After BinaryExpr() | Timestamp 4 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:72:1:72:22 | Function test_for_range | test_for_range | +| test_loops.py:73:14:73:37 | After BinaryExpr() | Timestamp 4 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:72:1:72:22 | Function test_for_range | test_for_range | +| test_loops.py:73:14:73:37 | After BinaryExpr() | Timestamp 5 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:72:1:72:22 | Function test_for_range | test_for_range | +| test_loops.py:73:14:73:37 | After BinaryExpr() | Timestamp 5 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:72:1:72:22 | Function test_for_range | test_for_range | +| test_loops.py:73:14:73:37 | After BinaryExpr() | Timestamp 6 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:72:1:72:22 | Function test_for_range | test_for_range | +| test_loops.py:73:14:73:37 | After BinaryExpr() | Timestamp 6 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:72:1:72:22 | Function test_for_range | test_for_range | +| test_loops.py:74:9:74:9 | i | Timestamp 3 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:72:1:72:22 | Function test_for_range | test_for_range | +| test_loops.py:74:9:74:9 | i | Timestamp 3 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:72:1:72:22 | Function test_for_range | test_for_range | +| test_loops.py:74:9:74:9 | i | Timestamp 4 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:72:1:72:22 | Function test_for_range | test_for_range | +| test_loops.py:74:9:74:9 | i | Timestamp 4 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:72:1:72:22 | Function test_for_range | test_for_range | +| test_loops.py:74:9:74:9 | i | Timestamp 5 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:72:1:72:22 | Function test_for_range | test_for_range | +| test_loops.py:74:9:74:9 | i | Timestamp 5 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:72:1:72:22 | Function test_for_range | test_for_range | +| test_loops.py:74:9:74:9 | i | Timestamp 6 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:72:1:72:22 | Function test_for_range | test_for_range | +| test_loops.py:74:9:74:9 | i | Timestamp 6 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:72:1:72:22 | Function test_for_range | test_for_range | +| test_loops.py:81:14:81:53 | After List | Timestamp 5 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:81:14:81:53 | After List | Timestamp 5 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:81:14:81:53 | After List | Timestamp 9 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:81:14:81:53 | After List | Timestamp 9 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:81:14:81:53 | After List | Timestamp 13 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:81:14:81:53 | After List | Timestamp 13 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:81:14:81:53 | After List | Timestamp 16 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:81:14:81:53 | After List | Timestamp 16 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:82:13:82:47 | After Compare | Timestamp 8 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:82:13:82:47 | After Compare | Timestamp 8 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:82:13:82:47 | After Compare | Timestamp 12 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:82:13:82:47 | After Compare | Timestamp 12 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:82:13:82:47 | After Compare | Timestamp 16 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:82:13:82:47 | After Compare | Timestamp 16 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:84:9:84:9 | x | Timestamp 5 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:84:9:84:9 | x | Timestamp 5 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:84:9:84:9 | x | Timestamp 9 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:84:9:84:9 | x | Timestamp 9 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:84:9:84:9 | x | Timestamp 13 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:84:9:84:9 | x | Timestamp 13 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:84:9:84:9 | x | Timestamp 16 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:84:9:84:9 | x | Timestamp 16 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:80:1:80:22 | Function test_for_break | test_for_break | +| test_loops.py:92:14:92:43 | After List | Timestamp 5 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:92:14:92:43 | After List | Timestamp 5 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:92:14:92:43 | After List | Timestamp 11 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:92:14:92:43 | After List | Timestamp 11 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:92:14:92:43 | After List | Timestamp 14 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:92:14:92:43 | After List | Timestamp 14 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:92:14:92:43 | After List | Timestamp 20 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:92:14:92:43 | After List | Timestamp 20 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:93:13:93:48 | After Compare | Timestamp 5 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:93:13:93:48 | After Compare | Timestamp 5 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:93:13:93:48 | After Compare | Timestamp 8 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:93:13:93:48 | After Compare | Timestamp 8 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:93:13:93:48 | After Compare | Timestamp 11 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:93:13:93:48 | After Compare | Timestamp 11 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:93:13:93:48 | After Compare | Timestamp 14 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:93:13:93:48 | After Compare | Timestamp 14 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:93:13:93:48 | After Compare | Timestamp 17 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:93:13:93:48 | After Compare | Timestamp 17 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:93:13:93:48 | After Compare | Timestamp 20 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:93:13:93:48 | After Compare | Timestamp 20 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:95:18:95:48 | After BinaryExpr | Timestamp 5 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:95:18:95:48 | After BinaryExpr | Timestamp 5 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:95:18:95:48 | After BinaryExpr | Timestamp 11 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:95:18:95:48 | After BinaryExpr | Timestamp 11 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:95:18:95:48 | After BinaryExpr | Timestamp 14 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:95:18:95:48 | After BinaryExpr | Timestamp 14 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:95:18:95:48 | After BinaryExpr | Timestamp 20 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:95:18:95:48 | After BinaryExpr | Timestamp 20 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:90:1:90:25 | Function test_for_continue | test_for_continue | +| test_loops.py:102:14:102:33 | After List | Timestamp 3 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:101:1:101:21 | Function test_for_else | test_for_else | +| test_loops.py:102:14:102:33 | After List | Timestamp 3 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:101:1:101:21 | Function test_for_else | test_for_else | +| test_loops.py:102:14:102:33 | After List | Timestamp 4 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:101:1:101:21 | Function test_for_else | test_for_else | +| test_loops.py:102:14:102:33 | After List | Timestamp 4 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:101:1:101:21 | Function test_for_else | test_for_else | +| test_loops.py:102:14:102:33 | After List | Timestamp 5 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:101:1:101:21 | Function test_for_else | test_for_else | +| test_loops.py:102:14:102:33 | After List | Timestamp 5 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:101:1:101:21 | Function test_for_else | test_for_else | +| test_loops.py:103:9:103:9 | x | Timestamp 3 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:101:1:101:21 | Function test_for_else | test_for_else | +| test_loops.py:103:9:103:9 | x | Timestamp 3 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:101:1:101:21 | Function test_for_else | test_for_else | +| test_loops.py:103:9:103:9 | x | Timestamp 4 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:101:1:101:21 | Function test_for_else | test_for_else | +| test_loops.py:103:9:103:9 | x | Timestamp 4 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:101:1:101:21 | Function test_for_else | test_for_else | +| test_loops.py:103:9:103:9 | x | Timestamp 5 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:101:1:101:21 | Function test_for_else | test_for_else | +| test_loops.py:103:9:103:9 | x | Timestamp 5 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:101:1:101:21 | Function test_for_else | test_for_else | +| test_loops.py:111:14:111:43 | After List | Timestamp 4 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break | +| test_loops.py:111:14:111:43 | After List | Timestamp 4 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break | +| test_loops.py:111:14:111:43 | After List | Timestamp 8 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break | +| test_loops.py:111:14:111:43 | After List | Timestamp 8 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break | +| test_loops.py:112:13:112:38 | After Compare | Timestamp 7 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break | +| test_loops.py:112:13:112:38 | After Compare | Timestamp 7 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break | +| test_loops.py:112:13:112:38 | After Compare | Timestamp 11 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break | +| test_loops.py:112:13:112:38 | After Compare | Timestamp 11 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break | +| test_loops.py:114:9:114:9 | x | Timestamp 4 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break | +| test_loops.py:114:9:114:9 | x | Timestamp 4 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break | +| test_loops.py:114:9:114:9 | x | Timestamp 8 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break | +| test_loops.py:114:9:114:9 | x | Timestamp 8 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break | +| test_loops.py:123:14:123:33 | After List | Timestamp 21 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:123:14:123:33 | After List | Timestamp 21 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:124:18:124:47 | After List | Timestamp 3 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:124:18:124:47 | After List | Timestamp 3 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:124:18:124:47 | After List | Timestamp 6 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:124:18:124:47 | After List | Timestamp 6 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:124:18:124:47 | After List | Timestamp 9 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:124:18:124:47 | After List | Timestamp 9 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:124:18:124:47 | After List | Timestamp 12 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:124:18:124:47 | After List | Timestamp 12 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:124:18:124:47 | After List | Timestamp 15 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:124:18:124:47 | After List | Timestamp 15 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:124:18:124:47 | After List | Timestamp 18 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:124:18:124:47 | After List | Timestamp 18 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:124:18:124:47 | After List | Timestamp 21 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:124:18:124:47 | After List | Timestamp 21 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:125:14:125:65 | After BinaryExpr | Timestamp 3 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:125:14:125:65 | After BinaryExpr | Timestamp 3 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:125:14:125:65 | After BinaryExpr | Timestamp 6 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:125:14:125:65 | After BinaryExpr | Timestamp 6 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:125:14:125:65 | After BinaryExpr | Timestamp 9 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:125:14:125:65 | After BinaryExpr | Timestamp 9 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:125:14:125:65 | After BinaryExpr | Timestamp 12 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:125:14:125:65 | After BinaryExpr | Timestamp 12 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:125:14:125:65 | After BinaryExpr | Timestamp 15 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:125:14:125:65 | After BinaryExpr | Timestamp 15 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:125:14:125:65 | After BinaryExpr | Timestamp 18 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:125:14:125:65 | After BinaryExpr | Timestamp 18 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:125:14:125:65 | After BinaryExpr | Timestamp 21 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:125:14:125:65 | After BinaryExpr | Timestamp 21 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops | +| test_loops.py:133:11:133:14 | True | Timestamp 2 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:131:1:131:29 | Function test_while_true_break | test_while_true_break | +| test_loops.py:133:11:133:14 | True | Timestamp 2 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:131:1:131:29 | Function test_while_true_break | test_while_true_break | +| test_loops.py:133:11:133:14 | True | Timestamp 9 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:131:1:131:29 | Function test_while_true_break | test_while_true_break | +| test_loops.py:133:11:133:14 | True | Timestamp 9 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:131:1:131:29 | Function test_while_true_break | test_while_true_break | +| test_loops.py:133:11:133:14 | True | Timestamp 16 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:131:1:131:29 | Function test_while_true_break | test_while_true_break | +| test_loops.py:133:11:133:14 | True | Timestamp 16 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:131:1:131:29 | Function test_while_true_break | test_while_true_break | +| test_loops.py:133:11:133:14 | True | Timestamp 22 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:131:1:131:29 | Function test_while_true_break | test_while_true_break | +| test_loops.py:133:11:133:14 | True | Timestamp 22 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:131:1:131:29 | Function test_while_true_break | test_while_true_break | +| test_loops.py:135:13:135:48 | After Compare | Timestamp 1 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:131:1:131:29 | Function test_while_true_break | test_while_true_break | +| test_loops.py:135:13:135:48 | After Compare | Timestamp 1 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:131:1:131:29 | Function test_while_true_break | test_while_true_break | +| test_loops.py:135:13:135:48 | After Compare | Timestamp 8 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:131:1:131:29 | Function test_while_true_break | test_while_true_break | +| test_loops.py:135:13:135:48 | After Compare | Timestamp 8 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:131:1:131:29 | Function test_while_true_break | test_while_true_break | +| test_loops.py:135:13:135:48 | After Compare | Timestamp 15 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:131:1:131:29 | Function test_while_true_break | test_while_true_break | +| test_loops.py:135:13:135:48 | After Compare | Timestamp 15 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:131:1:131:29 | Function test_while_true_break | test_while_true_break | +| test_loops.py:135:13:135:48 | After Compare | Timestamp 22 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:131:1:131:29 | Function test_while_true_break | test_while_true_break | +| test_loops.py:135:13:135:48 | After Compare | Timestamp 22 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:131:1:131:29 | Function test_while_true_break | test_while_true_break | +| test_loops.py:143:21:143:83 | After BinaryExpr() | Timestamp 6 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:142:1:142:26 | Function test_for_enumerate | test_for_enumerate | +| test_loops.py:143:21:143:83 | After BinaryExpr() | Timestamp 6 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:142:1:142:26 | Function test_for_enumerate | test_for_enumerate | +| test_loops.py:143:21:143:83 | After BinaryExpr() | Timestamp 8 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:142:1:142:26 | Function test_for_enumerate | test_for_enumerate | +| test_loops.py:143:21:143:83 | After BinaryExpr() | Timestamp 8 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:142:1:142:26 | Function test_for_enumerate | test_for_enumerate | +| test_loops.py:143:21:143:83 | After BinaryExpr() | Timestamp 10 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:142:1:142:26 | Function test_for_enumerate | test_for_enumerate | +| test_loops.py:143:21:143:83 | After BinaryExpr() | Timestamp 10 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:142:1:142:26 | Function test_for_enumerate | test_for_enumerate | +| test_loops.py:143:21:143:83 | After BinaryExpr() | Timestamp 12 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:142:1:142:26 | Function test_for_enumerate | test_for_enumerate | +| test_loops.py:143:21:143:83 | After BinaryExpr() | Timestamp 12 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:142:1:142:26 | Function test_for_enumerate | test_for_enumerate | +| test_loops.py:145:9:145:11 | val | Timestamp 6 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:142:1:142:26 | Function test_for_enumerate | test_for_enumerate | +| test_loops.py:145:9:145:11 | val | Timestamp 6 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:142:1:142:26 | Function test_for_enumerate | test_for_enumerate | +| test_loops.py:145:9:145:11 | val | Timestamp 8 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:142:1:142:26 | Function test_for_enumerate | test_for_enumerate | +| test_loops.py:145:9:145:11 | val | Timestamp 8 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:142:1:142:26 | Function test_for_enumerate | test_for_enumerate | +| test_loops.py:145:9:145:11 | val | Timestamp 10 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:142:1:142:26 | Function test_for_enumerate | test_for_enumerate | +| test_loops.py:145:9:145:11 | val | Timestamp 10 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:142:1:142:26 | Function test_for_enumerate | test_for_enumerate | +| test_loops.py:145:9:145:11 | val | Timestamp 12 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:142:1:142:26 | Function test_for_enumerate | test_for_enumerate | +| test_loops.py:145:9:145:11 | val | Timestamp 12 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:142:1:142:26 | Function test_for_enumerate | test_for_enumerate | +| test_match.py:16:11:16:11 | x | Timestamp 2 on true/false branch is missing a dead() annotation on the false successor in $@ | test_match.py:14:1:14:26 | Function test_match_literal | test_match_literal | +| test_match.py:16:11:16:11 | x | Timestamp 2 on true/false branch is missing a dead() annotation on the true successor in $@ | test_match.py:14:1:14:26 | Function test_match_literal | test_match_literal | +| test_match.py:16:11:16:11 | x | Timestamp 3 on true/false branch is missing a dead() annotation on the false successor in $@ | test_match.py:14:1:14:26 | Function test_match_literal | test_match_literal | +| test_match.py:16:11:16:11 | x | Timestamp 3 on true/false branch is missing a dead() annotation on the true successor in $@ | test_match.py:14:1:14:26 | Function test_match_literal | test_match_literal | +| test_match.py:27:11:27:11 | x | Timestamp 2 on true/false branch is missing a dead() annotation on the false successor in $@ | test_match.py:25:1:25:38 | Function test_match_literal_fallthrough | test_match_literal_fallthrough | +| test_match.py:27:11:27:11 | x | Timestamp 2 on true/false branch is missing a dead() annotation on the true successor in $@ | test_match.py:25:1:25:38 | Function test_match_literal_fallthrough | test_match_literal_fallthrough | +| test_match.py:27:11:27:11 | x | Timestamp 3 on true/false branch is missing a dead() annotation on the false successor in $@ | test_match.py:25:1:25:38 | Function test_match_literal_fallthrough | test_match_literal_fallthrough | +| test_match.py:27:11:27:11 | x | Timestamp 3 on true/false branch is missing a dead() annotation on the true successor in $@ | test_match.py:25:1:25:38 | Function test_match_literal_fallthrough | test_match_literal_fallthrough | +| test_match.py:51:11:51:11 | x | Timestamp 2 on true/false branch is missing a dead() annotation on the false successor in $@ | test_match.py:49:1:49:26 | Function test_match_capture | test_match_capture | +| test_match.py:51:11:51:11 | x | Timestamp 2 on true/false branch is missing a dead() annotation on the true successor in $@ | test_match.py:49:1:49:26 | Function test_match_capture | test_match_capture | +| test_match.py:51:11:51:11 | x | Timestamp 3 on true/false branch is missing a dead() annotation on the false successor in $@ | test_match.py:49:1:49:26 | Function test_match_capture | test_match_capture | +| test_match.py:51:11:51:11 | x | Timestamp 3 on true/false branch is missing a dead() annotation on the true successor in $@ | test_match.py:49:1:49:26 | Function test_match_capture | test_match_capture | +| test_match.py:71:11:71:11 | x | Timestamp 2 on true/false branch is missing a dead() annotation on the false successor in $@ | test_match.py:69:1:69:24 | Function test_match_guard | test_match_guard | +| test_match.py:71:11:71:11 | x | Timestamp 2 on true/false branch is missing a dead() annotation on the true successor in $@ | test_match.py:69:1:69:24 | Function test_match_guard | test_match_guard | +| test_match.py:82:11:82:11 | x | Timestamp 2 on true/false branch is missing a dead() annotation on the false successor in $@ | test_match.py:80:1:80:32 | Function test_match_class_pattern | test_match_class_pattern | +| test_match.py:82:11:82:11 | x | Timestamp 2 on true/false branch is missing a dead() annotation on the true successor in $@ | test_match.py:80:1:80:32 | Function test_match_class_pattern | test_match_class_pattern | +| test_match.py:82:11:82:11 | x | Timestamp 3 on true/false branch is missing a dead() annotation on the false successor in $@ | test_match.py:80:1:80:32 | Function test_match_class_pattern | test_match_class_pattern | +| test_match.py:82:11:82:11 | x | Timestamp 3 on true/false branch is missing a dead() annotation on the true successor in $@ | test_match.py:80:1:80:32 | Function test_match_class_pattern | test_match_class_pattern | +| test_match.py:93:11:93:11 | x | Timestamp 4 on true/false branch is missing a dead() annotation on the false successor in $@ | test_match.py:91:1:91:27 | Function test_match_sequence | test_match_sequence | +| test_match.py:93:11:93:11 | x | Timestamp 4 on true/false branch is missing a dead() annotation on the true successor in $@ | test_match.py:91:1:91:27 | Function test_match_sequence | test_match_sequence | +| test_try.py:95:16:95:36 | After BinaryExpr() | Timestamp 3 on true/false branch is missing a dead() annotation on the false successor in $@ | test_try.py:92:1:92:41 | Function test_try_except_finally_exception | test_try_except_finally_exception | +| test_try.py:95:16:95:36 | After BinaryExpr() | Timestamp 3 on true/false branch is missing a dead() annotation on the true successor in $@ | test_try.py:92:1:92:41 | Function test_try_except_finally_exception | test_try_except_finally_exception | +| test_try.py:95:16:95:36 | After BinaryExpr() | Timestamp 4 on true/false branch is missing a dead() annotation on the false successor in $@ | test_try.py:92:1:92:41 | Function test_try_except_finally_exception | test_try_except_finally_exception | +| test_try.py:95:16:95:36 | After BinaryExpr() | Timestamp 4 on true/false branch is missing a dead() annotation on the true successor in $@ | test_try.py:92:1:92:41 | Function test_try_except_finally_exception | test_try_except_finally_exception | +| test_try.py:147:20:147:40 | After BinaryExpr() | Timestamp 4 on true/false branch is missing a dead() annotation on the false successor in $@ | test_try.py:142:1:142:30 | Function test_nested_try_except | test_nested_try_except | +| test_try.py:147:20:147:40 | After BinaryExpr() | Timestamp 4 on true/false branch is missing a dead() annotation on the true successor in $@ | test_try.py:142:1:142:30 | Function test_nested_try_except | test_nested_try_except | +| test_try.py:162:17:162:52 | After Compare | Timestamp 7 on true/false branch is missing a dead() annotation on the false successor in $@ | test_try.py:158:1:158:24 | Function test_try_in_loop | test_try_in_loop | +| test_try.py:162:17:162:52 | After Compare | Timestamp 7 on true/false branch is missing a dead() annotation on the true successor in $@ | test_try.py:158:1:158:24 | Function test_try_in_loop | test_try_in_loop | +| test_try.py:162:17:162:52 | After Compare | Timestamp 14 on true/false branch is missing a dead() annotation on the false successor in $@ | test_try.py:158:1:158:24 | Function test_try_in_loop | test_try_in_loop | +| test_try.py:162:17:162:52 | After Compare | Timestamp 14 on true/false branch is missing a dead() annotation on the true successor in $@ | test_try.py:158:1:158:24 | Function test_try_in_loop | test_try_in_loop | +| test_try.py:162:17:162:52 | After Compare | Timestamp 23 on true/false branch is missing a dead() annotation on the false successor in $@ | test_try.py:158:1:158:24 | Function test_try_in_loop | test_try_in_loop | +| test_try.py:162:17:162:52 | After Compare | Timestamp 23 on true/false branch is missing a dead() annotation on the true successor in $@ | test_try.py:158:1:158:24 | Function test_try_in_loop | test_try_in_loop | +| test_try.py:176:20:176:40 | After BinaryExpr() | Timestamp 3 on true/false branch is missing a dead() annotation on the false successor in $@ | test_try.py:172:1:172:20 | Function test_reraise | test_reraise | +| test_try.py:176:20:176:40 | After BinaryExpr() | Timestamp 3 on true/false branch is missing a dead() annotation on the true successor in $@ | test_try.py:172:1:172:20 | Function test_reraise | test_reraise | +| test_try.py:176:20:176:40 | After BinaryExpr() | Timestamp 4 on true/false branch is missing a dead() annotation on the false successor in $@ | test_try.py:172:1:172:20 | Function test_reraise | test_reraise | +| test_try.py:176:20:176:40 | After BinaryExpr() | Timestamp 4 on true/false branch is missing a dead() annotation on the true successor in $@ | test_try.py:172:1:172:20 | Function test_reraise | test_reraise | +| test_with.py:55:14:55:33 | After List | Timestamp 3 on true/false branch is missing a dead() annotation on the false successor in $@ | test_with.py:54:1:54:25 | Function test_with_in_loop | test_with_in_loop | +| test_with.py:55:14:55:33 | After List | Timestamp 3 on true/false branch is missing a dead() annotation on the true successor in $@ | test_with.py:54:1:54:25 | Function test_with_in_loop | test_with_in_loop | +| test_with.py:55:14:55:33 | After List | Timestamp 6 on true/false branch is missing a dead() annotation on the false successor in $@ | test_with.py:54:1:54:25 | Function test_with_in_loop | test_with_in_loop | +| test_with.py:55:14:55:33 | After List | Timestamp 6 on true/false branch is missing a dead() annotation on the true successor in $@ | test_with.py:54:1:54:25 | Function test_with_in_loop | test_with_in_loop | +| test_with.py:55:14:55:33 | After List | Timestamp 9 on true/false branch is missing a dead() annotation on the false successor in $@ | test_with.py:54:1:54:25 | Function test_with_in_loop | test_with_in_loop | +| test_with.py:55:14:55:33 | After List | Timestamp 9 on true/false branch is missing a dead() annotation on the true successor in $@ | test_with.py:54:1:54:25 | Function test_with_in_loop | test_with_in_loop | +| test_with.py:57:17:57:17 | i | Timestamp 3 on true/false branch is missing a dead() annotation on the false successor in $@ | test_with.py:54:1:54:25 | Function test_with_in_loop | test_with_in_loop | +| test_with.py:57:17:57:17 | i | Timestamp 3 on true/false branch is missing a dead() annotation on the true successor in $@ | test_with.py:54:1:54:25 | Function test_with_in_loop | test_with_in_loop | +| test_with.py:57:17:57:17 | i | Timestamp 6 on true/false branch is missing a dead() annotation on the false successor in $@ | test_with.py:54:1:54:25 | Function test_with_in_loop | test_with_in_loop | +| test_with.py:57:17:57:17 | i | Timestamp 6 on true/false branch is missing a dead() annotation on the true successor in $@ | test_with.py:54:1:54:25 | Function test_with_in_loop | test_with_in_loop | +| test_with.py:57:17:57:17 | i | Timestamp 9 on true/false branch is missing a dead() annotation on the false successor in $@ | test_with.py:54:1:54:25 | Function test_with_in_loop | test_with_in_loop | +| test_with.py:57:17:57:17 | i | Timestamp 9 on true/false branch is missing a dead() annotation on the true successor in $@ | test_with.py:54:1:54:25 | Function test_with_in_loop | test_with_in_loop | diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgBranchTimestamps.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgBranchTimestamps.ql new file mode 100644 index 000000000000..cd591b867666 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgBranchTimestamps.ql @@ -0,0 +1,23 @@ +/** + * New-CFG version of BranchTimestamps. + * + * Checks that when a node has both a true and false successor, the + * live timestamps on one branch are marked as dead on the other. + * This ensures that boolean branches are fully annotated with dead() + * markers for the paths not taken. + */ + +import python +import TimerUtils +import NewCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils +private import Utils::CfgTests + +from TimerCfgNode node, int ts, string branch +where missingBranchTimestamp(node, ts, branch) +select node, + "Timestamp " + ts + " on true/false branch is missing a dead() annotation on the " + branch + + " successor in $@", node.getTestFunction(), node.getTestFunction().getName() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgConsecutivePredecessorTimestamps.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgConsecutivePredecessorTimestamps.expected new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgConsecutivePredecessorTimestamps.expected @@ -0,0 +1 @@ + diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgConsecutivePredecessorTimestamps.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgConsecutivePredecessorTimestamps.ql new file mode 100644 index 000000000000..3feacae264e5 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgConsecutivePredecessorTimestamps.ql @@ -0,0 +1,22 @@ +/** + * New-CFG version of ConsecutivePredecessorTimestamps. + * + * Checks that each annotated node (except the minimum timestamp) has + * a predecessor annotation with timestamp `a - 1`. This is the reverse + * of ConsecutiveTimestamps: it catches nodes that are reachable but + * arrived at from the wrong place (skipping an intermediate node). + */ + +import python +import TimerUtils +import NewCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils +private import Utils::CfgTests + +from TimerAnnotation ann, int a +where consecutivePredecessorTimestamps(ann, a) +select ann, "$@ in $@ has no consecutive predecessor (expected " + (a - 1) + ")", + ann.getTimestampExpr(a), "Timestamp " + a, ann.getTestFunction(), ann.getTestFunction().getName() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgConsecutiveTimestamps.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgConsecutiveTimestamps.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgConsecutiveTimestamps.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgConsecutiveTimestamps.ql new file mode 100644 index 000000000000..8e52663d6eaf --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgConsecutiveTimestamps.ql @@ -0,0 +1,29 @@ +/** + * New-CFG version of ConsecutiveTimestamps. + * + * Original: + * Checks that consecutive annotated nodes have consecutive timestamps: + * for each annotation with timestamp `a`, some CFG node for that annotation + * must have a next annotation containing `a + 1`. + * + * Handles CFG splitting (e.g., finally blocks duplicated for normal/exceptional + * flow) by checking that at least one split has the required successor. + * + * Only applies to functions where all annotations are in the function's + * own scope (excludes tests with generators, async, comprehensions, or + * lambdas that have annotations in nested scopes). + */ + +import python +import TimerUtils +import NewCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils +private import Utils::CfgTests + +from TimerAnnotation ann, int a +where consecutiveTimestamps(ann, a) +select ann, "$@ in $@ has no consecutive successor (expected " + (a + 1) + ")", + ann.getTimestampExpr(a), "Timestamp " + a, ann.getTestFunction(), ann.getTestFunction().getName() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgImpl.qll b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgImpl.qll new file mode 100644 index 000000000000..1da80d2ee0dd --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgImpl.qll @@ -0,0 +1,101 @@ +/** + * Implementation of the evaluation-order CFG signature using the new + * shared control flow graph from AstNodeImpl. + */ + +private import python as Py +import TimerUtils +private import semmle.python.controlflow.internal.AstNodeImpl as CfgImpl +private import codeql.controlflow.SuccessorType + +private class NewControlFlowNode = CfgImpl::ControlFlowNode; + +private class NewBasicBlock = CfgImpl::BasicBlock; + +/** New (shared) CFG implementation of the evaluation-order signature. */ +module NewCfg implements EvalOrderCfgSig { + class CfgNode instanceof NewControlFlowNode { + // Use the post-order representative for each AST node: the "after" node. + // For simple leaf nodes this is the merged before/after node. For + // post-order expressions this is the TAstNode. For pre-order expressions + // (and/or/not/ternary) this uses an AfterValueNode, which places the + // expression after its operands — matching the timer test expectations. + CfgNode() { NewControlFlowNode.super.isAfter(_) } + + string toString() { result = NewControlFlowNode.super.toString() } + + Py::Location getLocation() { result = NewControlFlowNode.super.getLocation() } + + Py::AstNode getNode() { + result = CfgImpl::astNodeToPyNode(NewControlFlowNode.super.getAstNode()) + } + + CfgNode getASuccessor() { nextCfgNode(this, result) } + + CfgNode getATrueSuccessor() { + NewControlFlowNode.super.isAfterTrue(_) and + // Only where there's also a false branch (true boolean split) + exists(NewControlFlowNode other | other.isAfterFalse(NewControlFlowNode.super.getAstNode())) and + nextCfgNodeFrom(this, result) + } + + CfgNode getAFalseSuccessor() { + NewControlFlowNode.super.isAfterFalse(_) and + // Only where there's also a true branch (true boolean split) + exists(NewControlFlowNode other | other.isAfterTrue(NewControlFlowNode.super.getAstNode())) and + nextCfgNodeFrom(this, result) + } + + CfgNode getAnExceptionalSuccessor() { + exists(NewControlFlowNode mid | + mid = NewControlFlowNode.super.getAnExceptionSuccessor() and + nextCfgNodeFrom(mid, result) + ) + } + + Py::Scope getScope() { result = NewControlFlowNode.super.getEnclosingCallable().asScope() } + + BasicBlock getBasicBlock() { + exists(NewBasicBlock bb, int i | bb.getNode(i) = this and result = bb) + } + } + + /** + * Holds if `next` is the nearest CfgNode reachable from `n` via + * one or more raw CFG successor edges, skipping non-CfgNode intermediaries. + */ + private predicate nextCfgNodeFrom(NewControlFlowNode n, CfgNode next) { + next = n.getASuccessor() + or + exists(NewControlFlowNode mid | + mid = n.getASuccessor() and + not mid instanceof CfgNode and + nextCfgNodeFrom(mid, next) + ) + } + + /** + * Holds if `next` is the nearest CfgNode successor of `n`, + * skipping synthetic intermediate nodes. + */ + private predicate nextCfgNode(CfgNode n, CfgNode next) { nextCfgNodeFrom(n, next) } + + class BasicBlock instanceof NewBasicBlock { + string toString() { result = NewBasicBlock.super.toString() } + + CfgNode getNode(int n) { result = NewBasicBlock.super.getNode(n) } + + predicate reaches(BasicBlock bb) { this = bb or this.strictlyReaches(bb) } + + predicate strictlyReaches(BasicBlock bb) { NewBasicBlock.super.getASuccessor+() = bb } + + predicate strictlyDominates(BasicBlock bb) { NewBasicBlock.super.strictlyDominates(bb) } + } + + CfgNode scopeGetEntryNode(Py::Scope s) { + exists(CfgImpl::ControlFlow::EntryNode entry | + entry.getEnclosingCallable().asScope() = s and + nextCfgNodeFrom(entry, result) + ) + } +} diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNeverReachable.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNeverReachable.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNeverReachable.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNeverReachable.ql new file mode 100644 index 000000000000..6949b2cc6e9b --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNeverReachable.ql @@ -0,0 +1,21 @@ +/** + * New-CFG version of NeverReachable. + * + * Original: + * Checks that expressions annotated with `t.never` either have no CFG + * node, or if they do, that the node is not reachable from its scope's + * entry (including within the same basic block). + */ + +import python +import TimerUtils +import NewCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils::CfgTests + +from TimerAnnotation ann +where neverReachable(ann) +select ann, "Node annotated with t.never is reachable in $@", ann.getTestFunction(), + ann.getTestFunction().getName() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoBackwardFlow.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoBackwardFlow.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoBackwardFlow.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoBackwardFlow.ql new file mode 100644 index 000000000000..442ca5f5456c --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoBackwardFlow.ql @@ -0,0 +1,22 @@ +/** + * New-CFG version of NoBackwardFlow. + * + * Original: + * Checks that time never flows backward between consecutive timer annotations + * in the CFG. For each pair of consecutive annotated nodes (A -> B), there must + * exist timestamps a in A and b in B with a < b. + */ + +import python +import TimerUtils +import NewCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils +private import Utils::CfgTests + +from TimerCfgNode a, TimerCfgNode b, int minA, int maxB +where noBackwardFlow(a, b, minA, maxB) +select a, "Backward flow: $@ flows to $@ (max timestamp $@)", a.getTimestampExpr(minA), + minA.toString(), b, b.getNode().toString(), b.getTimestampExpr(maxB), maxB.toString() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoBasicBlock.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoBasicBlock.expected new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoBasicBlock.expected @@ -0,0 +1 @@ + diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoBasicBlock.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoBasicBlock.ql new file mode 100644 index 000000000000..e07890f72502 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoBasicBlock.ql @@ -0,0 +1,18 @@ +/** + * New-CFG version of NoBasicBlock. + * + * Checks that every annotated CFG node belongs to a basic block. + */ + +import python +import TimerUtils +import NewCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils +private import Utils::CfgTests + +from CfgNode n, TestFunction f +where noBasicBlock(n, f) +select n, "CFG node in $@ does not belong to any basic block", f, f.getName() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoSharedReachable.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoSharedReachable.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoSharedReachable.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoSharedReachable.ql new file mode 100644 index 000000000000..5a1a1aba2a7a --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoSharedReachable.ql @@ -0,0 +1,21 @@ +/** + * New-CFG version of NoSharedReachable. + * + * Original: + * Checks that two annotations sharing a timestamp value are on + * mutually exclusive CFG paths (neither can reach the other). + */ + +import python +import TimerUtils +import NewCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils +private import Utils::CfgTests + +from TimerCfgNode a, TimerCfgNode b, int ts +where noSharedReachable(a, b, ts) +select a, "Shared timestamp $@ but this node reaches $@", a.getTimestampExpr(ts), ts.toString(), b, + b.getNode().toString() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgStrictForward.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgStrictForward.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgStrictForward.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgStrictForward.ql new file mode 100644 index 000000000000..ebbc60346db0 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgStrictForward.ql @@ -0,0 +1,22 @@ +/** + * New-CFG version of StrictForward. + * + * Original: + * Stronger version of NoBackwardFlow: for consecutive annotated nodes + * A -> B that both have a single timestamp (non-loop code) and B does + * NOT dominate A (forward edge), requires max(A) < min(B). + */ + +import python +import TimerUtils +import NewCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils +private import Utils::CfgTests + +from TimerCfgNode a, TimerCfgNode b, int maxA, int minB +where strictForward(a, b, maxA, minB) +select a, "Strict forward violation: $@ flows to $@", a.getTimestampExpr(maxA), "timestamp " + maxA, + b.getTimestampExpr(minB), "timestamp " + minB diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NoBackwardFlow.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/NoBackwardFlow.expected new file mode 100644 index 000000000000..1ef8be08d27b --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NoBackwardFlow.expected @@ -0,0 +1,10 @@ +| test_boolean.py:9:10:9:43 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_boolean.py:9:59:9:59 | IntegerLiteral | 2 | test_boolean.py:9:10:9:13 | ControlFlowNode for True | True | test_boolean.py:9:19:9:19 | IntegerLiteral | 0 | +| test_boolean.py:15:10:15:43 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_boolean.py:15:50:15:50 | IntegerLiteral | 1 | test_boolean.py:15:10:15:14 | ControlFlowNode for False | False | test_boolean.py:15:20:15:20 | IntegerLiteral | 0 | +| test_boolean.py:21:10:21:42 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_boolean.py:21:49:21:49 | IntegerLiteral | 1 | test_boolean.py:21:10:21:13 | ControlFlowNode for True | True | test_boolean.py:21:19:21:19 | IntegerLiteral | 0 | +| test_boolean.py:27:10:27:43 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_boolean.py:27:59:27:59 | IntegerLiteral | 2 | test_boolean.py:27:10:27:14 | ControlFlowNode for False | False | test_boolean.py:27:20:27:20 | IntegerLiteral | 0 | +| test_boolean.py:40:10:40:61 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_boolean.py:40:86:40:86 | IntegerLiteral | 3 | test_boolean.py:40:10:40:10 | ControlFlowNode for IntegerLiteral | IntegerLiteral | test_boolean.py:40:16:40:16 | IntegerLiteral | 0 | +| test_boolean.py:46:10:46:61 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_boolean.py:46:86:46:86 | IntegerLiteral | 3 | test_boolean.py:46:10:46:10 | ControlFlowNode for IntegerLiteral | IntegerLiteral | test_boolean.py:46:16:46:16 | IntegerLiteral | 0 | +| test_boolean.py:52:10:52:95 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_boolean.py:52:120:52:120 | IntegerLiteral | 4 | test_boolean.py:52:11:52:47 | ControlFlowNode for BoolExpr | BoolExpr | test_boolean.py:52:63:52:63 | IntegerLiteral | 2 | +| test_boolean.py:52:11:52:47 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_boolean.py:52:63:52:63 | IntegerLiteral | 2 | test_boolean.py:52:11:52:14 | ControlFlowNode for True | True | test_boolean.py:52:20:52:20 | IntegerLiteral | 0 | +| test_boolean.py:64:10:64:52 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_boolean.py:64:59:64:59 | IntegerLiteral | 6 | test_boolean.py:64:11:64:11 | ControlFlowNode for f | f | test_boolean.py:64:17:64:17 | IntegerLiteral | 0 | +| test_boolean.py:76:10:76:51 | ControlFlowNode for BoolExpr | Backward flow: $@ flows to $@ (max timestamp $@) | test_boolean.py:76:58:76:58 | IntegerLiteral | 6 | test_boolean.py:76:11:76:11 | ControlFlowNode for f | f | test_boolean.py:76:17:76:17 | IntegerLiteral | 0 | diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NoBackwardFlow.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/NoBackwardFlow.ql new file mode 100644 index 000000000000..4acf45db3cda --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NoBackwardFlow.ql @@ -0,0 +1,19 @@ +/** + * Checks that time never flows backward between consecutive timer annotations + * in the CFG. For each pair of consecutive annotated nodes (A -> B), there must + * exist timestamps a in A and b in B with a < b. + */ + +import python +import TimerUtils +import OldCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils +private import Utils::CfgTests + +from TimerCfgNode a, TimerCfgNode b, int minA, int maxB +where noBackwardFlow(a, b, minA, maxB) +select a, "Backward flow: $@ flows to $@ (max timestamp $@)", a.getTimestampExpr(minA), + minA.toString(), b, b.getNode().toString(), b.getTimestampExpr(maxB), maxB.toString() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NoBasicBlock.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/NoBasicBlock.expected new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NoBasicBlock.expected @@ -0,0 +1 @@ + diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NoBasicBlock.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/NoBasicBlock.ql new file mode 100644 index 000000000000..5568bd2a9a4a --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NoBasicBlock.ql @@ -0,0 +1,16 @@ +/** + * Checks that every annotated CFG node belongs to a basic block. + */ + +import python +import TimerUtils +import OldCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils +private import Utils::CfgTests + +from CfgNode n, TestFunction f +where noBasicBlock(n, f) +select n, "CFG node in $@ does not belong to any basic block", f, f.getName() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NoSharedReachable.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/NoSharedReachable.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NoSharedReachable.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/NoSharedReachable.ql new file mode 100644 index 000000000000..1fcceb2aca98 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NoSharedReachable.ql @@ -0,0 +1,18 @@ +/** + * Checks that two annotations sharing a timestamp value are on + * mutually exclusive CFG paths (neither can reach the other). + */ + +import python +import TimerUtils +import OldCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils +private import Utils::CfgTests + +from TimerCfgNode a, TimerCfgNode b, int ts +where noSharedReachable(a, b, ts) +select a, "Shared timestamp $@ but this node reaches $@", a.getTimestampExpr(ts), ts.toString(), b, + b.getNode().toString() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/OldCfgImpl.qll b/python/ql/test/library-tests/ControlFlow/evaluation-order/OldCfgImpl.qll new file mode 100644 index 000000000000..6ddfe672de75 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/OldCfgImpl.qll @@ -0,0 +1,16 @@ +/** + * Implementation of the evaluation-order CFG signature using the existing + * Python control flow graph. + */ + +private import python as Py +import TimerUtils + +/** Existing Python CFG implementation of the evaluation-order signature. */ +module OldCfg implements EvalOrderCfgSig { + class CfgNode = Py::ControlFlowNode; + + class BasicBlock = Py::BasicBlock; + + CfgNode scopeGetEntryNode(Scope s) { result = s.getEntryNode() } +} diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/StrictForward.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/StrictForward.expected new file mode 100644 index 000000000000..aa03001b61bd --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/StrictForward.expected @@ -0,0 +1,10 @@ +| test_boolean.py:9:10:9:43 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_boolean.py:9:59:9:59 | IntegerLiteral | timestamp 2 | test_boolean.py:9:19:9:19 | IntegerLiteral | timestamp 0 | +| test_boolean.py:15:10:15:43 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_boolean.py:15:50:15:50 | IntegerLiteral | timestamp 1 | test_boolean.py:15:20:15:20 | IntegerLiteral | timestamp 0 | +| test_boolean.py:21:10:21:42 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_boolean.py:21:49:21:49 | IntegerLiteral | timestamp 1 | test_boolean.py:21:19:21:19 | IntegerLiteral | timestamp 0 | +| test_boolean.py:27:10:27:43 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_boolean.py:27:59:27:59 | IntegerLiteral | timestamp 2 | test_boolean.py:27:20:27:20 | IntegerLiteral | timestamp 0 | +| test_boolean.py:40:10:40:61 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_boolean.py:40:86:40:86 | IntegerLiteral | timestamp 3 | test_boolean.py:40:16:40:16 | IntegerLiteral | timestamp 0 | +| test_boolean.py:46:10:46:61 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_boolean.py:46:86:46:86 | IntegerLiteral | timestamp 3 | test_boolean.py:46:16:46:16 | IntegerLiteral | timestamp 0 | +| test_boolean.py:52:10:52:95 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_boolean.py:52:120:52:120 | IntegerLiteral | timestamp 4 | test_boolean.py:52:63:52:63 | IntegerLiteral | timestamp 2 | +| test_boolean.py:52:11:52:47 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_boolean.py:52:63:52:63 | IntegerLiteral | timestamp 2 | test_boolean.py:52:20:52:20 | IntegerLiteral | timestamp 0 | +| test_boolean.py:64:10:64:52 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_boolean.py:64:59:64:59 | IntegerLiteral | timestamp 6 | test_boolean.py:64:17:64:17 | IntegerLiteral | timestamp 0 | +| test_boolean.py:76:10:76:51 | ControlFlowNode for BoolExpr | Strict forward violation: $@ flows to $@ | test_boolean.py:76:58:76:58 | IntegerLiteral | timestamp 6 | test_boolean.py:76:17:76:17 | IntegerLiteral | timestamp 0 | diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/StrictForward.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/StrictForward.ql new file mode 100644 index 000000000000..9e64770bab4d --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/StrictForward.ql @@ -0,0 +1,19 @@ +/** + * Stronger version of NoBackwardFlow: for consecutive annotated nodes + * A -> B that both have a single timestamp (non-loop code) and B does + * NOT dominate A (forward edge), requires max(A) < min(B). + */ + +import python +import TimerUtils +import OldCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils +private import Utils::CfgTests + +from TimerCfgNode a, TimerCfgNode b, int maxA, int minB +where strictForward(a, b, maxA, minB) +select a, "Strict forward violation: $@ flows to $@", a.getTimestampExpr(maxA), "timestamp " + maxA, + b.getTimestampExpr(minB), "timestamp " + minB diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/TimerUtils.qll b/python/ql/test/library-tests/ControlFlow/evaluation-order/TimerUtils.qll new file mode 100644 index 000000000000..da66bd31b258 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/TimerUtils.qll @@ -0,0 +1,620 @@ +/** + * Utility library for identifying timer annotations in evaluation-order tests. + * + * Identifies `expr @ t[n]` (matmul), `t(expr, n)` (call), and + * `expr @ t.dead[n]` (dead-code) patterns, extracts timestamp values, + * and provides predicates for traversing consecutive annotated CFG nodes. + */ + +import python + +/** + * A function decorated with `@test` from the timer module. + * The first parameter is the timer object. + */ +class TestFunction extends Function { + TestFunction() { + this.getADecorator().(Name).getId() = "test" and + this.getPositionalParameterCount() >= 1 + } + + /** Gets the name of the timer parameter (first parameter). */ + string getTimerParamName() { result = this.getArgName(0) } +} + +/** Gets an IntegerLiteral from a timestamp expression (single int or tuple of ints). */ +private IntegerLiteral timestampLiteral(Expr timestamps) { + result = timestamps + or + result = timestamps.(Tuple).getAnElt() +} + +/** + * Gets an element from a timestamp subscript index. Each element is either + * an `IntegerLiteral` (live), a `Call` to `dead` (dead), a `Name("never")` + * (never), or a tuple containing any mix of these. + */ +private Expr timestampElement(Expr timestamps) { + result = timestamps and not timestamps instanceof Tuple + or + result = timestamps.(Tuple).getAnElt() +} + +/** Gets a live timestamp value from a subscript index expression. */ +private IntegerLiteral liveTimestampLiteral(Expr timestamps) { + result = timestampElement(timestamps) and + not result = any(Call c).getAnArg() +} + +/** Gets a dead timestamp value from a subscript index expression. */ +private IntegerLiteral deadTimestampLiteral(Expr timestamps) { + exists(Call c | + c = timestampElement(timestamps) and + c.getFunc().(Name).getId() = "dead" and + result = c.getArg(0) + ) +} + +/** Holds if the subscript index contains `never`. */ +private predicate hasNever(Expr timestamps) { + timestampElement(timestamps).(Name).getId() = "never" +} + +/** A timer annotation in the AST. */ +private newtype TTimerAnnotation = + /** `expr @ t[n]` or `expr @ t[n, m, ...]` or `expr @ t[dead(n), m, never]` */ + TMatmulAnnotation(TestFunction func, Expr annotated, Expr timestamps) { + exists(BinaryExpr be | + be.getOp() instanceof MatMult and + be.getRight().(Subscript).getObject().(Name).getId() = func.getTimerParamName() and + be.getScope().getEnclosingScope*() = func and + annotated = be.getLeft() and + timestamps = be.getRight().(Subscript).getIndex() + ) + } or + /** `t(expr, n)` */ + TCallAnnotation(TestFunction func, Expr annotated, Expr timestamps) { + exists(Call call | + call.getFunc().(Name).getId() = func.getTimerParamName() and + call.getScope().getEnclosingScope*() = func and + annotated = call.getArg(0) and + timestamps = call.getArg(1) + ) + } + +/** A timer annotation (wrapping the newtype for a clean API). */ +class TimerAnnotation extends TTimerAnnotation { + /** Gets a live timestamp value from this annotation. */ + int getATimestamp() { exists(this.getTimestampExpr(result)) } + + /** Gets the source expression for live timestamp value `ts`. */ + IntegerLiteral getTimestampExpr(int ts) { + result = liveTimestampLiteral(this.getTimestampsExpr()) and + result.getValue() = ts + } + + /** Gets a dead timestamp value from this annotation. */ + int getADeadTimestamp() { exists(this.getDeadTimestampExpr(result)) } + + /** Gets the source expression for dead timestamp value `ts`. */ + IntegerLiteral getDeadTimestampExpr(int ts) { + result = deadTimestampLiteral(this.getTimestampsExpr()) and + result.getValue() = ts + } + + /** Gets the raw timestamp expression (single element or tuple). */ + abstract Expr getTimestampsExpr(); + + /** Gets the test function this annotation belongs to. */ + abstract TestFunction getTestFunction(); + + /** Gets the annotated expression (the LHS of `@` or the first arg of `t(...)`). */ + abstract Expr getAnnotatedExpr(); + + /** Gets the enclosing annotation expression (the `BinaryExpr` or `Call`). */ + abstract Expr getTimerExpr(); + + /** Holds if timestamp `ts` is marked as dead in this annotation. */ + predicate isDeadTimestamp(int ts) { ts = this.getADeadTimestamp() } + + /** Holds if all timestamps in this annotation are dead (no live timestamps). */ + predicate isDead() { + not exists(this.getATimestamp()) and + not this.isNever() and + exists(this.getADeadTimestamp()) + } + + /** Holds if this is a never-evaluated annotation (contains `never`). */ + predicate isNever() { hasNever(this.getTimestampsExpr()) } + + string toString() { result = this.getAnnotatedExpr().toString() } + + Location getLocation() { result = this.getAnnotatedExpr().getLocation() } +} + +/** A matmul-based timer annotation: `expr @ t[...]`. */ +class MatmulTimerAnnotation extends TMatmulAnnotation, TimerAnnotation { + TestFunction func; + Expr annotated; + Expr timestamps; + + MatmulTimerAnnotation() { this = TMatmulAnnotation(func, annotated, timestamps) } + + override Expr getTimestampsExpr() { result = timestamps } + + override TestFunction getTestFunction() { result = func } + + override Expr getAnnotatedExpr() { result = annotated } + + override BinaryExpr getTimerExpr() { result.getLeft() = annotated } +} + +/** A call-based timer annotation: `t(expr, n)`. */ +class CallTimerAnnotation extends TCallAnnotation, TimerAnnotation { + TestFunction func; + Expr annotated; + Expr timestamps; + + CallTimerAnnotation() { this = TCallAnnotation(func, annotated, timestamps) } + + override Expr getTimestampsExpr() { result = timestamps } + + override TestFunction getTestFunction() { result = func } + + override Expr getAnnotatedExpr() { result = annotated } + + override Call getTimerExpr() { result.getArg(0) = annotated } +} + +/** + * Signature module defining the CFG interface needed by evaluation-order tests. + * This allows the test utilities to be instantiated with different CFG implementations. + */ +signature module EvalOrderCfgSig { + /** A control flow node. */ + class CfgNode { + /** Gets a textual representation of this node. */ + string toString(); + + /** Gets the location of this node. */ + Location getLocation(); + + /** Gets the AST node corresponding to this CFG node, if any. */ + AstNode getNode(); + + /** Gets a successor of this CFG node (including exceptional). */ + CfgNode getASuccessor(); + + /** Gets a true-branch successor of this CFG node, if any. */ + CfgNode getATrueSuccessor(); + + /** Gets a false-branch successor of this CFG node, if any. */ + CfgNode getAFalseSuccessor(); + + /** Gets an exceptional successor of this CFG node. */ + CfgNode getAnExceptionalSuccessor(); + + /** Gets the scope containing this CFG node. */ + Scope getScope(); + + /** Gets the basic block containing this CFG node. */ + BasicBlock getBasicBlock(); + } + + /** A basic block in the control flow graph. */ + class BasicBlock { + /** Gets the CFG node at position `n` in this basic block. */ + CfgNode getNode(int n); + + /** Holds if this basic block reaches `bb` (reflexive). */ + predicate reaches(BasicBlock bb); + + /** Holds if this basic block strictly reaches `bb` (non-reflexive). */ + predicate strictlyReaches(BasicBlock bb); + + /** Holds if this basic block strictly dominates `bb`. */ + predicate strictlyDominates(BasicBlock bb); + } + + /** Gets the entry CFG node for scope `s`. */ + CfgNode scopeGetEntryNode(Scope s); +} + +/** + * Parameterised module providing CFG-dependent utilities for evaluation-order tests. + * Instantiate with a specific CFG implementation to get `TimerCfgNode` and related predicates. + */ +module EvalOrderCfgUtils { + /** The CFG node type from the underlying implementation. */ + final class CfgNode = Input::CfgNode; + + /** The basic block type from the underlying implementation (named to avoid clash with `python::BasicBlock`). */ + final class CfgBasicBlock = Input::BasicBlock; + + /** Gets the entry CFG node for scope `s`. */ + CfgNode scopeGetEntryNode(Scope s) { result = Input::scopeGetEntryNode(s) } + + /** + * A CFG node corresponding to a timer annotation. + */ + class TimerCfgNode extends CfgNode { + private TimerAnnotation annot; + + TimerCfgNode() { annot.getAnnotatedExpr() = this.getNode() } + + /** Gets a timestamp value from this annotation. */ + int getATimestamp() { result = annot.getATimestamp() } + + /** Gets the source expression for timestamp value `ts`. */ + IntegerLiteral getTimestampExpr(int ts) { result = annot.getTimestampExpr(ts) } + + /** Gets the test function this annotation belongs to. */ + TestFunction getTestFunction() { result = annot.getTestFunction() } + + /** Holds if timestamp `ts` is marked as dead. */ + predicate isDeadTimestamp(int ts) { annot.isDeadTimestamp(ts) } + + /** Holds if all timestamps in this annotation are dead. */ + predicate isDead() { annot.isDead() } + + /** Holds if this is a never-evaluated annotation. */ + predicate isNever() { annot.isNever() } + } + + /** + * Holds if `next` is the next timer annotation reachable from `n` via + * CFG successors (both normal and exceptional), skipping non-annotated + * intermediaries within the same scope. + */ + predicate nextTimerAnnotation(CfgNode n, TimerCfgNode next) { + next = n.getASuccessor() and + next.getScope() = n.getScope() + or + exists(CfgNode mid | + mid = n.getASuccessor() and + not mid instanceof TimerCfgNode and + mid.getScope() = n.getScope() and + nextTimerAnnotation(mid, next) + ) + } + + /** + * Holds if `next` is the next timer annotation reachable from `n` via + * the true branch, skipping non-annotated intermediaries and after-value + * nodes for the same AST node. + */ + predicate nextTimerAnnotationFromTrue(CfgNode n, TimerCfgNode next) { + exists(CfgNode trueSucc | + trueSucc = n.getATrueSuccessor() and + trueSucc.getScope() = n.getScope() + | + // If the true successor is a different annotated node, use it + next = trueSucc and next.getNode() != n.getNode() + or + // Otherwise skip through it (it's an after-value node for the same expr) + nextTimerAnnotation(trueSucc, next) + ) + } + + /** + * Holds if `next` is the next timer annotation reachable from `n` via + * the false branch, skipping non-annotated intermediaries and after-value + * nodes for the same AST node. + */ + predicate nextTimerAnnotationFromFalse(CfgNode n, TimerCfgNode next) { + exists(CfgNode falseSucc | + falseSucc = n.getAFalseSuccessor() and + falseSucc.getScope() = n.getScope() + | + // If the false successor is a different annotated node, use it + next = falseSucc and next.getNode() != n.getNode() + or + // Otherwise skip through it (it's an after-value node for the same expr) + nextTimerAnnotation(falseSucc, next) + ) + } + + /** CFG-dependent test predicates, one per evaluation-order query. */ + module CfgTests { + /** + * Holds if live annotation `a` in function `f` is unreachable from + * the function entry in the CFG. + */ + predicate allLiveReachable(TimerCfgNode a, TestFunction f) { + not a.isDead() and + f = a.getTestFunction() and + a.getScope() = f and + not scopeGetEntryNode(f).getBasicBlock().reaches(a.getBasicBlock()) + } + + /** + * Holds if annotated node `a` is followed by unannotated `succ` in the + * same basic block. + */ + predicate basicBlockAnnotationGap(TimerCfgNode a, CfgNode succ) { + exists(CfgBasicBlock bb, int i | + a = bb.getNode(i) and + succ = bb.getNode(i + 1) + ) and + not succ instanceof TimerCfgNode and + not isUnannotatable(succ.getNode()) and + not isTimerMechanism(succ.getNode(), a.getTestFunction()) and + not exists(a.getAnExceptionalSuccessor()) and + succ.getNode() instanceof Expr + } + + /** + * Holds if annotations `a` and `b` appear in the same basic block with + * `a` before `b`, but `a`'s minimum timestamp is not less than `b`'s. + */ + predicate basicBlockOrdering(TimerCfgNode a, TimerCfgNode b, int minA, int minB) { + exists(CfgBasicBlock bb, int i, int j | a = bb.getNode(i) and b = bb.getNode(j) and i < j) and + minA = min(a.getATimestamp()) and + minB = min(b.getATimestamp()) and + minA >= minB + } + + /** + * Holds if function `f` has an annotation in a nested scope + * (generator, async function, comprehension, lambda). + */ + private predicate hasNestedScopeAnnotation(TestFunction f) { + exists(TimerAnnotation a | + a.getTestFunction() = f and + a.getAnnotatedExpr().getScope() != f + ) + } + + /** + * Holds if annotation `ann` with timestamp `a` has no consecutive + * successor (expected `a + 1`) in the CFG. + */ + predicate consecutiveTimestamps(TimerAnnotation ann, int a) { + not hasNestedScopeAnnotation(ann.getTestFunction()) and + not ann.isDead() and + a = ann.getATimestamp() and + not exists(TimerCfgNode x, TimerCfgNode y | + ann.getAnnotatedExpr() = x.getNode() and + nextTimerAnnotation(x, y) and + (a + 1) = y.getATimestamp() + ) and + // Exclude the maximum timestamp in the function (it has no successor) + not a = + max(TimerAnnotation other | + other.getTestFunction() = ann.getTestFunction() + | + other.getATimestamp() + ) + } + + /** + * Holds if the expression annotated with `t.never` is reachable from + * its scope's entry. + */ + predicate neverReachable(TimerAnnotation ann) { + ann.isNever() and + exists(CfgNode n, Scope s | + n.getNode() = ann.getAnnotatedExpr() and + s = n.getScope() and + ( + // Reachable via inter-block path (includes same block) + scopeGetEntryNode(s).getBasicBlock().reaches(n.getBasicBlock()) + or + // In same block as entry but at a later index + exists(CfgBasicBlock bb, int i, int j | + bb.getNode(i) = scopeGetEntryNode(s) and bb.getNode(j) = n and i < j + ) + ) + ) + } + + /** + * Holds if consecutive annotated nodes `a` -> `b` have backward time + * flow (`minA >= maxB`). + */ + predicate noBackwardFlow(TimerCfgNode a, TimerCfgNode b, int minA, int maxB) { + nextTimerAnnotation(a, b) and + not a.isDead() and + not b.isDead() and + minA = min(a.getATimestamp()) and + maxB = max(b.getATimestamp()) and + minA >= maxB + } + + /** + * Holds if annotations `a` and `b` share timestamp `ts` but `a` + * can reach `b` in the CFG. + */ + predicate noSharedReachable(TimerCfgNode a, TimerCfgNode b, int ts) { + a != b and + not a.isDead() and + not b.isDead() and + a.getTestFunction() = b.getTestFunction() and + ts = a.getATimestamp() and + ts = b.getATimestamp() and + ( + a.getBasicBlock().strictlyReaches(b.getBasicBlock()) + or + exists(CfgBasicBlock bb, int i, int j | a = bb.getNode(i) and b = bb.getNode(j) and i < j) + ) + } + + /** + * Holds if consecutive single-timestamp annotations `a` -> `b` on a + * forward edge have `maxA >= minB`. + */ + predicate strictForward(TimerCfgNode a, TimerCfgNode b, int maxA, int minB) { + nextTimerAnnotation(a, b) and + not a.isDead() and + not b.isDead() and + // Only apply to non-loop code (single timestamps on both sides) + strictcount(a.getATimestamp()) = 1 and + strictcount(b.getATimestamp()) = 1 and + // Forward edge: B does not strictly dominate A (excludes loop back-edges + // but still checks same-basic-block pairs) + not b.getBasicBlock().strictlyDominates(a.getBasicBlock()) and + maxA = max(a.getATimestamp()) and + minB = min(b.getATimestamp()) and + maxA >= minB + } + + /** + * Holds if CFG node `n` in test function `f` does not belong to any basic block. + */ + predicate noBasicBlock(CfgNode n, TestFunction f) { + n.getScope() = f and + not exists(n.getBasicBlock()) + } + + /** + * Holds if non-dead annotation `ann` has no corresponding CFG node. + */ + predicate annotationWithoutCfgNode(TimerAnnotation ann) { + not ann.isDead() and + not ann.isNever() and + not exists(CfgNode n | n.getNode() = ann.getAnnotatedExpr()) + } + + predicate annotationWithCfgNode(TimerAnnotation ann) { + exists(CfgNode n | n.getNode() = ann.getAnnotatedExpr()) + } + + /** + * Holds if annotation `ann` with timestamp `a` has no consecutive + * predecessor (expected `a - 1`) in the CFG. + */ + predicate consecutivePredecessorTimestamps(TimerAnnotation ann, int a) { + not hasNestedScopeAnnotation(ann.getTestFunction()) and + not ann.isDead() and + a = ann.getATimestamp() and + not exists(TimerCfgNode x, TimerCfgNode y | + ann.getAnnotatedExpr() = y.getNode() and + nextTimerAnnotation(x, y) and + (a - 1) = x.getATimestamp() + ) and + // Exclude the minimum timestamp in the function (it has no predecessor) + not a = + min(TimerAnnotation other | + other.getTestFunction() = ann.getTestFunction() and + not other.isDead() + | + other.getATimestamp() + ) + } + + /** + * Holds if `node` has both a true and false successor, but the true + * successor's timestamp `ts` is not marked as dead on the false + * successor (or vice versa). + * + * This checks that boolean branches are properly annotated: when a + * condition splits into true/false paths, the next annotated node + * on each side should account for the other side's timestamps as dead. + */ + predicate missingBranchTimestamp(TimerCfgNode node, int ts, string branch) { + not hasNestedScopeAnnotation(node.getTestFunction()) and + exists(TimerCfgNode trueNext, TimerCfgNode falseNext | + nextTimerAnnotationFromTrue(node, trueNext) and + nextTimerAnnotationFromFalse(node, falseNext) and + trueNext != falseNext + | + // True successor has live timestamp ts, but false successor + // doesn't have it as dead + ts = trueNext.getATimestamp() and + not falseNext.isDeadTimestamp(ts) and + not ts = falseNext.getATimestamp() and + branch = "false" + or + // False successor has live timestamp ts, but true successor + // doesn't have it as dead + ts = falseNext.getATimestamp() and + not trueNext.isDeadTimestamp(ts) and + not ts = trueNext.getATimestamp() and + branch = "true" + ) + } + } +} + +/** + * Holds if `e` is part of the timer mechanism: a top-level timer + * expression or a (transitive) sub-expression of one. + */ +predicate isTimerMechanism(Expr e, TestFunction f) { + exists(TimerAnnotation a | + a.getTestFunction() = f and + e = a.getTimerExpr().getASubExpression*() + ) +} + +/** + * Holds if expression `e` cannot be annotated due to Python syntax + * limitations (e.g., it is a definition target, a pattern, or part + * of a decorator application). + */ +predicate isUnannotatable(Expr e) { + // Function/class definitions + e instanceof FunctionExpr + or + e instanceof ClassExpr + or + // Docstrings are string literals used as expression statements + e instanceof StringLiteral and e.getParent() instanceof ExprStmt + or + // Function parameters are bound by the call, not evaluated in the body + e instanceof Parameter + or + // Name nodes that are definitions or deletions (assignment targets, def/class + // name bindings, augmented assignment targets, for-loop targets, del targets) + e.(Name).isDefinition() + or + e.(Name).isDeletion() + or + // Tuple/List/Starred nodes in assignment or for-loop targets are + // structural unpack patterns, not evaluations + (e instanceof Tuple or e instanceof List or e instanceof Starred) and + e = any(AssignStmt a).getATarget().getASubExpression*() + or + (e instanceof Tuple or e instanceof List or e instanceof Starred) and + e = any(For f).getTarget().getASubExpression*() + or + // The decorator call node wrapping a function/class definition, + // and its sub-expressions (the decorator name itself) + e = any(FunctionExpr func).getADecoratorCall().getASubExpression*() + or + e = any(ClassExpr cls).getADecoratorCall().getASubExpression*() + or + // Augmented assignment (x += e): the implicit BinaryExpr for the operation + e = any(AugAssign aug).getOperation() + or + // with-statement `as` variables are bindings + (e instanceof Name or e instanceof Tuple or e instanceof List) and + e = any(With w).getOptionalVars().getASubExpression*() + or + // except-clause exception type and `as` variable are part of except syntax + exists(ExceptStmt ex | e = ex.getType() or e = ex.getName()) + or + // match/case pattern expressions are part of pattern syntax + e.getParent+() instanceof Pattern + or + // Subscript/Attribute nodes on the LHS of an assignment are store + // operations, not value expressions (including nested ones like d["a"][1]) + (e instanceof Subscript or e instanceof Attribute) and + e = any(AssignStmt a).getATarget().getASubExpression*() + or + // Match/case guard nodes are part of case syntax + e instanceof Guard + or + // Yield/YieldFrom in statement position — the return value is + // discarded and cannot be meaningfully annotated + (e instanceof Yield or e instanceof YieldFrom) and + e.getParent() instanceof ExprStmt + or + // Synthetic nodes inside desugared comprehensions + e.getScope() = any(Comp c).getFunction() and + ( + e.(Name).getId() = ".0" + or + e instanceof Tuple and e.getParent() instanceof Yield + ) +} diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/test_assert_raise.py b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_assert_raise.py new file mode 100644 index 000000000000..692a9c6e407c --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_assert_raise.py @@ -0,0 +1,56 @@ +"""Assert and raise statement evaluation order.""" + +from timer import test, dead + + +@test +def test_assert_true(t): + x = True @ t[0] + assert x @ t[1] + y = 1 @ t[2] + + +@test +def test_assert_true_with_message(t): + x = True @ t[0] + assert x @ t[1], "msg" @ t[dead(2)] + y = 1 @ t[2] + + +@test +def test_assert_false_caught(t): + try: + x = False @ t[0] + assert x @ t[1], "fail" @ t[2] + except AssertionError: + y = 1 @ t[3] + + +@test +def test_raise_caught(t): + try: + x = 1 @ t[0] + raise ((ValueError @ t[1])("test" @ t[2]) @ t[3]) + except ValueError: + y = 2 @ t[4] + + +@test +def test_raise_from_caught(t): + try: + x = 1 @ t[0] + raise ((ValueError @ t[1])("test" @ t[2]) @ t[3]) from ((RuntimeError @ t[4])("cause" @ t[5]) @ t[6]) + except ValueError: + y = 2 @ t[7] + + +@test +def test_bare_reraise(t): + try: + try: + raise ((ValueError @ t[0])("test" @ t[1]) @ t[2]) + except ValueError: + x = 1 @ t[3] + raise + except ValueError: + y = 2 @ t[4] diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/test_async.py b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_async.py new file mode 100644 index 000000000000..0c9b08e3e9eb --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_async.py @@ -0,0 +1,97 @@ +"""Async/await evaluation order tests. + +Coroutine bodies are lazy — like generators, the body runs only when +awaited (or driven by the event loop). asyncio.run() drives the +coroutine to completion synchronously from the caller's perspective. +""" + +import asyncio +from contextlib import asynccontextmanager +from timer import test + + +@test +def test_simple_async(t): + """Simple async function: body runs inside asyncio.run().""" + async def coro(): + x = 1 @ t[4] + return x @ t[5] + + result = ((asyncio @ t[0]).run @ t[1])((coro @ t[2])() @ t[3]) @ t[6] + + +@test +def test_await_expression(t): + """await suspends the caller until the inner coroutine completes.""" + async def helper(): + return 1 @ t[4] + + async def main(): + x = await helper() @ t[5] + return x @ t[6] + + result = ((asyncio @ t[0]).run @ t[1])((main @ t[2])() @ t[3]) @ t[7] + + +@test +def test_async_for(t): + """async for iterates an async generator.""" + async def agen(): + yield 1 @ t[5] + yield 2 @ t[7] + + async def main(): + async for val in agen() @ t[4]: + val @ t[6, 8] + + ((asyncio @ t[0]).run @ t[1])((main @ t[2])() @ t[3]) @ t[9] + + +@test +def test_async_with(t): + """async with enters/exits an async context manager.""" + @asynccontextmanager + async def ctx(): + yield 1 @ t[5] + + async def main(): + async with ctx() @ t[4] as val: + val @ t[6] + + ((asyncio @ t[0]).run @ t[1])((main @ t[2])() @ t[3]) @ t[7] + + +@test +def test_multiple_awaits(t): + """Sequential awaits in one coroutine.""" + async def task_a(): + return 10 @ t[4] + + async def task_b(): + return 20 @ t[6] + + async def main(): + a = await task_a() @ t[5] + b = await task_b() @ t[7] + return (a @ t[8] + b @ t[9]) @ t[10] + + result = ((asyncio @ t[0]).run @ t[1])((main @ t[2])() @ t[3]) @ t[11] + + +@test +def test_gather(t): + """asyncio.gather schedules coroutines as concurrent tasks.""" + async def task_a(): + return 1 @ t[6] + + async def task_b(): + return 2 @ t[7] + + async def main(): + results = await asyncio.gather( + task_a() @ t[4], + task_b() @ t[5], + ) @ t[8] + return results @ t[9] + + result = ((asyncio @ t[0]).run @ t[1])((main @ t[2])() @ t[3]) @ t[10] diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/test_augassign.py b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_augassign.py new file mode 100644 index 000000000000..2f1d5eb5c3e6 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_augassign.py @@ -0,0 +1,53 @@ +"""Augmented assignment evaluation order.""" + +from timer import test + + +@test +def test_plus_equals(t): + x = 1 @ t[0] + x += 2 @ t[1] + y = x @ t[2] + + +@test +def test_sub_mul_div(t): + x = 20 @ t[0] + x -= 5 @ t[1] + x *= 2 @ t[2] + x /= 6 @ t[3] + x = 17 @ t[4] + x //= 3 @ t[5] + x %= 3 @ t[6] + y = x @ t[7] + + +@test +def test_power_equals(t): + x = 2 @ t[0] + x **= 3 @ t[1] + y = x @ t[2] + + +@test +def test_bitwise_equals(t): + x = 0b1111 @ t[0] + x &= 0b1010 @ t[1] + x |= 0b0101 @ t[2] + x ^= 0b0011 @ t[3] + y = x @ t[4] + + +@test +def test_shift_equals(t): + x = 1 @ t[0] + x <<= 4 @ t[1] + x >>= 2 @ t[2] + y = x @ t[3] + + +@test +def test_list_extend(t): + x = [1 @ t[0], 2 @ t[1]] @ t[2] + x += [3 @ t[3], 4 @ t[4]] @ t[5] + y = x @ t[6] diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/test_basic.py b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_basic.py new file mode 100644 index 000000000000..3e8ee925d913 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_basic.py @@ -0,0 +1,223 @@ +"""Basic expression evaluation order. + +These tests verify that sub-expressions within a single expression +are evaluated in the expected order (typically left to right for +operands of binary operators, elements of collection literals, etc.) + +Every evaluated expression has a timestamp annotation, except the +timer mechanism itself (t[n], t.dead[n]). +""" + +from timer import test, never + + +@test +def test_sequential_statements(t): + """Statements execute top to bottom.""" + x = 1 @ t[0] + y = 2 @ t[1] + z = 3 @ t[2] + + +@test +def test_binary_add(t): + """In a + b, left operand evaluates before right.""" + x = (1 @ t[0] + 2 @ t[1]) @ t[2] + + +@test +def test_binary_subtract(t): + """In a - b, left operand evaluates before right.""" + x = (10 @ t[0] - 3 @ t[1]) @ t[2] + + +@test +def test_binary_multiply(t): + """In a * b, left operand evaluates before right.""" + x = ((3 @ t[0]) * (4 @ t[1])) @ t[2] + + +@test +def test_nested_binary(t): + """Sub-expressions evaluate before their containing expression.""" + x = ((1 @ t[0] + 2 @ t[1]) @ t[2] + (3 @ t[3] + 4 @ t[4]) @ t[5]) @ t[6] + + +@test +def test_chained_add(t): + """a + b + c is (a + b) + c: left to right.""" + x = ((1 @ t[0] + 2 @ t[1]) @ t[2] + 3 @ t[3]) @ t[4] + + +@test +def test_mixed_precedence(t): + """In a + b * c, all operands still evaluate left to right.""" + x = (1 @ t[0] + ((2 @ t[1]) * (3 @ t[2])) @ t[3]) @ t[4] + + +@test +def test_string_concat(t): + """String concatenation operands: left to right.""" + x = (("hello" @ t[0] + " " @ t[1]) @ t[2] + "world" @ t[3]) @ t[4] + + +@test +def test_comparison(t): + """In a < b, left operand evaluates before right.""" + x = (1 @ t[0] < 2 @ t[1]) @ t[2] + + +@test +def test_chained_comparison(t): + """Chained a < b < c: all evaluated left to right (b only once).""" + x = (1 @ t[0] < 2 @ t[1] < 3 @ t[2]) @ t[3] + + +@test +def test_list_elements(t): + """List elements evaluate left to right.""" + x = [1 @ t[0], 2 @ t[1], 3 @ t[2]] @ t[3] + + +@test +def test_dict_entries(t): + """Dict: key before value, entries left to right.""" + d = {1 @ t[0]: "a" @ t[1], 2 @ t[2]: "b" @ t[3]} @ t[4] + + +@test +def test_tuple_elements(t): + """Tuple elements evaluate left to right.""" + x = (1 @ t[0], 2 @ t[1], 3 @ t[2]) @ t[3] + + +@test +def test_set_elements(t): + """Set elements evaluate left to right.""" + x = {1 @ t[0], 2 @ t[1], 3 @ t[2]} @ t[3] + + +@test +def test_subscript(t): + """In obj[idx], object evaluates before index.""" + x = ([10 @ t[0], 20 @ t[1], 30 @ t[2]] @ t[3])[1 @ t[4]] @ t[5] + + +@test +def test_slice(t): + """Slice parameters: object, then start, then stop.""" + x = ([1 @ t[0], 2 @ t[1], 3 @ t[2], 4 @ t[3], 5 @ t[4]] @ t[5])[1 @ t[6]:3 @ t[7]] @ t[8] + + +@test +def test_method_call(t): + """Object evaluated, then attribute lookup, then arguments left to right, then call.""" + x = (("hello world" @ t[0]).replace @ t[1])("world" @ t[2], "there" @ t[3]) @ t[4] + + +@test +def test_method_chaining(t): + """Chained method calls: left to right.""" + x = ((((" hello " @ t[0]).strip @ t[1])() @ t[2]).upper @ t[3])() @ t[4] + + +@test +def test_unary_not(t): + """Unary not: operand evaluated first.""" + x = (not True @ t[0]) @ t[1] + + +@test +def test_unary_neg(t): + """Unary negation: operand evaluated first.""" + x = (-(3 @ t[0])) @ t[1] + + +@test +def test_multiple_assignment(t): + """RHS evaluated once in x = y = expr.""" + x = y = (1 @ t[0] + 2 @ t[1]) @ t[2] + + +@test +def test_callable_syntax(t): + """t(value, n) is equivalent to value @ t[n].""" + x = (1 @ t[0] + 2 @ t[1]) @ t[2] + y = (x @ t[3] * 3 @ t[4]) @ t[5] + + +@test +def test_subscript_assign(t): + """In obj[idx] = val, value is evaluated before target sub-expressions.""" + lst = [0 @ t[0], 0 @ t[1], 0 @ t[2]] @ t[3] + (lst @ t[5])[1 @ t[6]] = 42 @ t[4] + x = lst @ t[7] + + +@test +def test_attribute_assign(t): + """In obj.attr = val, value is evaluated before the object.""" + class Obj: + pass + o = (Obj @ t[0])() @ t[1] + (o @ t[3]).x = 42 @ t[2] + y = (o @ t[4]).x @ t[5] + + +@test +def test_nested_subscript_assign(t): + """Nested subscript assignment: val, then outer obj, then keys.""" + d = {"a" @ t[0]: [0 @ t[1], 0 @ t[2]] @ t[3]} @ t[4] + (d @ t[6])["a" @ t[7]][1 @ t[8]] = 99 @ t[5] + x = d @ t[9] + + +@test +def test_unreachable_after_return(t): + """Code after return has no CFG node.""" + def f(): + x = 1 @ t[1] + return x @ t[2] + y = 2 @ t[never] + result = (f @ t[0])() @ t[3] + + +@test +def test_none_literal(t): + """None is a name constant.""" + x = None @ t[0] + y = (x @ t[1] is None @ t[2]) @ t[3] + + +@test +def test_delete(t): + """del statement removes a variable binding.""" + x = 1 @ t[0] + del x + y = 2 @ t[1] + + +@test +def test_global(t): + """global statement allows writing to module-level variable.""" + global _test_global_var + _test_global_var = 1 @ t[0] + x = _test_global_var @ t[1] + + +@test +def test_nonlocal(t): + """nonlocal statement allows inner function to rebind outer variable.""" + x = 0 @ t[0] + def inner(): + nonlocal x + x = 1 @ t[2] + (inner @ t[1])() @ t[3] + y = x @ t[4] + + +@test +def test_walrus(t): + """Walrus operator := evaluates the RHS and binds it.""" + if (y := 1 @ t[0]) @ t[1]: + z = y @ t[2] diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/test_boolean.py b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_boolean.py new file mode 100644 index 000000000000..a3b2268a8315 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_boolean.py @@ -0,0 +1,76 @@ +"""Short-circuit boolean operators and evaluation order.""" + +from timer import test, dead + + +@test +def test_and_both_sides(t): + # True and X — both operands evaluated, result is X + x = (True @ t[0] and 42 @ t[1, dead(2)]) @ t[dead(1), 2] + + +@test +def test_and_short_circuit(t): + # False and ... — right side never evaluated + x = (False @ t[0] and True @ t[dead(1)]) @ t[1, dead(2)] + + +@test +def test_or_short_circuit(t): + # True or ... — right side never evaluated + x = (True @ t[0] or False @ t[dead(1)]) @ t[1, dead(2)] + + +@test +def test_or_both_sides(t): + # False or X — both operands evaluated, result is X + x = (False @ t[0] or 42 @ t[1, dead(2)]) @ t[dead(1), 2] + + +@test +def test_not(t): + # not evaluates its operand, then negates + x = (not True @ t[0]) @ t[1] + y = (not False @ t[2]) @ t[3] + + +@test +def test_chained_and(t): + # 1 and 2 and 3 — all truthy, all evaluated left-to-right + x = (1 @ t[0] and 2 @ t[1, dead(3)] and 3 @ t[2, dead(3)]) @ t[dead(1), dead(2), 3] + + +@test +def test_chained_or(t): + # 0 or "" or 42 — first two falsy, all evaluated until truthy found + x = (0 @ t[0] or "" @ t[1, dead(3)] or 42 @ t[2, dead(3)]) @ t[dead(1), dead(2), 3] + + +@test +def test_mixed_and_or(t): + # True and False or 42 => (True and False) or 42 => False or 42 => 42 + x = ((True @ t[0] and False @ t[1, dead(2)]) @ t[dead(1), 2, dead(4)] or 42 @ t[3, dead(4)]) @ t[dead(2), dead(3), 4] + + +@test +def test_and_side_effects(t): + # Both functions called when left side is truthy + def f(): + return 10 @ t[1] + + def g(): + return 20 @ t[4] + + x = ((f @ t[0])() @ t[2] and (g @ t[3])() @ t[5]) @ t[6] + + +@test +def test_or_side_effects(t): + # Both functions called when left side is falsy + def f(): + return 0 @ t[1] + + def g(): + return 20 @ t[4] + + x = ((f @ t[0])() @ t[2] or (g @ t[3])() @ t[5]) @ t[6] diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/test_classes.py b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_classes.py new file mode 100644 index 000000000000..92313b5073c3 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_classes.py @@ -0,0 +1,74 @@ +"""Class definitions — evaluation order.""" + +from timer import test + + +@test +def test_simple_class(t): + """Simple class definition and instantiation.""" + class Foo: + pass + obj = (Foo @ t[0])() @ t[1] + + +@test +def test_class_with_bases(t): + """Base class expressions evaluated at class definition time.""" + class Base: + pass + class Derived(Base @ t[0]): + pass + obj = (Derived @ t[1])() @ t[2] + + +@test +def test_class_with_methods(t): + """Object evaluated before method is called.""" + class Foo: + def greet(self, name): + return ("hello " @ t[5] + name @ t[6]) @ t[7] + obj = (Foo @ t[0])() @ t[1] + msg = ((obj @ t[2]).greet @ t[3])("world" @ t[4]) @ t[8] + + +@test +def test_class_instantiation(t): + """Arguments to __init__ evaluate before instantiation completes.""" + class Foo: + def __init__(self, x): + (self @ t[3]).x = x @ t[2] + obj = (Foo @ t[0])(42 @ t[1]) @ t[4] + val = (obj @ t[5]).x @ t[6] + + +@test +def test_method_call(t): + """Method arguments evaluate left-to-right before the call.""" + class Calculator: + def __init__(self, value): + (self @ t[3]).value = value @ t[2] + def add(self, x): + return ((self @ t[8]).value @ t[9] + x @ t[10]) @ t[11] + calc = (Calculator @ t[0])(10 @ t[1]) @ t[4] + result = ((calc @ t[5]).add @ t[6])(5 @ t[7]) @ t[12] + + +@test +def test_class_level_attribute(t): + """Multiple attribute accesses in a single expression.""" + class Config: + debug = True @ t[0] + version = 1 @ t[1] + x = ((Config @ t[2]).debug @ t[3], (Config @ t[4]).version @ t[5]) @ t[6] + + +@test +def test_class_decorator(t): + """Decorator expression evaluated, class defined, then decorator called.""" + def add_marker(cls): + (cls @ t[2]).marked = True @ t[1] + return cls @ t[3] + @(add_marker @ t[0]) + class Foo: + pass + result = (Foo @ t[4]).marked @ t[5] diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/test_comprehensions.py b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_comprehensions.py new file mode 100644 index 000000000000..8ce8ca6e4c46 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_comprehensions.py @@ -0,0 +1,46 @@ +"""Evaluation order tests for comprehensions and generator expressions.""" + +from timer import test + + +@test +def test_list_comprehension(t): + items = [1 @ t[0], 2 @ t[1], 3 @ t[2]] @ t[3] + result = [x @ t[5, 6, 7] for x in items @ t[4]] @ t[8] + + +@test +def test_filtered_comprehension(t): + items = [1 @ t[0], 2 @ t[1], 3 @ t[2], 4 @ t[3]] @ t[4] + result = [x @ t[14, 23] for x in items @ t[5] if (x @ t[6, 10, 15, 19] % 2 @ t[7, 11, 16, 20] == 0 @ t[8, 12, 17, 21]) @ t[9, 13, 18, 22]] @ t[24] + + +@test +def test_dict_comprehension(t): + items = [("a" @ t[0], 1 @ t[1]) @ t[2], ("b" @ t[3], 2 @ t[4]) @ t[5]] @ t[6] + result = {k @ t[8, 10]: v @ t[9, 11] for k, v in items @ t[7]} @ t[12] + + +@test +def test_set_comprehension(t): + items = [1 @ t[0], 2 @ t[1], 3 @ t[2]] @ t[3] + result = {x @ t[5, 6, 7] for x in items @ t[4]} @ t[8] + + +@test +def test_generator_expression(t): + items = [1 @ t[0], 2 @ t[1], 3 @ t[2]] @ t[3] + gen = (x @ t[8, 9, 10] for x in items @ t[4]) @ t[5] + result = (list @ t[6])(gen @ t[7]) @ t[11] + + +@test +def test_nested_comprehension(t): + matrix = [[1 @ t[0], 2 @ t[1]] @ t[2], [3 @ t[3], 4 @ t[4]] @ t[5]] @ t[6] + result = [x @ t[9, 10, 12, 13] for row in matrix @ t[7] for x in row @ t[8, 11]] @ t[14] + + +@test +def test_comprehension_with_call(t): + items = [1 @ t[0], 2 @ t[1], 3 @ t[2]] @ t[3] + result = [(str @ t[5, 8, 11])(x @ t[6, 9, 12]) @ t[7, 10, 13] for x in items @ t[4]] @ t[14] diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/test_conditional.py b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_conditional.py new file mode 100644 index 000000000000..48d45a779583 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_conditional.py @@ -0,0 +1,44 @@ +"""Ternary conditional expressions and evaluation order.""" + +from timer import test, dead + + +@test +def test_ternary_true(t): + # Condition is True — consequent evaluated, alternative skipped + x = (1 @ t[1] if True @ t[0] else 2 @ t[dead(1)]) @ t[2] + + +@test +def test_ternary_false(t): + # Condition is False — alternative evaluated, consequent skipped + x = (1 @ t[dead(1)] if False @ t[0] else 2 @ t[1]) @ t[2] + + +@test +def test_ternary_nested(t): + # Nested: outer condition True, inner condition True + # ((10 if C1 else 20) if C2 else 30) — C2 first, then C1, then 10 + x = ((10 @ t[2] if True @ t[1] else 20 @ t[dead(2)]) @ t[3] if True @ t[0] else 30 @ t[dead(1)]) @ t[4] + + +@test +def test_ternary_assignment(t): + # Ternary result assigned, then used in later expression + value = (100 @ t[1] if True @ t[0] else 200 @ t[dead(1)]) @ t[2] + result = (value @ t[3] + 1 @ t[4]) @ t[5] + + +@test +def test_ternary_complex_expressions(t): + # Complex sub-expressions in condition and consequent + x = ((1 @ t[3] + 2 @ t[4]) @ t[5] if (3 @ t[0] > 2 @ t[1]) @ t[2] else (4 @ t[dead(3)] + 5 @ t[dead(4)]) @ t[dead(5)]) @ t[6] + + +@test +def test_ternary_as_argument(t): + # Ternary used as a function argument + def f(a): + return a @ t[4] + + result = (f @ t[0])((1 @ t[2] if True @ t[1] else 2 @ t[dead(2)]) @ t[3]) @ t[5] diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/test_fstring.py b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_fstring.py new file mode 100644 index 000000000000..2dd36f6ef36a --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_fstring.py @@ -0,0 +1,34 @@ +"""F-string evaluation order.""" + +from timer import test + + +@test +def test_simple_fstring(t): + name = "world" @ t[0] + s = f"hello {name @ t[1]}" @ t[2] + + +@test +def test_multi_expr_fstring(t): + a = "hello" @ t[0] + b = "world" @ t[1] + s = f"{a @ t[2]} {b @ t[3]}" @ t[4] + + +@test +def test_nested_fstring(t): + inner = "world" @ t[0] + s = f"hello {f'dear {inner @ t[1]}' @ t[2]}" @ t[3] + + +@test +def test_format_spec(t): + x = 3.14159 @ t[0] + s = f"{x @ t[1]:.2f}" @ t[2] + + +@test +def test_method_in_fstring(t): + name = "world" @ t[0] + s = f"hello {((name @ t[1]).upper @ t[2])() @ t[3]}" @ t[4] diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/test_functions.py b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_functions.py new file mode 100644 index 000000000000..e19b944c4cef --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_functions.py @@ -0,0 +1,85 @@ +"""Function calls and definitions — evaluation order.""" + +from timer import test + + +@test +def test_argument_order(t): + """Arguments evaluate left-to-right before the call.""" + def add(a, b): + return (a @ t[3] + b @ t[4]) @ t[5] + result = (add @ t[0])(1 @ t[1], 2 @ t[2]) @ t[6] + + +@test +def test_multiple_arguments(t): + """All arguments left-to-right, then the call.""" + def f(a, b, c): + return ((a @ t[4] + b @ t[5]) @ t[6] + c @ t[7]) @ t[8] + result = (f @ t[0])(1 @ t[1], 2 @ t[2], 3 @ t[3]) @ t[9] + + +@test +def test_default_arguments(t): + """Default expressions are evaluated at definition time.""" + val = 5 @ t[0] + def f(a, b=val @ t[1]): + return (a @ t[4] + b @ t[5]) @ t[6] + result = (f @ t[2])(10 @ t[3]) @ t[7] + + +@test +def test_args_kwargs(t): + """*args and **kwargs — expressions evaluated before the call.""" + def f(*args, **kwargs): + return ((sum @ t[9])(args @ t[10]) @ t[11] + (sum @ t[12])(((kwargs @ t[13]).values @ t[14])() @ t[15]) @ t[16]) @ t[17] + args = [1 @ t[0], 2 @ t[1]] @ t[2] + kwargs = {"c" @ t[3]: 3 @ t[4]} @ t[5] + result = (f @ t[6])(*args @ t[7], **kwargs @ t[8]) @ t[18] + + +@test +def test_nested_calls(t): + """Inner call completes before becoming an argument to outer call.""" + def f(x): + return (x @ t[7] + 1 @ t[8]) @ t[9] + def g(x): + return (x @ t[3] * 2 @ t[4]) @ t[5] + result = (f @ t[0])((g @ t[1])(1 @ t[2]) @ t[6]) @ t[10] + + +@test +def test_function_as_argument(t): + """Function object is just another argument, evaluated left-to-right.""" + def apply(fn, x): + return (fn @ t[3])(x @ t[4]) @ t[8] + def double(x): + return (x @ t[5] * 2 @ t[6]) @ t[7] + result = (apply @ t[0])(double @ t[1], 5 @ t[2]) @ t[9] + + +@test +def test_decorator(t): + """Decorator: expression evaluated, function defined, decorator called.""" + def my_decorator(fn): + return fn @ t[1] + @(my_decorator @ t[0]) + def f(): + return 42 @ t[3] + result = (f @ t[2])() @ t[4] + + +@test +def test_keyword_arguments(t): + """Keyword argument values evaluate left-to-right.""" + def f(a, b): + return (a @ t[3] + b @ t[4]) @ t[5] + result = (f @ t[0])(a=1 @ t[1], b=2 @ t[2]) @ t[6] + + +@test +def test_return_value(t): + """The return value is just the result of the call expression.""" + def f(x): + return (x @ t[2] * 2 @ t[3]) @ t[4] + result = (f @ t[0])(3 @ t[1]) @ t[5] diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/test_if.py b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_if.py new file mode 100644 index 000000000000..79abb278684c --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_if.py @@ -0,0 +1,114 @@ +"""If/elif/else control flow evaluation order.""" + +from timer import test, dead + + +@test +def test_if_true(t): + x = True @ t[0] + if x @ t[1]: + y = 1 @ t[2] + z = 0 @ t[3] + + +@test +def test_if_false(t): + x = False @ t[0] + if x @ t[1]: + y = 1 @ t[dead(2)] + z = 0 @ t[2] + + +@test +def test_if_else_true(t): + x = True @ t[0] + if x @ t[1]: + y = 1 @ t[2] + else: + y = 2 @ t[dead(2)] + z = 0 @ t[3] + + +@test +def test_if_else_false(t): + x = False @ t[0] + if x @ t[1]: + y = 1 @ t[dead(2)] + else: + y = 2 @ t[2] + z = 0 @ t[3] + + +@test +def test_if_elif_else_first(t): + x = 1 @ t[0] + if (x @ t[1] == 1 @ t[2]) @ t[3]: + y = "first" @ t[4] + elif (x @ t[dead(4)] == 2 @ t[dead(5)]) @ t[dead(6)]: + y = "second" @ t[dead(4)] + else: + y = "third" @ t[dead(4)] + z = 0 @ t[5] + + +@test +def test_if_elif_else_second(t): + x = 2 @ t[0] + if (x @ t[1] == 1 @ t[2]) @ t[3]: + y = "first" @ t[dead(7)] + elif (x @ t[4] == 2 @ t[5]) @ t[6]: + y = "second" @ t[7] + else: + y = "third" @ t[dead(7)] + z = 0 @ t[8] + + +@test +def test_if_elif_else_third(t): + x = 3 @ t[0] + if (x @ t[1] == 1 @ t[2]) @ t[3]: + y = "first" @ t[dead(7)] + elif (x @ t[4] == 2 @ t[5]) @ t[6]: + y = "second" @ t[dead(7)] + else: + y = "third" @ t[7] + z = 0 @ t[8] + + +@test +def test_nested_if_else(t): + x = True @ t[0] + y = True @ t[1] + if x @ t[2]: + if y @ t[3]: + z = 1 @ t[4] + else: + z = 2 @ t[dead(4)] + else: + z = 3 @ t[dead(4)] + w = 0 @ t[5] + + +@test +def test_if_compound_condition(t): + x = True @ t[0] + y = False @ t[1] + if (x @ t[2] and y @ t[3]) @ t[4]: + z = 1 @ t[dead(5)] + else: + z = 2 @ t[5] + w = 0 @ t[6] + + +@test +def test_if_pass(t): + x = True @ t[0] + if x @ t[1]: + pass + z = 0 @ t[2] + + +@test + + +@test diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/test_lambda.py b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_lambda.py new file mode 100644 index 000000000000..c60cbb5b3172 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_lambda.py @@ -0,0 +1,46 @@ +"""Lambda expressions — evaluation order.""" + +from timer import test + + +@test +def test_simple_lambda(t): + """Lambda creates a function object in one step.""" + f = (lambda x: (x @ t[3] + 1 @ t[4]) @ t[5]) @ t[0] + result = (f @ t[1])(10 @ t[2]) @ t[6] + + +@test +def test_lambda_multiple_args(t): + """Lambda call: arguments evaluate left to right.""" + f = (lambda a, b, c: ((a @ t[5] + b @ t[6]) @ t[7] + c @ t[8]) @ t[9]) @ t[0] + result = (f @ t[1])(1 @ t[2], 2 @ t[3], 3 @ t[4]) @ t[10] + + +@test +def test_lambda_default(t): + """Default argument evaluated at lambda creation time.""" + val = 5 @ t[0] + f = (lambda x, y=val @ t[1]: (x @ t[5] + y @ t[6]) @ t[7]) @ t[2] + result = (f @ t[3])(10 @ t[4]) @ t[8] + + +@test +def test_lambda_map(t): + """Lambda body runs once per element when consumed by list(map(...)).""" + f = (lambda x: (x @ t[9, 12, 15] * 2 @ t[10, 13, 16]) @ t[11, 14, 17]) @ t[0] + result = (list @ t[1])((map @ t[2])(f @ t[3], [1 @ t[4], 2 @ t[5], 3 @ t[6]] @ t[7]) @ t[8]) @ t[18] + + +@test +def test_immediately_invoked(t): + """Arguments evaluated, then immediately-invoked lambda called.""" + result = ((lambda x: (x @ t[2] + 1 @ t[3]) @ t[4]) @ t[0])(10 @ t[1]) @ t[5] + + +@test +def test_lambda_closure(t): + """Lambda captures enclosing scope; body runs at call time.""" + x = 10 @ t[0] + f = (lambda: x @ t[3]) @ t[1] + result = (f @ t[2])() @ t[4] diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/test_loops.py b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_loops.py new file mode 100644 index 000000000000..17df7a4703a3 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_loops.py @@ -0,0 +1,146 @@ +"""Loop control flow evaluation order tests.""" + +from timer import test, dead + + +# 1. Simple while loop (fixed iterations) +@test +def test_while_loop(t): + i = 0 @ t[0] + while (i @ t[1, 7, 13, 19] < 3 @ t[2, 8, 14, 20]) @ t[3, 9, 15, 21]: # 4 checks: 3 true + 1 false + i = (i @ t[4, 10, 16] + 1 @ t[5, 11, 17]) @ t[6, 12, 18] + done = True @ t[22] + + +# 2. While loop with break +@test +def test_while_break(t): + i = 0 @ t[0] + while (i @ t[1, 10, 19] < 5 @ t[2, 11, 20]) @ t[3, 12, 21]: + if (i @ t[4, 13, 22] == 2 @ t[5, 14, 23]) @ t[6, 15, 24]: + break + i = (i @ t[7, 16] + 1 @ t[8, 17]) @ t[9, 18] + done = True @ t[25] + + +# 3. While loop with continue +@test +def test_while_continue(t): + i = 0 @ t[0] + total = 0 @ t[1] + while (i @ t[2, 14, 23, 35] < 3 @ t[3, 15, 24, 36]) @ t[4, 16, 25, 37]: + i = (i @ t[5, 17, 26] + 1 @ t[6, 18, 27]) @ t[7, 19, 28] + if (i @ t[8, 20, 29] == 2 @ t[9, 21, 30]) @ t[10, 22, 31]: + continue + total = (total @ t[11, 32] + i @ t[12, 33]) @ t[13, 34] + done = True @ t[38] + + +# 4. While/else (no break — else executes) +@test +def test_while_else(t): + i = 0 @ t[0] + while (i @ t[1, 7, 13] < 2 @ t[2, 8, 14]) @ t[3, 9, 15]: + i = (i @ t[4, 10] + 1 @ t[5, 11]) @ t[6, 12] + else: + done = True @ t[16] + + +# 5. While/else (with break — else skipped) +@test +def test_while_else_break(t): + i = 0 @ t[0] + while (i @ t[1, 10] < 5 @ t[2, 11]) @ t[3, 12]: + if (i @ t[4, 13] == 1 @ t[5, 14]) @ t[6, 15]: + break + i = (i @ t[7] + 1 @ t[8]) @ t[9] + else: + never = True @ t[dead(16)] + after = True @ t[16] + + +# 6. Simple for loop over a list +@test +def test_for_list(t): + for x in [1 @ t[0], 2 @ t[1], 3 @ t[2]] @ t[3]: + x @ t[4, 5, 6] + done = True @ t[7] + + +# 7. For loop with range +@test +def test_for_range(t): + for i in (range @ t[0])(3 @ t[1]) @ t[2]: + i @ t[3, 4, 5] + done = True @ t[6] + + +# 8. For loop with break +@test +def test_for_break(t): + for x in [1 @ t[0], 2 @ t[1], 3 @ t[2], 4 @ t[3]] @ t[4]: + if (x @ t[5, 9, 13] == 3 @ t[6, 10, 14]) @ t[7, 11, 15]: + break + x @ t[8, 12] + done = True @ t[16] + + +# 9. For loop with continue +@test +def test_for_continue(t): + total = 0 @ t[0] + for x in [1 @ t[1], 2 @ t[2], 3 @ t[3]] @ t[4]: + if (x @ t[5, 11, 14] == 2 @ t[6, 12, 15]) @ t[7, 13, 16]: + continue + total = (total @ t[8, 17] + x @ t[9, 18]) @ t[10, 19] + done = True @ t[20] + + +# 10. For/else (no break — else executes) +@test +def test_for_else(t): + for x in [1 @ t[0], 2 @ t[1]] @ t[2]: + x @ t[3, 4] + else: + done = True @ t[5] + + +# 11. For/else (with break — else skipped) +@test +def test_for_else_break(t): + for x in [1 @ t[0], 2 @ t[1], 3 @ t[2]] @ t[3]: + if (x @ t[4, 8] == 2 @ t[5, 9]) @ t[6, 10]: + break + x @ t[7] + else: + never = True @ t[dead(11)] + after = True @ t[11] + + +# 12. Nested loops +@test +def test_nested_loops(t): + for i in [1 @ t[0], 2 @ t[1]] @ t[2]: + for j in [10 @ t[3, 12], 20 @ t[4, 13]] @ t[5, 14]: + (i @ t[6, 9, 15, 18, dead(21)] + j @ t[7, 10, 16, 19]) @ t[8, 11, 17, 20] + done = True @ t[dead(3), dead(6), dead(9), dead(12), dead(15), dead(18), 21] + + +# 13. While True with conditional break +@test +def test_while_true_break(t): + i = 0 @ t[0] + while True @ t[1, 8, 15]: + i = (i @ t[2, 9, 16] + 1 @ t[3, 10, 17]) @ t[4, 11, 18] + if (i @ t[5, 12, 19] == 3 @ t[6, 13, 20]) @ t[7, 14, 21]: + break + done = True @ t[22] + + +# 14. For with enumerate +@test +def test_for_enumerate(t): + for idx, val in (enumerate @ t[0])(["a" @ t[1], "b" @ t[2], "c" @ t[3]] @ t[4]) @ t[5]: + idx @ t[6, 8, 10] + val @ t[7, 9, 11] + done = True @ t[12] diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/test_match.py b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_match.py new file mode 100644 index 000000000000..ba15a2d7c857 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_match.py @@ -0,0 +1,173 @@ +"""Evaluation order for match/case (structural pattern matching, Python 3.10+).""" + +import sys +if sys.version_info < (3, 10): + print("Skipping match/case tests (requires Python 3.10+)") + print("---") + print("0/0 tests passed") + sys.exit(0) + +from timer import test, dead, never + + +@test +def test_match_literal(t): + x = 1 @ t[0] + match x @ t[1]: + case 1: + y = "one" @ t[2] + case 2: + y = "two" @ t[dead(2)] + z = y @ t[3] + + +@test +def test_match_literal_fallthrough(t): + x = 3 @ t[0] + match x @ t[1]: + case 1: + y = "one" @ t[dead(2)] + case 2: + y = "two" @ t[dead(2)] + case 3: + y = "three" @ t[2] + z = y @ t[3] + + +@test +def test_match_wildcard(t): + x = 42 @ t[0] + match x @ t[1]: + case 1: + y = "one" @ t[dead(2)] + case _: + y = "other" @ t[2] + z = y @ t[3] + + +@test +def test_match_capture(t): + x = 42 @ t[0] + match x @ t[1]: + case n: + y = n @ t[2] + z = y @ t[3] + + +@test +def test_match_or_pattern(t): + x = 2 @ t[0] + match x @ t[1]: + case 1 | 2: + y = "low" @ t[2] + case _: + y = "other" @ t[dead(2)] + z = y @ t[3] + + +@test +def test_match_guard(t): + x = 5 @ t[0] + match x @ t[1]: + case n if (n @ t[2] > 3 @ t[3]) @ t[4]: + y = n @ t[5] + case _: + y = 0 @ t[dead(5)] + z = y @ t[6] + + +@test +def test_match_class_pattern(t): + x = 42 @ t[0] + match x @ t[1]: + case int(): + y = "integer" @ t[2] + case str(): + y = "string" @ t[dead(2)] + z = y @ t[3] + + +@test +def test_match_sequence(t): + x = [1 @ t[0], 2 @ t[1]] @ t[2] + match x @ t[3]: + case [a, b]: + y = (a @ t[4] + b @ t[5]) @ t[6] + case _: + y = 0 @ t[dead(6)] + z = y @ t[7] + + +@test +def test_match_mapping(t): + x = {"key" @ t[0]: 42 @ t[1]} @ t[2] + match x @ t[3]: + case {"key": value}: + y = value @ t[4] + case _: + y = 0 @ t[dead(4)] + z = y @ t[5] + + +@test +def test_match_nested(t): + x = {"users" @ t[0]: [{"name" @ t[1]: "Alice" @ t[2]} @ t[3]] @ t[4]} @ t[5] + match x @ t[6]: + case {"users": [{"name": name}]}: + y = name @ t[7] + case _: + y = "unknown" @ t[dead(7)] + z = y @ t[8] + + +@test +def test_match_or_pattern_with_as(t): + """OR pattern with `as` binding and method call on the result.""" + clause = "foo@bar" @ t[0] + match clause @ t[1]: + case (str() as uses) | {"uses": uses}: + result = ((uses @ t[2]).partition @ t[3])("@" @ t[4]) @ t[5] + x = (result @ t[6])[0 @ t[7]] @ t[8] + case _: + raise ((ValueError @ t[dead(2)])(clause @ t[dead(3)]) @ t[dead(4)]) + y = x @ t[9] + + +@test +def test_match_wildcard_raise(t): + """Wildcard case that raises, with OR pattern on the other branch.""" + clause = 42 @ t[0] + try: + match clause @ t[1]: + case (str() as uses) | {"uses": uses}: + result = uses @ t[dead(2)] + case _: + raise ((ValueError @ t[2])(f"Invalid: {clause @ t[3]}" @ t[4]) @ t[5]) + except ValueError: + y = 0 @ t[6] + + +@test +def test_match_exhaustive_return_first(t): + """Every case returns; code after match is unreachable (first case taken).""" + def f(x): + match x @ t[2]: + case 1: + return "one" @ t[3] + case _: + return "other" @ t[dead(3)] + y = 0 @ t[never] + result = (f @ t[0])(1 @ t[1]) @ t[4] + + +@test +def test_match_exhaustive_return_wildcard(t): + """Every case returns; code after match is unreachable (wildcard taken).""" + def f(x): + match x @ t[2]: + case 1: + return "one" @ t[dead(3)] + case _: + return "other" @ t[3] + y = 0 @ t[never] + result = (f @ t[0])(99 @ t[1]) @ t[4] diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/test_try.py b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_try.py new file mode 100644 index 000000000000..dd0b15457d69 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_try.py @@ -0,0 +1,182 @@ +"""Exception handling control flow: try/except/else/finally evaluation order.""" + +from timer import test, dead, never + + +# 1. try/except — no exception raised (except block skipped) +@test +def test_try_no_exception(t): + try: + x = 1 @ t[0] + y = 2 @ t[1] + except ValueError: + z = 3 @ t[dead(2)] + after = 0 @ t[2] + + +# 2. try/except — exception raised and caught +@test +def test_try_with_exception(t): + try: + x = 1 @ t[0] + raise ((ValueError @ t[1])() @ t[2]) + y = 2 @ t[never] + except ValueError: + z = 3 @ t[3] + after = 0 @ t[4] + + +# 3. try/except/else — no exception (else runs) +@test +def test_try_except_else_no_exception(t): + try: + x = 1 @ t[0] + except ValueError: + y = 2 @ t[dead(1)] + else: + z = 3 @ t[1] + after = 0 @ t[2] + + +# 4. try/except/else — exception raised (else skipped) +@test +def test_try_except_else_with_exception(t): + try: + x = 1 @ t[0] + raise ((ValueError @ t[1])() @ t[2]) + except ValueError: + y = 2 @ t[3] + else: + z = 3 @ t[dead(3)] + after = 0 @ t[4] + + +# 5. try/finally — no exception +@test +def test_try_finally_no_exception(t): + try: + x = 1 @ t[0] + y = 2 @ t[1] + finally: + z = 3 @ t[2] + after = 0 @ t[3] + + +# 6. try/finally — exception raised (finally runs, then exception propagates) +@test +def test_try_finally_exception(t): + try: + try: + x = 1 @ t[0] + raise ((ValueError @ t[1])() @ t[2]) + finally: + y = 2 @ t[3] + except ValueError: + z = 3 @ t[4] + + +# 7. try/except/finally — no exception +@test +def test_try_except_finally_no_exception(t): + try: + x = 1 @ t[0] + except ValueError: + y = 2 @ t[dead(1)] + finally: + z = 3 @ t[1] + after = 0 @ t[2] + + +# 8. try/except/finally — exception caught +@test +def test_try_except_finally_exception(t): + try: + x = 1 @ t[0] + raise ((ValueError @ t[1])() @ t[2]) + except ValueError: + y = 2 @ t[3] + finally: + z = 3 @ t[4] + after = 0 @ t[5] + + +# 9. Multiple except clauses — first matching +@test +def test_multiple_except_first(t): + try: + x = 1 @ t[0] + raise ((ValueError @ t[1])() @ t[2]) + except ValueError: + y = 2 @ t[3] + except TypeError: + z = 3 @ t[dead(3)] + after = 0 @ t[4] + + +# 10. Multiple except clauses — second matching +@test +def test_multiple_except_second(t): + try: + x = 1 @ t[0] + raise ((TypeError @ t[1])() @ t[2]) + except ValueError: + y = 2 @ t[dead(3)] + except TypeError: + z = 3 @ t[3] + after = 0 @ t[4] + + +# 11. except with `as` binding +@test +def test_except_as_binding(t): + try: + x = 1 @ t[0] + raise ((ValueError @ t[1])("msg" @ t[2]) @ t[3]) + except ValueError as e: + y = (str @ t[4])(e @ t[5]) @ t[6] + after = 0 @ t[7] + + +# 12. Nested try/except +@test +def test_nested_try_except(t): + try: + x = 1 @ t[0] + try: + y = 2 @ t[1] + raise ((ValueError @ t[2])() @ t[3]) + except ValueError: + z = 3 @ t[4] + w = 4 @ t[5] + except TypeError: + v = 5 @ t[dead(6)] + after = 0 @ t[6] + + +# 13. try/except in a loop +@test +def test_try_in_loop(t): + total = 0 @ t[0] + for i in (range @ t[1])(3 @ t[2]) @ t[3]: + try: + if (i @ t[4, 11, 20] == 1 @ t[5, 12, 21]) @ t[6, 13, 22]: + raise ((ValueError @ t[14])() @ t[15]) + total = (total @ t[7, 23] + 1 @ t[8, 24]) @ t[9, 25] + except ValueError: + total = (total @ t[16] + 10 @ t[17]) @ t[18] + r = 0 @ t[10, 19, 26] + + +# 14. Re-raise with bare `raise` +@test +def test_reraise(t): + try: + try: + x = 1 @ t[0] + raise ((ValueError @ t[1])() @ t[2]) + except ValueError: + y = 2 @ t[3] + raise + except ValueError: + z = 3 @ t[4] + after = 0 @ t[5] diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/test_unpacking.py b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_unpacking.py new file mode 100644 index 000000000000..45f292cb0b7d --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_unpacking.py @@ -0,0 +1,48 @@ +"""Unpacking and star expressions evaluation order.""" + +from timer import test + + +@test +def test_tuple_unpack(t): + """RHS expression evaluates, then unpacking assigns targets.""" + a, b = (1 @ t[0], 2 @ t[1]) @ t[2] + x = (a @ t[3] + b @ t[4]) @ t[5] + + +@test +def test_list_unpack(t): + """List unpacking: RHS elements left to right, then unpack.""" + [a, b] = [1 @ t[0], 2 @ t[1]] @ t[2] + x = (a @ t[3] + b @ t[4]) @ t[5] + + +@test +def test_star_unpack(t): + """Star unpacking: RHS evaluates first.""" + a, *b = [1 @ t[0], 2 @ t[1], 3 @ t[2], 4 @ t[3]] @ t[4] + x = (a @ t[5], b @ t[6]) @ t[7] + + +@test +def test_nested_unpack(t): + """Nested unpacking: RHS evaluates first.""" + (a, b), c = ((1 @ t[0], 2 @ t[1]) @ t[2], 3 @ t[3]) @ t[4] + x = ((a @ t[5] + b @ t[6]) @ t[7] + c @ t[8]) @ t[9] + + +@test +def test_swap(t): + a = 1 @ t[0] + b = 2 @ t[1] + a, b = (b @ t[2], a @ t[3]) @ t[4] + x = a @ t[5] + y = b @ t[6] + + +@test +def test_unpack_for(t): + pairs = [(1 @ t[0], 2 @ t[1]) @ t[2], (3 @ t[3], 4 @ t[4]) @ t[5]] @ t[6] + for a, b in pairs @ t[7]: + x = a @ t[8, 10] + y = b @ t[9, 11] diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/test_with.py b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_with.py new file mode 100644 index 000000000000..1dcc7169092b --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_with.py @@ -0,0 +1,58 @@ +"""Evaluation order tests for with statements.""" + +from contextlib import contextmanager +from timer import test + + +@contextmanager +def ctx(value=None): + yield value + + +@test +def test_simple_with(t): + x = 1 @ t[0] + with (ctx @ t[1])() @ t[2]: + y = 2 @ t[3] + z = 3 @ t[4] + + +@test +def test_with_as(t): + with (ctx @ t[0])(42 @ t[1]) @ t[2] as v: + x = v @ t[3] + y = 0 @ t[4] + + +@test +def test_nested_with(t): + with (ctx @ t[0])() @ t[1]: + with (ctx @ t[2])() @ t[3]: + x = 1 @ t[4] + y = 2 @ t[5] + + +@test +def test_multiple_context_managers(t): + with (ctx @ t[0])(1 @ t[1]) @ t[2] as a, (ctx @ t[3])(2 @ t[4]) @ t[5] as b: + x = (a @ t[6], b @ t[7]) @ t[8] + y = 0 @ t[9] + + +@test +def test_with_exception_handling(t): + try: + with (ctx @ t[0])() @ t[1]: + x = 1 @ t[2] + raise ((ValueError @ t[3])() @ t[4]) + except ValueError: + y = 2 @ t[5] + z = 3 @ t[6] + + +@test +def test_with_in_loop(t): + for i in [1 @ t[0], 2 @ t[1]] @ t[2]: + with (ctx @ t[3, 6])() @ t[4, 7]: + x = i @ t[5, 8] + y = 0 @ t[9] diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/test_yield.py b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_yield.py new file mode 100644 index 000000000000..b2a28d793bc6 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/test_yield.py @@ -0,0 +1,105 @@ +"""Generator and yield evaluation order tests. + +Generator bodies are lazy — code runs only when iterated. The timer +annotations inside generator bodies fire interleaved with the caller's +annotations, reflecting the suspend/resume semantics of yield. +""" + +from timer import test + + +@test +def test_simple_generator(t): + """Basic generator: body runs on next(), not on gen().""" + def gen(): + yield 1 @ t[4] + yield 2 @ t[8] + + g = (gen @ t[0])() @ t[1] + x = (next @ t[2])(g @ t[3]) @ t[5] + y = (next @ t[6])(g @ t[7]) @ t[9] + + +@test +def test_multiple_yields(t): + """Three yields interleave with three next() calls.""" + def gen(): + yield 1 @ t[4] + yield 2 @ t[8] + yield 3 @ t[12] + + g = (gen @ t[0])() @ t[1] + a = (next @ t[2])(g @ t[3]) @ t[5] + b = (next @ t[6])(g @ t[7]) @ t[9] + c = (next @ t[10])(g @ t[11]) @ t[13] + + +@test +def test_generator_for_loop(t): + """for-loop consumes generator, interleaving body and loop.""" + def gen(): + yield 1 @ t[2] + yield 2 @ t[4] + + for val in (gen @ t[0])() @ t[1]: + val @ t[3, 5] + + +@test +def test_generator_list(t): + """list() consumes the entire generator without interleaving.""" + def gen(): + yield 10 @ t[3] + yield 20 @ t[4] + yield 30 @ t[5] + + result = (list @ t[0])((gen @ t[1])() @ t[2]) @ t[6] + + +@test +def test_yield_from(t): + """yield from delegates to an inner generator transparently.""" + def inner(): + yield 1 @ t[6] + yield 2 @ t[10] + + def outer(): + yield from (inner @ t[4])() @ t[5] + + g = (outer @ t[0])() @ t[1] + x = (next @ t[2])(g @ t[3]) @ t[7] + y = (next @ t[8])(g @ t[9]) @ t[11] + + +@test +def test_generator_return(t): + """Generator return value accessed via yield from.""" + def gen(): + yield 1 @ t[6] + return 42 @ t[10] + + def wrapper(): + result = (yield from (gen @ t[4])() @ t[5]) @ t[11] + yield result @ t[12] + + g = (wrapper @ t[0])() @ t[1] + x = (next @ t[2])(g @ t[3]) @ t[7] + y = (next @ t[8])(g @ t[9]) @ t[13] + + +@test +def test_generator_send(t): + """send() passes a value into the generator at the yield point.""" + def gen(): + x = (yield 1 @ t[4]) @ t[9] + yield (x @ t[10] + 10 @ t[11]) @ t[12] + + g = (gen @ t[0])() @ t[1] + first = (next @ t[2])(g @ t[3]) @ t[5] + second = ((g @ t[6]).send @ t[7])(42 @ t[8]) @ t[13] + + +@test +def test_generator_expression(t): + """Inline generator expression consumed by list().""" + result = (list @ t[0])(x @ t[5, 6, 7] for x in [10 @ t[1], 20 @ t[2], 30 @ t[3]] @ t[4]) @ t[8] diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/timer.py b/python/ql/test/library-tests/ControlFlow/evaluation-order/timer.py new file mode 100644 index 000000000000..e10dde2592af --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/timer.py @@ -0,0 +1,189 @@ +"""Abstract timer for self-validating CFG evaluation-order tests. + +Provides a Timer context manager and a @test decorator for writing tests +that verify the order in which Python evaluates expressions. + +Usage with @test decorator (preferred): + + from timer import test, dead, never + + @test + def test_sequential(t): + x = 1 @ t[0] + y = 2 @ t[1] + z = (x + y) @ t[2] + +Annotation forms: + t[n] - assert current timestamp is n, return marker + t[n, m, ...] - assert current timestamp is one of {n, m, ...} + t[dead(n)] - mark timestamp n as dead (fails if evaluated) + t[dead(n), m] - dead at n, live at m + t[never] - mark as never evaluated (fails if evaluated) + t["label"] - record current timestamp under label (development aid) + t(value, n) - equivalent to: value @ t[n] + +Run a test file directly to self-validate: python test_file.py +""" + +import atexit +import sys + +_results = [] + + +class _Check: + """Marker returned by t[n] — asserts the current timestamp. + + Receives the raw subscript elements: plain ints are live timestamps, + dead(n) markers are dead timestamps, and `never` means any evaluation + is an error. + """ + + __slots__ = ("_timer", "_live", "_dead", "_never") + + def __init__(self, timer, elements): + self._timer = timer + self._live = set() + self._dead = set() + self._never = False + for e in elements: + if isinstance(e, int): + self._live.add(e) + elif isinstance(e, _DeadMarker): + self._dead.add(e.timestamp) + elif isinstance(e, _NeverSentinel): + self._never = True + + def __rmatmul__(self, value): + ts = self._timer._tick() + if self._never: + self._timer._error( + f"expression annotated with t[never] was evaluated (timestamp {ts})" + ) + elif ts in self._dead: + self._timer._error( + f"timestamp {ts} is marked dead but was evaluated" + ) + elif ts not in self._live: + self._timer._error( + f"expected {sorted(self._live)}, got {ts}" + ) + return value + + +class _Label: + """Marker returned by t["name"] — records the timestamp under a label.""" + + __slots__ = ("_timer", "_name") + + def __init__(self, timer, name): + self._timer = timer + self._name = name + + def __rmatmul__(self, value): + ts = self._timer._tick() + self._timer._labels.setdefault(self._name, []).append(ts) + return value + + +class _DeadMarker: + """Marker returned by dead(n) — used inside t[...] to mark a timestamp as dead.""" + + def __init__(self, timestamp): + self.timestamp = timestamp + + +def dead(n): + """Mark timestamp `n` as dead code inside a timer subscript: t[dead(1), 2].""" + return _DeadMarker(n) + + +class _NeverSentinel: + """Sentinel for never-evaluated annotations: t[never].""" + pass + + +never = _NeverSentinel() + + +class Timer: + """Context manager tracking abstract evaluation timestamps. + + Each Timer instance maintains a counter starting at 0. Every time an + annotation (@ t[n] or t(value, n)) is encountered, the counter is + compared against the expected value and then incremented. + """ + + def __init__(self, name=""): + self._name = name + self._counter = 0 + self._errors = [] + self._labels = {} + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if self._labels: + for name, timestamps in sorted(self._labels.items()): + print(f" {name}: {', '.join(map(str, timestamps))}") + _results.append((self._name, list(self._errors))) + if self._errors: + print(f"{self._name}: FAIL") + for err in self._errors: + print(f" {err}") + else: + print(f"{self._name}: ok") + return False + + def _tick(self): + ts = self._counter + self._counter += 1 + return ts + + def _error(self, msg): + self._errors.append(msg) + + def __getitem__(self, key): + if isinstance(key, str): + return _Label(self, key) + elif isinstance(key, tuple): + return _Check(self, key) + else: + return _Check(self, [key]) + + def __call__(self, value, key): + """Alternative to @ operator: t(value, 4) or t(value, [1, 2, 3]).""" + if isinstance(key, list): + key = tuple(key) + marker = self[key] + return marker.__rmatmul__(value) + + +def test(fn): + """Decorator that creates a Timer and runs the test function immediately. + + The function receives a fresh Timer as its sole argument. Errors are + collected (not raised) and reported after the function completes. + """ + with Timer(fn.__name__) as t: + try: + fn(t) + except Exception as e: + t._error(f"exception: {type(e).__name__}: {e}") + return fn + + +def _report(): + """Print summary at interpreter exit.""" + if not _results: + return + total = len(_results) + passed = sum(1 for _, errors in _results if not errors) + print("---") + print(f"{passed}/{total} tests passed") + if passed < total: + sys.exit(1) + + +atexit.register(_report) diff --git a/python/ql/test/library-tests/ControlFlow/store-load/StoreLoadTest.expected b/python/ql/test/library-tests/ControlFlow/store-load/StoreLoadTest.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/ControlFlow/store-load/StoreLoadTest.ql b/python/ql/test/library-tests/ControlFlow/store-load/StoreLoadTest.ql new file mode 100644 index 000000000000..7d83ea98a0eb --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/store-load/StoreLoadTest.ql @@ -0,0 +1,45 @@ +/** + * Inline-expectations test for the store/load/delete/parameter + * classification predicates on the new-CFG facade. + * + * Each tag fires when the corresponding predicate (`isLoad`, + * `isStore`, `isDelete`, `isParameter`, `isAugLoad`, `isAugStore`) + * holds on the canonical CFG node wrapping a `Py::Name` with the + * given identifier. + * + * For subscript / attribute stores the tag fires on the Subscript / + * Attribute node itself, with `value` set to the rightmost identifier + * (the attribute name for `Attribute`, the index expression's textual + * form for `Subscript`). + */ + +import python +import semmle.python.controlflow.internal.Cfg as Cfg +import utils.test.InlineExpectationsTest + +module StoreLoadTest implements TestSig { + string getARelevantTag() { result = ["load", "store", "delete", "param", "augload", "augstore"] } + + predicate hasActualResult(Location location, string element, string tag, string value) { + exists(Cfg::NameNode n | + location = n.getLocation() and + element = n.toString() and + value = n.getId() and + ( + n.isLoad() and not n.isAugLoad() and tag = "load" + or + n.isStore() and not n.isAugStore() and tag = "store" + or + n.isDelete() and tag = "delete" + or + n.isParameter() and tag = "param" + or + n.isAugLoad() and tag = "augload" + or + n.isAugStore() and tag = "augstore" + ) + ) + } +} + +import MakeTest diff --git a/python/ql/test/library-tests/ControlFlow/store-load/test.py b/python/ql/test/library-tests/ControlFlow/store-load/test.py new file mode 100644 index 000000000000..dfca45a0b47b --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/store-load/test.py @@ -0,0 +1,56 @@ +# Store/load/delete/parameter classification on the new-CFG facade. +# +# Each annotated location carries the (sorted, deduplicated) set of +# kinds the CFG facade reports there. Comparing against the legacy +# 'semmle.python.Flow' classification is done by the comparison query +# 'StoreLoadParity.ql' — annotations here are only the positive +# assertions for the new facade. +# +# Tags: +# load= -- isLoad() fires on the Name +# store= -- isStore() fires +# delete= -- isDelete() fires +# param= -- isParameter() fires +# augload= -- isAugLoad() fires (the LHS of x += ... when read) +# augstore= -- isAugStore() fires (the LHS of x += ... when written) + + +# --- plain load / store / delete --- + +x = 1 # $ store=x +y = x + 1 # $ store=y load=x +print(y) # $ load=print load=y +del x # $ delete=x + + +# --- function definitions (parameters) --- + +def f(a, b=2, *args, c, **kwargs): # $ store=f param=a param=b param=args param=c param=kwargs + return a + b + c # $ load=a load=b load=c + + +# --- augmented assignment splits one Name into load + store halves --- + +def aug(): # $ store=aug + n = 0 # $ store=n + n += 1 # $ augload=n augstore=n + return n # $ load=n + + +# --- subscript / attribute stores --- + +class C: # $ store=C + pass + + +def stores(obj, container, idx): # $ store=stores param=obj param=container param=idx + obj.attr = 1 # $ load=obj + container[idx] = 2 # $ load=container load=idx + return obj # $ load=obj + + +# --- tuple unpacking --- + +def unpack(pair): # $ store=unpack param=pair + a, b = pair # $ store=a store=b load=pair + return a + b # $ load=a load=b diff --git a/python/ql/test/library-tests/PointsTo/global/Global.ql b/python/ql/test/library-tests/PointsTo/global/Global.ql index 4dc6d16d3797..9887b79fbfc7 100644 --- a/python/ql/test/library-tests/PointsTo/global/Global.ql +++ b/python/ql/test/library-tests/PointsTo/global/Global.ql @@ -3,6 +3,6 @@ private import LegacyPointsTo from ControlFlowNode f, PointsToContext ctx, Value obj, ControlFlowNode orig where - exists(ExprStmt s | s.getValue().getAFlowNode() = f) and + exists(ExprStmt s | f.getNode() = s.getValue()) and PointsTo::pointsTo(f, ctx, obj, orig) select ctx, f, obj.toString(), orig diff --git a/python/ql/test/library-tests/PointsTo/local/LocalPointsTo.ql b/python/ql/test/library-tests/PointsTo/local/LocalPointsTo.ql index c81bd0ed3deb..ecf67aa7b33e 100644 --- a/python/ql/test/library-tests/PointsTo/local/LocalPointsTo.ql +++ b/python/ql/test/library-tests/PointsTo/local/LocalPointsTo.ql @@ -4,6 +4,6 @@ import semmle.python.objects.ObjectInternal from ControlFlowNode f, ObjectInternal obj, ControlFlowNode orig where - exists(ExprStmt s | s.getValue().getAFlowNode() = f) and + exists(ExprStmt s | f.getNode() = s.getValue()) and PointsTo::pointsTo(f, _, obj, orig) select f, obj.toString(), orig diff --git a/python/ql/test/library-tests/PointsTo/new/ImpliesDataflow.expected b/python/ql/test/library-tests/PointsTo/new/ImpliesDataflow.expected index 96663031d9a0..e6fc40a7151d 100644 --- a/python/ql/test/library-tests/PointsTo/new/ImpliesDataflow.expected +++ b/python/ql/test/library-tests/PointsTo/new/ImpliesDataflow.expected @@ -1,7 +1,7 @@ -| code/h_classes.py:3:1:3:16 | ControlFlowNode for ClassExpr | code/h_classes.py:10:1:10:9 | ControlFlowNode for type() | -| code/h_classes.py:3:1:3:16 | ControlFlowNode for ClassExpr | code/h_classes.py:15:5:15:13 | ControlFlowNode for type() | -| code/l_calls.py:12:1:12:20 | ControlFlowNode for ClassExpr | code/l_calls.py:16:16:16:18 | ControlFlowNode for cls | -| code/l_calls.py:12:1:12:20 | ControlFlowNode for ClassExpr | code/l_calls.py:24:13:24:22 | ControlFlowNode for Attribute() | -| code/l_calls.py:12:1:12:20 | ControlFlowNode for ClassExpr | code/l_calls.py:25:16:25:16 | ControlFlowNode for a | -| code/t_type.py:3:1:3:16 | ControlFlowNode for ClassExpr | code/t_type.py:6:1:6:9 | ControlFlowNode for type() | -| code/t_type.py:3:1:3:16 | ControlFlowNode for ClassExpr | code/t_type.py:13:5:13:13 | ControlFlowNode for type() | +| code/h_classes.py:3:1:3:16 | After ClassExpr | code/h_classes.py:10:1:10:9 | After type() | +| code/h_classes.py:3:1:3:16 | After ClassExpr | code/h_classes.py:15:5:15:13 | After type() | +| code/l_calls.py:12:1:12:20 | After ClassExpr | code/l_calls.py:16:16:16:18 | cls | +| code/l_calls.py:12:1:12:20 | After ClassExpr | code/l_calls.py:24:13:24:22 | After Attribute() | +| code/l_calls.py:12:1:12:20 | After ClassExpr | code/l_calls.py:25:16:25:16 | a | +| code/t_type.py:3:1:3:16 | After ClassExpr | code/t_type.py:6:1:6:9 | After type() | +| code/t_type.py:3:1:3:16 | After ClassExpr | code/t_type.py:13:5:13:13 | After type() | diff --git a/python/ql/test/library-tests/PointsTo/new/ImpliesDataflow.ql b/python/ql/test/library-tests/PointsTo/new/ImpliesDataflow.ql index da4b46595e67..ebe0071a5801 100644 --- a/python/ql/test/library-tests/PointsTo/new/ImpliesDataflow.ql +++ b/python/ql/test/library-tests/PointsTo/new/ImpliesDataflow.ql @@ -8,7 +8,11 @@ private import LegacyPointsTo import semmle.python.dataflow.new.DataFlow predicate pointsToOrigin(DataFlow::CfgNode pointer, DataFlow::CfgNode origin) { - origin.getNode() = pointer.getNode().(ControlFlowNodeWithPointsTo).pointsTo().getOrigin() + exists(ControlFlowNodeWithPointsTo legacyPointer, ControlFlowNode legacyOrigin | + legacyPointer.getNode() = pointer.getNode().getNode() and + legacyOrigin = legacyPointer.pointsTo().getOrigin() and + legacyOrigin.getNode() = origin.getNode().getNode() + ) } module PointsToConfig implements DataFlow::ConfigSig { diff --git a/python/ql/test/library-tests/dataflow-new-ssa-vs-legacy/CmpTest.expected b/python/ql/test/library-tests/dataflow-new-ssa-vs-legacy/CmpTest.expected new file mode 100644 index 000000000000..7aaeefce2f88 --- /dev/null +++ b/python/ql/test/library-tests/dataflow-new-ssa-vs-legacy/CmpTest.expected @@ -0,0 +1,21 @@ +| def-only-new | compute:37:1 | +| def-only-new | open:44:1 | +| def-only-new | sum:27:1 | +| def-only-old | $:0:0 | +| def-only-old | GLOBAL:49:1 | +| def-only-old | __name__:0:0 | +| def-only-old | __package__:0:0 | +| def-only-old | closure:31:5 | +| def-only-old | e:37:1 | +| def-only-old | e:40:25 | +| def-only-old | exception_binding:37:5 | +| def-only-old | if_else_branch:12:5 | +| def-only-old | kwargs:27:32 | +| def-only-old | loop:20:5 | +| def-only-old | parameter:27:5 | +| def-only-old | read_global:52:5 | +| def-only-old | reassignment:6:5 | +| def-only-old | simple_assign:1:5 | +| def-only-old | with_binding:44:5 | +| def-only-old | x:20:1 | +| def-only-old | x:31:13 | diff --git a/python/ql/test/library-tests/dataflow-new-ssa-vs-legacy/CmpTest.ql b/python/ql/test/library-tests/dataflow-new-ssa-vs-legacy/CmpTest.ql new file mode 100644 index 000000000000..590f5ebed47a --- /dev/null +++ b/python/ql/test/library-tests/dataflow-new-ssa-vs-legacy/CmpTest.ql @@ -0,0 +1,59 @@ +/** + * Compares the new-CFG SSA against the legacy ESSA on the same Python + * sources. Reports definitions present in one implementation but not + * the other, identified by variable name + source position. + * + * The `.expected` file records the current diff as a snapshot: as the + * new SSA matures (closing captured-variable gap, exception bindings, + * etc.) and tracks more variables, the snapshot should monotonically + * shrink. + * + * Known categories of `def-only-old` mismatches: + * - Function / class / global definitions with no in-scope read + * (intentional: SSA is liveness-pruned, write-only variables are + * not tracked). + * - Captured / closure variables (gap: new SSA does not yet model + * closure captures). + * - Module variables `__name__`, `__package__`, `$` (legacy ESSA + * adds implicit bindings the new SSA does not). + * - Exception-handler `as` bindings (depend on raise modelling). + * + * `def-only-new` mismatches would indicate the new SSA produces spurious + * definitions; currently none are expected. + */ + +import python +import semmle.python.dataflow.new.internal.SsaImpl as NewSsa +import semmle.python.controlflow.internal.Cfg as Cfg +import semmle.python.essa.Essa + +string newDefSig(NewSsa::EssaNodeDefinition def) { + exists(Cfg::ControlFlowNode n | n = def.getDefiningNode() | + result = + def.getVariable().getVariable().getId() + ":" + n.getLocation().getStartLine() + ":" + + n.getLocation().getStartColumn() + ) +} + +string legacyDefSig(EssaNodeDefinition def) { + exists(ControlFlowNode n | n = def.getDefiningNode() | + result = + def.getSourceVariable().getName() + ":" + n.getLocation().getStartLine() + ":" + + n.getLocation().getStartColumn() + ) +} + +from string kind, string sig +where + kind = "def-only-new" and + exists(NewSsa::EssaNodeDefinition def | + sig = newDefSig(def) and + not exists(EssaNodeDefinition legacyDef | sig = legacyDefSig(legacyDef)) + ) + or + kind = "def-only-old" and + exists(EssaNodeDefinition legacyDef | + sig = legacyDefSig(legacyDef) and + not exists(NewSsa::EssaNodeDefinition def | sig = newDefSig(def)) + ) +select kind, sig diff --git a/python/ql/test/library-tests/dataflow-new-ssa-vs-legacy/test.py b/python/ql/test/library-tests/dataflow-new-ssa-vs-legacy/test.py new file mode 100644 index 000000000000..8b061109bf2c --- /dev/null +++ b/python/ql/test/library-tests/dataflow-new-ssa-vs-legacy/test.py @@ -0,0 +1,53 @@ +def simple_assign(): + x = 1 + return x + + +def reassignment(): + x = 1 + x = 2 + return x + + +def if_else_branch(cond): + if cond: + x = 1 + else: + x = 2 + return x + + +def loop(xs): + total = 0 + for x in xs: + total = total + x + return total + + +def parameter(a, b=2, *args, **kwargs): + return a + b + sum(args) + + +def closure(x): + def inner(): + return x + return inner + + +def exception_binding(): + try: + compute() + except Exception as e: + return e + + +def with_binding(): + with open("file") as f: + return f.read() + + +GLOBAL = 1 + + +def read_global(): + return GLOBAL diff --git a/python/ql/test/library-tests/dataflow-new-ssa/SsaTest.expected b/python/ql/test/library-tests/dataflow-new-ssa/SsaTest.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/dataflow-new-ssa/SsaTest.ql b/python/ql/test/library-tests/dataflow-new-ssa/SsaTest.ql new file mode 100644 index 000000000000..0bebf4a637d0 --- /dev/null +++ b/python/ql/test/library-tests/dataflow-new-ssa/SsaTest.ql @@ -0,0 +1,59 @@ +/** + * Inline-expectations test for the new-CFG SSA adapter + * (`semmle.python.dataflow.new.internal.SsaImpl`). + * + * Tags: + * - `def=`: there is an SSA write definition of `` at this + * line (parameter init, plain assignment, augmented assignment, + * exception-handler binding, deletion, etc.). + * - `use=`: `` is used at this line, and some SSA definition + * of `` reaches the read. + * - `phi=`: there is an SSA phi definition of `` whose BB + * starts on this line. + */ + +import python +import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl +import semmle.python.controlflow.internal.AstNodeImpl as CfgImpl +import semmle.python.controlflow.internal.Cfg as Cfg +import utils.test.InlineExpectationsTest + +module SsaTest implements TestSig { + string getARelevantTag() { result = ["def", "use", "phi"] } + + predicate hasActualResult(Location location, string element, string tag, string value) { + // A `def=` fires when an SSA WriteDefinition is at a CFG node + // on the given line. + exists(SsaImpl::Ssa::WriteDefinition def, CfgImpl::BasicBlock bb, int i, Cfg::NameNode n | + def.definesAt(_, bb, i) and + bb.getNode(i) = n and + tag = "def" and + location = n.getLocation() and + element = n.toString() and + value = n.getId() + ) + or + // A `use=` fires when an SSA Definition reaches a read at this + // CFG node. + exists(SsaImpl::Ssa::Definition def, CfgImpl::BasicBlock bb, int i, Cfg::NameNode n | + SsaImpl::Ssa::ssaDefReachesRead(_, def, bb, i) and + bb.getNode(i) = n and + tag = "use" and + location = n.getLocation() and + element = n.toString() and + value = n.getId() + ) + or + // A `phi=` fires when there is a phi node whose BB's first + // CFG node is on the given line. + exists(SsaImpl::Ssa::PhiNode phi, CfgImpl::BasicBlock bb | + phi.definesAt(_, bb, _) and + tag = "phi" and + location = bb.getNode(0).getLocation() and + element = bb.toString() and + value = phi.getSourceVariable().(SsaImpl::SsaSourceVariable).getVariable().getId() + ) + } +} + +import MakeTest diff --git a/python/ql/test/library-tests/dataflow-new-ssa/test.py b/python/ql/test/library-tests/dataflow-new-ssa/test.py new file mode 100644 index 000000000000..c6cdc22c3b36 --- /dev/null +++ b/python/ql/test/library-tests/dataflow-new-ssa/test.py @@ -0,0 +1,40 @@ +# Basic SSA tests for the new-CFG SSA adapter. +# +# The shared SSA implementation prunes its construction by liveness: +# definitions of variables that are not read are never materialised. +# This is by design — write-only variables would only bloat the SSA +# graph. Tests therefore must always include a read of each variable +# being verified. +# +# Annotations: +# def=: there is an SSA write definition of at this line +# use=: is used here and the read resolves to some def + + +def basic_param(x): # $ def=x + return x # $ use=x + + +def basic_assign(): + y = 1 # $ def=y + return y # $ use=y + + +def reassignment(): + x = 1 + x = 2 # $ def=x + return x # $ use=x + + +def if_else_phi(cond): # $ def=cond + if cond: # $ use=cond phi=x + x = 1 # $ def=x + else: + x = 2 # $ def=x + return x # $ use=x + + +def use_global(): + return some_undefined # $ use=some_undefined + + diff --git a/python/ql/test/library-tests/dataflow/basic/callGraph.expected b/python/ql/test/library-tests/dataflow/basic/callGraph.expected index 222e11cb54f6..2ce5c66bc4a2 100644 --- a/python/ql/test/library-tests/dataflow/basic/callGraph.expected +++ b/python/ql/test/library-tests/dataflow/basic/callGraph.expected @@ -1,3 +1,3 @@ -| test.py:4:10:4:10 | ControlFlowNode for z | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | -| test.py:7:19:7:19 | ControlFlowNode for a | test.py:1:19:1:19 | ControlFlowNode for x | -| test.py:7:19:7:19 | ControlFlowNode for a | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | +| test.py:4:10:4:10 | z | test.py:7:5:7:20 | After obfuscated_id() | +| test.py:7:19:7:19 | a | test.py:1:19:1:19 | x | +| test.py:7:19:7:19 | a | test.py:7:5:7:20 | After obfuscated_id() | diff --git a/python/ql/test/library-tests/dataflow/basic/callGraphSinks.expected b/python/ql/test/library-tests/dataflow/basic/callGraphSinks.expected index e4b8f905530b..046e18186c03 100644 --- a/python/ql/test/library-tests/dataflow/basic/callGraphSinks.expected +++ b/python/ql/test/library-tests/dataflow/basic/callGraphSinks.expected @@ -1,3 +1,3 @@ | test.py:1:1:1:21 | SynthDictSplatParameterNode | -| test.py:1:19:1:19 | ControlFlowNode for x | -| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | +| test.py:1:19:1:19 | x | +| test.py:7:5:7:20 | After obfuscated_id() | diff --git a/python/ql/test/library-tests/dataflow/basic/callGraphSources.expected b/python/ql/test/library-tests/dataflow/basic/callGraphSources.expected index 4023ba8f3ea1..a8fea599f65c 100644 --- a/python/ql/test/library-tests/dataflow/basic/callGraphSources.expected +++ b/python/ql/test/library-tests/dataflow/basic/callGraphSources.expected @@ -1,2 +1,2 @@ -| test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:7:19:7:19 | ControlFlowNode for a | +| test.py:4:10:4:10 | z | +| test.py:7:19:7:19 | a | diff --git a/python/ql/test/library-tests/dataflow/basic/global.expected b/python/ql/test/library-tests/dataflow/basic/global.expected index 9e0ef2e6751b..c5794bfca970 100644 --- a/python/ql/test/library-tests/dataflow/basic/global.expected +++ b/python/ql/test/library-tests/dataflow/basic/global.expected @@ -1,73 +1,73 @@ -| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:0:0:0:0 | ModuleVariableNode in Module test for obfuscated_id | -| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:1:5:1:17 | ControlFlowNode for obfuscated_id | -| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:7:5:7:17 | ControlFlowNode for obfuscated_id | -| test.py:1:5:1:17 | ControlFlowNode for obfuscated_id | test.py:0:0:0:0 | ModuleVariableNode in Module test for obfuscated_id | -| test.py:1:5:1:17 | ControlFlowNode for obfuscated_id | test.py:7:5:7:17 | ControlFlowNode for obfuscated_id | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:2:3:2:3 | ControlFlowNode for y | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:2:7:2:7 | ControlFlowNode for x | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:3:7:3:7 | ControlFlowNode for y | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:7:1:7:1 | ControlFlowNode for b | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | -| test.py:2:3:2:3 | ControlFlowNode for y | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | -| test.py:2:3:2:3 | ControlFlowNode for y | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:2:3:2:3 | ControlFlowNode for y | test.py:3:7:3:7 | ControlFlowNode for y | -| test.py:2:3:2:3 | ControlFlowNode for y | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:2:3:2:3 | ControlFlowNode for y | test.py:7:1:7:1 | ControlFlowNode for b | -| test.py:2:3:2:3 | ControlFlowNode for y | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | -| test.py:2:7:2:7 | ControlFlowNode for x | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | -| test.py:2:7:2:7 | ControlFlowNode for x | test.py:2:3:2:3 | ControlFlowNode for y | -| test.py:2:7:2:7 | ControlFlowNode for x | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:2:7:2:7 | ControlFlowNode for x | test.py:3:7:3:7 | ControlFlowNode for y | -| test.py:2:7:2:7 | ControlFlowNode for x | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:2:7:2:7 | ControlFlowNode for x | test.py:7:1:7:1 | ControlFlowNode for b | -| test.py:2:7:2:7 | ControlFlowNode for x | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | -| test.py:3:3:3:3 | ControlFlowNode for z | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | -| test.py:3:3:3:3 | ControlFlowNode for z | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:3:3:3:3 | ControlFlowNode for z | test.py:7:1:7:1 | ControlFlowNode for b | -| test.py:3:3:3:3 | ControlFlowNode for z | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | -| test.py:3:7:3:7 | ControlFlowNode for y | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | -| test.py:3:7:3:7 | ControlFlowNode for y | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:3:7:3:7 | ControlFlowNode for y | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:3:7:3:7 | ControlFlowNode for y | test.py:7:1:7:1 | ControlFlowNode for b | -| test.py:3:7:3:7 | ControlFlowNode for y | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | -| test.py:4:10:4:10 | ControlFlowNode for z | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | -| test.py:4:10:4:10 | ControlFlowNode for z | test.py:7:1:7:1 | ControlFlowNode for b | -| test.py:4:10:4:10 | ControlFlowNode for z | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | -| test.py:6:1:6:1 | ControlFlowNode for a | test.py:0:0:0:0 | ModuleVariableNode in Module test for a | -| test.py:6:1:6:1 | ControlFlowNode for a | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | -| test.py:6:1:6:1 | ControlFlowNode for a | test.py:1:19:1:19 | ControlFlowNode for x | -| test.py:6:1:6:1 | ControlFlowNode for a | test.py:2:3:2:3 | ControlFlowNode for y | -| test.py:6:1:6:1 | ControlFlowNode for a | test.py:2:7:2:7 | ControlFlowNode for x | -| test.py:6:1:6:1 | ControlFlowNode for a | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:6:1:6:1 | ControlFlowNode for a | test.py:3:7:3:7 | ControlFlowNode for y | -| test.py:6:1:6:1 | ControlFlowNode for a | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:6:1:6:1 | ControlFlowNode for a | test.py:7:1:7:1 | ControlFlowNode for b | -| test.py:6:1:6:1 | ControlFlowNode for a | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | -| test.py:6:1:6:1 | ControlFlowNode for a | test.py:7:19:7:19 | ControlFlowNode for a | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:0:0:0:0 | ModuleVariableNode in Module test for a | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:1:19:1:19 | ControlFlowNode for x | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:2:3:2:3 | ControlFlowNode for y | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:2:7:2:7 | ControlFlowNode for x | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:3:7:3:7 | ControlFlowNode for y | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:6:1:6:1 | ControlFlowNode for a | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:7:1:7:1 | ControlFlowNode for b | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:7:19:7:19 | ControlFlowNode for a | -| test.py:7:1:7:1 | ControlFlowNode for b | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | -| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | -| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | test.py:7:1:7:1 | ControlFlowNode for b | -| test.py:7:19:7:19 | ControlFlowNode for a | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | -| test.py:7:19:7:19 | ControlFlowNode for a | test.py:1:19:1:19 | ControlFlowNode for x | -| test.py:7:19:7:19 | ControlFlowNode for a | test.py:2:3:2:3 | ControlFlowNode for y | -| test.py:7:19:7:19 | ControlFlowNode for a | test.py:2:7:2:7 | ControlFlowNode for x | -| test.py:7:19:7:19 | ControlFlowNode for a | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:7:19:7:19 | ControlFlowNode for a | test.py:3:7:3:7 | ControlFlowNode for y | -| test.py:7:19:7:19 | ControlFlowNode for a | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:7:19:7:19 | ControlFlowNode for a | test.py:7:1:7:1 | ControlFlowNode for b | -| test.py:7:19:7:19 | ControlFlowNode for a | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | +| test.py:1:1:1:21 | FunctionExpr | test.py:0:0:0:0 | ModuleVariableNode in Module test for obfuscated_id | +| test.py:1:1:1:21 | FunctionExpr | test.py:1:5:1:17 | obfuscated_id | +| test.py:1:1:1:21 | FunctionExpr | test.py:7:5:7:17 | obfuscated_id | +| test.py:1:5:1:17 | obfuscated_id | test.py:0:0:0:0 | ModuleVariableNode in Module test for obfuscated_id | +| test.py:1:5:1:17 | obfuscated_id | test.py:7:5:7:17 | obfuscated_id | +| test.py:1:19:1:19 | x | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | +| test.py:1:19:1:19 | x | test.py:2:3:2:3 | y | +| test.py:1:19:1:19 | x | test.py:2:7:2:7 | x | +| test.py:1:19:1:19 | x | test.py:3:3:3:3 | z | +| test.py:1:19:1:19 | x | test.py:3:7:3:7 | y | +| test.py:1:19:1:19 | x | test.py:4:10:4:10 | z | +| test.py:1:19:1:19 | x | test.py:7:1:7:1 | b | +| test.py:1:19:1:19 | x | test.py:7:5:7:20 | After obfuscated_id() | +| test.py:2:3:2:3 | y | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | +| test.py:2:3:2:3 | y | test.py:3:3:3:3 | z | +| test.py:2:3:2:3 | y | test.py:3:7:3:7 | y | +| test.py:2:3:2:3 | y | test.py:4:10:4:10 | z | +| test.py:2:3:2:3 | y | test.py:7:1:7:1 | b | +| test.py:2:3:2:3 | y | test.py:7:5:7:20 | After obfuscated_id() | +| test.py:2:7:2:7 | x | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | +| test.py:2:7:2:7 | x | test.py:2:3:2:3 | y | +| test.py:2:7:2:7 | x | test.py:3:3:3:3 | z | +| test.py:2:7:2:7 | x | test.py:3:7:3:7 | y | +| test.py:2:7:2:7 | x | test.py:4:10:4:10 | z | +| test.py:2:7:2:7 | x | test.py:7:1:7:1 | b | +| test.py:2:7:2:7 | x | test.py:7:5:7:20 | After obfuscated_id() | +| test.py:3:3:3:3 | z | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | +| test.py:3:3:3:3 | z | test.py:4:10:4:10 | z | +| test.py:3:3:3:3 | z | test.py:7:1:7:1 | b | +| test.py:3:3:3:3 | z | test.py:7:5:7:20 | After obfuscated_id() | +| test.py:3:7:3:7 | y | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | +| test.py:3:7:3:7 | y | test.py:3:3:3:3 | z | +| test.py:3:7:3:7 | y | test.py:4:10:4:10 | z | +| test.py:3:7:3:7 | y | test.py:7:1:7:1 | b | +| test.py:3:7:3:7 | y | test.py:7:5:7:20 | After obfuscated_id() | +| test.py:4:10:4:10 | z | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | +| test.py:4:10:4:10 | z | test.py:7:1:7:1 | b | +| test.py:4:10:4:10 | z | test.py:7:5:7:20 | After obfuscated_id() | +| test.py:6:1:6:1 | a | test.py:0:0:0:0 | ModuleVariableNode in Module test for a | +| test.py:6:1:6:1 | a | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | +| test.py:6:1:6:1 | a | test.py:1:19:1:19 | x | +| test.py:6:1:6:1 | a | test.py:2:3:2:3 | y | +| test.py:6:1:6:1 | a | test.py:2:7:2:7 | x | +| test.py:6:1:6:1 | a | test.py:3:3:3:3 | z | +| test.py:6:1:6:1 | a | test.py:3:7:3:7 | y | +| test.py:6:1:6:1 | a | test.py:4:10:4:10 | z | +| test.py:6:1:6:1 | a | test.py:7:1:7:1 | b | +| test.py:6:1:6:1 | a | test.py:7:5:7:20 | After obfuscated_id() | +| test.py:6:1:6:1 | a | test.py:7:19:7:19 | a | +| test.py:6:5:6:6 | IntegerLiteral | test.py:0:0:0:0 | ModuleVariableNode in Module test for a | +| test.py:6:5:6:6 | IntegerLiteral | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | +| test.py:6:5:6:6 | IntegerLiteral | test.py:1:19:1:19 | x | +| test.py:6:5:6:6 | IntegerLiteral | test.py:2:3:2:3 | y | +| test.py:6:5:6:6 | IntegerLiteral | test.py:2:7:2:7 | x | +| test.py:6:5:6:6 | IntegerLiteral | test.py:3:3:3:3 | z | +| test.py:6:5:6:6 | IntegerLiteral | test.py:3:7:3:7 | y | +| test.py:6:5:6:6 | IntegerLiteral | test.py:4:10:4:10 | z | +| test.py:6:5:6:6 | IntegerLiteral | test.py:6:1:6:1 | a | +| test.py:6:5:6:6 | IntegerLiteral | test.py:7:1:7:1 | b | +| test.py:6:5:6:6 | IntegerLiteral | test.py:7:5:7:20 | After obfuscated_id() | +| test.py:6:5:6:6 | IntegerLiteral | test.py:7:19:7:19 | a | +| test.py:7:1:7:1 | b | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | +| test.py:7:5:7:20 | After obfuscated_id() | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | +| test.py:7:5:7:20 | After obfuscated_id() | test.py:7:1:7:1 | b | +| test.py:7:19:7:19 | a | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | +| test.py:7:19:7:19 | a | test.py:1:19:1:19 | x | +| test.py:7:19:7:19 | a | test.py:2:3:2:3 | y | +| test.py:7:19:7:19 | a | test.py:2:7:2:7 | x | +| test.py:7:19:7:19 | a | test.py:3:3:3:3 | z | +| test.py:7:19:7:19 | a | test.py:3:7:3:7 | y | +| test.py:7:19:7:19 | a | test.py:4:10:4:10 | z | +| test.py:7:19:7:19 | a | test.py:7:1:7:1 | b | +| test.py:7:19:7:19 | a | test.py:7:5:7:20 | After obfuscated_id() | diff --git a/python/ql/test/library-tests/dataflow/basic/globalStep.expected b/python/ql/test/library-tests/dataflow/basic/globalStep.expected index 26d8902e7bbe..dd758848448f 100644 --- a/python/ql/test/library-tests/dataflow/basic/globalStep.expected +++ b/python/ql/test/library-tests/dataflow/basic/globalStep.expected @@ -1,46 +1,46 @@ -| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:1:5:1:17 | ControlFlowNode for obfuscated_id | -| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:1:5:1:17 | ControlFlowNode for obfuscated_id | -| test.py:1:5:1:17 | ControlFlowNode for obfuscated_id | test.py:0:0:0:0 | ModuleVariableNode in Module test for obfuscated_id | -| test.py:1:5:1:17 | ControlFlowNode for obfuscated_id | test.py:7:5:7:17 | ControlFlowNode for obfuscated_id | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:2:3:2:3 | ControlFlowNode for y | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:2:3:2:3 | ControlFlowNode for y | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:2:3:2:3 | ControlFlowNode for y | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:2:3:2:3 | ControlFlowNode for y | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:2:7:2:7 | ControlFlowNode for x | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:2:7:2:7 | ControlFlowNode for x | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:2:7:2:7 | ControlFlowNode for x | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:2:7:2:7 | ControlFlowNode for x | -| test.py:2:3:2:3 | ControlFlowNode for y | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:2:3:2:3 | ControlFlowNode for y | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:2:3:2:3 | ControlFlowNode for y | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:2:3:2:3 | ControlFlowNode for y | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:2:3:2:3 | ControlFlowNode for y | test.py:3:7:3:7 | ControlFlowNode for y | -| test.py:2:3:2:3 | ControlFlowNode for y | test.py:3:7:3:7 | ControlFlowNode for y | -| test.py:2:3:2:3 | ControlFlowNode for y | test.py:3:7:3:7 | ControlFlowNode for y | -| test.py:2:3:2:3 | ControlFlowNode for y | test.py:3:7:3:7 | ControlFlowNode for y | -| test.py:2:7:2:7 | ControlFlowNode for x | test.py:2:3:2:3 | ControlFlowNode for y | -| test.py:2:7:2:7 | ControlFlowNode for x | test.py:2:3:2:3 | ControlFlowNode for y | -| test.py:2:7:2:7 | ControlFlowNode for x | test.py:2:3:2:3 | ControlFlowNode for y | -| test.py:2:7:2:7 | ControlFlowNode for x | test.py:2:3:2:3 | ControlFlowNode for y | -| test.py:3:3:3:3 | ControlFlowNode for z | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:3:3:3:3 | ControlFlowNode for z | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:3:3:3:3 | ControlFlowNode for z | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:3:3:3:3 | ControlFlowNode for z | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:3:7:3:7 | ControlFlowNode for y | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:3:7:3:7 | ControlFlowNode for y | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:3:7:3:7 | ControlFlowNode for y | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:3:7:3:7 | ControlFlowNode for y | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:4:10:4:10 | ControlFlowNode for z | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | -| test.py:4:10:4:10 | ControlFlowNode for z | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | -| test.py:6:1:6:1 | ControlFlowNode for a | test.py:0:0:0:0 | ModuleVariableNode in Module test for a | -| test.py:6:1:6:1 | ControlFlowNode for a | test.py:7:19:7:19 | ControlFlowNode for a | -| test.py:6:1:6:1 | ControlFlowNode for a | test.py:7:19:7:19 | ControlFlowNode for a | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:6:1:6:1 | ControlFlowNode for a | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:6:1:6:1 | ControlFlowNode for a | -| test.py:7:1:7:1 | ControlFlowNode for b | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | -| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | test.py:7:1:7:1 | ControlFlowNode for b | -| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | test.py:7:1:7:1 | ControlFlowNode for b | -| test.py:7:19:7:19 | ControlFlowNode for a | test.py:1:19:1:19 | ControlFlowNode for x | -| test.py:7:19:7:19 | ControlFlowNode for a | test.py:1:19:1:19 | ControlFlowNode for x | -| test.py:7:19:7:19 | ControlFlowNode for a | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | -| test.py:7:19:7:19 | ControlFlowNode for a | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | +| test.py:1:1:1:21 | FunctionExpr | test.py:1:5:1:17 | obfuscated_id | +| test.py:1:1:1:21 | FunctionExpr | test.py:1:5:1:17 | obfuscated_id | +| test.py:1:5:1:17 | obfuscated_id | test.py:0:0:0:0 | ModuleVariableNode in Module test for obfuscated_id | +| test.py:1:5:1:17 | obfuscated_id | test.py:7:5:7:17 | obfuscated_id | +| test.py:1:19:1:19 | x | test.py:2:3:2:3 | y | +| test.py:1:19:1:19 | x | test.py:2:3:2:3 | y | +| test.py:1:19:1:19 | x | test.py:2:3:2:3 | y | +| test.py:1:19:1:19 | x | test.py:2:3:2:3 | y | +| test.py:1:19:1:19 | x | test.py:2:7:2:7 | x | +| test.py:1:19:1:19 | x | test.py:2:7:2:7 | x | +| test.py:1:19:1:19 | x | test.py:2:7:2:7 | x | +| test.py:1:19:1:19 | x | test.py:2:7:2:7 | x | +| test.py:2:3:2:3 | y | test.py:3:3:3:3 | z | +| test.py:2:3:2:3 | y | test.py:3:3:3:3 | z | +| test.py:2:3:2:3 | y | test.py:3:3:3:3 | z | +| test.py:2:3:2:3 | y | test.py:3:3:3:3 | z | +| test.py:2:3:2:3 | y | test.py:3:7:3:7 | y | +| test.py:2:3:2:3 | y | test.py:3:7:3:7 | y | +| test.py:2:3:2:3 | y | test.py:3:7:3:7 | y | +| test.py:2:3:2:3 | y | test.py:3:7:3:7 | y | +| test.py:2:7:2:7 | x | test.py:2:3:2:3 | y | +| test.py:2:7:2:7 | x | test.py:2:3:2:3 | y | +| test.py:2:7:2:7 | x | test.py:2:3:2:3 | y | +| test.py:2:7:2:7 | x | test.py:2:3:2:3 | y | +| test.py:3:3:3:3 | z | test.py:4:10:4:10 | z | +| test.py:3:3:3:3 | z | test.py:4:10:4:10 | z | +| test.py:3:3:3:3 | z | test.py:4:10:4:10 | z | +| test.py:3:3:3:3 | z | test.py:4:10:4:10 | z | +| test.py:3:7:3:7 | y | test.py:3:3:3:3 | z | +| test.py:3:7:3:7 | y | test.py:3:3:3:3 | z | +| test.py:3:7:3:7 | y | test.py:3:3:3:3 | z | +| test.py:3:7:3:7 | y | test.py:3:3:3:3 | z | +| test.py:4:10:4:10 | z | test.py:7:5:7:20 | After obfuscated_id() | +| test.py:4:10:4:10 | z | test.py:7:5:7:20 | After obfuscated_id() | +| test.py:6:1:6:1 | a | test.py:0:0:0:0 | ModuleVariableNode in Module test for a | +| test.py:6:1:6:1 | a | test.py:7:19:7:19 | a | +| test.py:6:1:6:1 | a | test.py:7:19:7:19 | a | +| test.py:6:5:6:6 | IntegerLiteral | test.py:6:1:6:1 | a | +| test.py:6:5:6:6 | IntegerLiteral | test.py:6:1:6:1 | a | +| test.py:7:1:7:1 | b | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | +| test.py:7:5:7:20 | After obfuscated_id() | test.py:7:1:7:1 | b | +| test.py:7:5:7:20 | After obfuscated_id() | test.py:7:1:7:1 | b | +| test.py:7:19:7:19 | a | test.py:1:19:1:19 | x | +| test.py:7:19:7:19 | a | test.py:1:19:1:19 | x | +| test.py:7:19:7:19 | a | test.py:7:5:7:20 | After obfuscated_id() | +| test.py:7:19:7:19 | a | test.py:7:5:7:20 | After obfuscated_id() | diff --git a/python/ql/test/library-tests/dataflow/basic/local.expected b/python/ql/test/library-tests/dataflow/basic/local.expected index 96d402325129..4ed0993e4e0c 100644 --- a/python/ql/test/library-tests/dataflow/basic/local.expected +++ b/python/ql/test/library-tests/dataflow/basic/local.expected @@ -3,45 +3,45 @@ | test.py:0:0:0:0 | ModuleVariableNode in Module test for a | test.py:0:0:0:0 | ModuleVariableNode in Module test for a | | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | | test.py:0:0:0:0 | ModuleVariableNode in Module test for obfuscated_id | test.py:0:0:0:0 | ModuleVariableNode in Module test for obfuscated_id | -| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | -| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:1:5:1:17 | ControlFlowNode for obfuscated_id | -| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:7:5:7:17 | ControlFlowNode for obfuscated_id | +| test.py:1:1:1:21 | FunctionExpr | test.py:1:1:1:21 | FunctionExpr | +| test.py:1:1:1:21 | FunctionExpr | test.py:1:5:1:17 | obfuscated_id | +| test.py:1:1:1:21 | FunctionExpr | test.py:7:5:7:17 | obfuscated_id | | test.py:1:1:1:21 | SynthDictSplatParameterNode | test.py:1:1:1:21 | SynthDictSplatParameterNode | -| test.py:1:5:1:17 | ControlFlowNode for obfuscated_id | test.py:1:5:1:17 | ControlFlowNode for obfuscated_id | -| test.py:1:5:1:17 | ControlFlowNode for obfuscated_id | test.py:7:5:7:17 | ControlFlowNode for obfuscated_id | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:1:19:1:19 | ControlFlowNode for x | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:2:3:2:3 | ControlFlowNode for y | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:2:7:2:7 | ControlFlowNode for x | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:3:7:3:7 | ControlFlowNode for y | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:2:3:2:3 | ControlFlowNode for y | test.py:2:3:2:3 | ControlFlowNode for y | -| test.py:2:3:2:3 | ControlFlowNode for y | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:2:3:2:3 | ControlFlowNode for y | test.py:3:7:3:7 | ControlFlowNode for y | -| test.py:2:3:2:3 | ControlFlowNode for y | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:2:7:2:7 | ControlFlowNode for x | test.py:2:3:2:3 | ControlFlowNode for y | -| test.py:2:7:2:7 | ControlFlowNode for x | test.py:2:7:2:7 | ControlFlowNode for x | -| test.py:2:7:2:7 | ControlFlowNode for x | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:2:7:2:7 | ControlFlowNode for x | test.py:3:7:3:7 | ControlFlowNode for y | -| test.py:2:7:2:7 | ControlFlowNode for x | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:3:3:3:3 | ControlFlowNode for z | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:3:3:3:3 | ControlFlowNode for z | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:3:7:3:7 | ControlFlowNode for y | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:3:7:3:7 | ControlFlowNode for y | test.py:3:7:3:7 | ControlFlowNode for y | -| test.py:3:7:3:7 | ControlFlowNode for y | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:4:10:4:10 | ControlFlowNode for z | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:6:1:6:1 | ControlFlowNode for a | test.py:6:1:6:1 | ControlFlowNode for a | -| test.py:6:1:6:1 | ControlFlowNode for a | test.py:7:19:7:19 | ControlFlowNode for a | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:6:1:6:1 | ControlFlowNode for a | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:7:19:7:19 | ControlFlowNode for a | -| test.py:7:1:7:1 | ControlFlowNode for b | test.py:7:1:7:1 | ControlFlowNode for b | +| test.py:1:5:1:17 | obfuscated_id | test.py:1:5:1:17 | obfuscated_id | +| test.py:1:5:1:17 | obfuscated_id | test.py:7:5:7:17 | obfuscated_id | +| test.py:1:19:1:19 | x | test.py:1:19:1:19 | x | +| test.py:1:19:1:19 | x | test.py:2:3:2:3 | y | +| test.py:1:19:1:19 | x | test.py:2:7:2:7 | x | +| test.py:1:19:1:19 | x | test.py:3:3:3:3 | z | +| test.py:1:19:1:19 | x | test.py:3:7:3:7 | y | +| test.py:1:19:1:19 | x | test.py:4:10:4:10 | z | +| test.py:2:3:2:3 | y | test.py:2:3:2:3 | y | +| test.py:2:3:2:3 | y | test.py:3:3:3:3 | z | +| test.py:2:3:2:3 | y | test.py:3:7:3:7 | y | +| test.py:2:3:2:3 | y | test.py:4:10:4:10 | z | +| test.py:2:7:2:7 | x | test.py:2:3:2:3 | y | +| test.py:2:7:2:7 | x | test.py:2:7:2:7 | x | +| test.py:2:7:2:7 | x | test.py:3:3:3:3 | z | +| test.py:2:7:2:7 | x | test.py:3:7:3:7 | y | +| test.py:2:7:2:7 | x | test.py:4:10:4:10 | z | +| test.py:3:3:3:3 | z | test.py:3:3:3:3 | z | +| test.py:3:3:3:3 | z | test.py:4:10:4:10 | z | +| test.py:3:7:3:7 | y | test.py:3:3:3:3 | z | +| test.py:3:7:3:7 | y | test.py:3:7:3:7 | y | +| test.py:3:7:3:7 | y | test.py:4:10:4:10 | z | +| test.py:4:10:4:10 | z | test.py:4:10:4:10 | z | +| test.py:6:1:6:1 | a | test.py:6:1:6:1 | a | +| test.py:6:1:6:1 | a | test.py:7:19:7:19 | a | +| test.py:6:5:6:6 | IntegerLiteral | test.py:6:1:6:1 | a | +| test.py:6:5:6:6 | IntegerLiteral | test.py:6:5:6:6 | IntegerLiteral | +| test.py:6:5:6:6 | IntegerLiteral | test.py:7:19:7:19 | a | +| test.py:7:1:7:1 | b | test.py:7:1:7:1 | b | | test.py:7:5:7:17 | Capturing closure argument | test.py:7:5:7:17 | Capturing closure argument | -| test.py:7:5:7:17 | ControlFlowNode for obfuscated_id | test.py:7:5:7:17 | ControlFlowNode for obfuscated_id | | test.py:7:5:7:17 | [post] Capturing closure argument | test.py:7:5:7:17 | [post] Capturing closure argument | -| test.py:7:5:7:17 | [post] ControlFlowNode for obfuscated_id | test.py:7:5:7:17 | [post] ControlFlowNode for obfuscated_id | -| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | test.py:7:1:7:1 | ControlFlowNode for b | -| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | -| test.py:7:5:7:20 | [pre] ControlFlowNode for obfuscated_id() | test.py:7:5:7:20 | [pre] ControlFlowNode for obfuscated_id() | -| test.py:7:19:7:19 | ControlFlowNode for a | test.py:7:19:7:19 | ControlFlowNode for a | -| test.py:7:19:7:19 | [post] ControlFlowNode for a | test.py:7:19:7:19 | [post] ControlFlowNode for a | +| test.py:7:5:7:17 | [post] obfuscated_id | test.py:7:5:7:17 | [post] obfuscated_id | +| test.py:7:5:7:17 | obfuscated_id | test.py:7:5:7:17 | obfuscated_id | +| test.py:7:5:7:20 | After obfuscated_id() | test.py:7:1:7:1 | b | +| test.py:7:5:7:20 | After obfuscated_id() | test.py:7:5:7:20 | After obfuscated_id() | +| test.py:7:5:7:20 | [pre] After obfuscated_id() | test.py:7:5:7:20 | [pre] After obfuscated_id() | +| test.py:7:19:7:19 | [post] a | test.py:7:19:7:19 | [post] a | +| test.py:7:19:7:19 | a | test.py:7:19:7:19 | a | diff --git a/python/ql/test/library-tests/dataflow/basic/localStep.expected b/python/ql/test/library-tests/dataflow/basic/localStep.expected index ce190945d363..6631b2123f2b 100644 --- a/python/ql/test/library-tests/dataflow/basic/localStep.expected +++ b/python/ql/test/library-tests/dataflow/basic/localStep.expected @@ -1,10 +1,10 @@ -| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:1:5:1:17 | ControlFlowNode for obfuscated_id | -| test.py:1:5:1:17 | ControlFlowNode for obfuscated_id | test.py:7:5:7:17 | ControlFlowNode for obfuscated_id | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:2:7:2:7 | ControlFlowNode for x | -| test.py:2:3:2:3 | ControlFlowNode for y | test.py:3:7:3:7 | ControlFlowNode for y | -| test.py:2:7:2:7 | ControlFlowNode for x | test.py:2:3:2:3 | ControlFlowNode for y | -| test.py:3:3:3:3 | ControlFlowNode for z | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:3:7:3:7 | ControlFlowNode for y | test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:6:1:6:1 | ControlFlowNode for a | test.py:7:19:7:19 | ControlFlowNode for a | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:6:1:6:1 | ControlFlowNode for a | -| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | test.py:7:1:7:1 | ControlFlowNode for b | +| test.py:1:1:1:21 | FunctionExpr | test.py:1:5:1:17 | obfuscated_id | +| test.py:1:5:1:17 | obfuscated_id | test.py:7:5:7:17 | obfuscated_id | +| test.py:1:19:1:19 | x | test.py:2:7:2:7 | x | +| test.py:2:3:2:3 | y | test.py:3:7:3:7 | y | +| test.py:2:7:2:7 | x | test.py:2:3:2:3 | y | +| test.py:3:3:3:3 | z | test.py:4:10:4:10 | z | +| test.py:3:7:3:7 | y | test.py:3:3:3:3 | z | +| test.py:6:1:6:1 | a | test.py:7:19:7:19 | a | +| test.py:6:5:6:6 | IntegerLiteral | test.py:6:1:6:1 | a | +| test.py:7:5:7:20 | After obfuscated_id() | test.py:7:1:7:1 | b | diff --git a/python/ql/test/library-tests/dataflow/basic/maximalFlows.expected b/python/ql/test/library-tests/dataflow/basic/maximalFlows.expected index 421918620455..b820e3d2d7b0 100644 --- a/python/ql/test/library-tests/dataflow/basic/maximalFlows.expected +++ b/python/ql/test/library-tests/dataflow/basic/maximalFlows.expected @@ -1,12 +1,12 @@ -| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:0:0:0:0 | ModuleVariableNode in Module test for obfuscated_id | -| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:7:5:7:17 | ControlFlowNode for obfuscated_id | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:1:19:1:19 | ControlFlowNode for x | test.py:7:1:7:1 | ControlFlowNode for b | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:0:0:0:0 | ModuleVariableNode in Module test for a | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:7:1:7:1 | ControlFlowNode for b | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | test.py:7:19:7:19 | ControlFlowNode for a | -| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | -| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | test.py:7:1:7:1 | ControlFlowNode for b | +| test.py:1:1:1:21 | FunctionExpr | test.py:0:0:0:0 | ModuleVariableNode in Module test for obfuscated_id | +| test.py:1:1:1:21 | FunctionExpr | test.py:7:5:7:17 | obfuscated_id | +| test.py:1:19:1:19 | x | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | +| test.py:1:19:1:19 | x | test.py:4:10:4:10 | z | +| test.py:1:19:1:19 | x | test.py:7:1:7:1 | b | +| test.py:6:5:6:6 | IntegerLiteral | test.py:0:0:0:0 | ModuleVariableNode in Module test for a | +| test.py:6:5:6:6 | IntegerLiteral | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | +| test.py:6:5:6:6 | IntegerLiteral | test.py:4:10:4:10 | z | +| test.py:6:5:6:6 | IntegerLiteral | test.py:7:1:7:1 | b | +| test.py:6:5:6:6 | IntegerLiteral | test.py:7:19:7:19 | a | +| test.py:7:5:7:20 | After obfuscated_id() | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | +| test.py:7:5:7:20 | After obfuscated_id() | test.py:7:1:7:1 | b | diff --git a/python/ql/test/library-tests/dataflow/basic/sinks.expected b/python/ql/test/library-tests/dataflow/basic/sinks.expected index 80055f9a2f2b..d516fa208956 100644 --- a/python/ql/test/library-tests/dataflow/basic/sinks.expected +++ b/python/ql/test/library-tests/dataflow/basic/sinks.expected @@ -3,23 +3,23 @@ | test.py:0:0:0:0 | ModuleVariableNode in Module test for a | | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | | test.py:0:0:0:0 | ModuleVariableNode in Module test for obfuscated_id | -| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | +| test.py:1:1:1:21 | FunctionExpr | | test.py:1:1:1:21 | SynthDictSplatParameterNode | -| test.py:1:5:1:17 | ControlFlowNode for obfuscated_id | -| test.py:1:19:1:19 | ControlFlowNode for x | -| test.py:2:3:2:3 | ControlFlowNode for y | -| test.py:2:7:2:7 | ControlFlowNode for x | -| test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:3:7:3:7 | ControlFlowNode for y | -| test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:6:1:6:1 | ControlFlowNode for a | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | -| test.py:7:1:7:1 | ControlFlowNode for b | +| test.py:1:5:1:17 | obfuscated_id | +| test.py:1:19:1:19 | x | +| test.py:2:3:2:3 | y | +| test.py:2:7:2:7 | x | +| test.py:3:3:3:3 | z | +| test.py:3:7:3:7 | y | +| test.py:4:10:4:10 | z | +| test.py:6:1:6:1 | a | +| test.py:6:5:6:6 | IntegerLiteral | +| test.py:7:1:7:1 | b | | test.py:7:5:7:17 | Capturing closure argument | -| test.py:7:5:7:17 | ControlFlowNode for obfuscated_id | | test.py:7:5:7:17 | [post] Capturing closure argument | -| test.py:7:5:7:17 | [post] ControlFlowNode for obfuscated_id | -| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | -| test.py:7:5:7:20 | [pre] ControlFlowNode for obfuscated_id() | -| test.py:7:19:7:19 | ControlFlowNode for a | -| test.py:7:19:7:19 | [post] ControlFlowNode for a | +| test.py:7:5:7:17 | [post] obfuscated_id | +| test.py:7:5:7:17 | obfuscated_id | +| test.py:7:5:7:20 | After obfuscated_id() | +| test.py:7:5:7:20 | [pre] After obfuscated_id() | +| test.py:7:19:7:19 | [post] a | +| test.py:7:19:7:19 | a | diff --git a/python/ql/test/library-tests/dataflow/basic/sources.expected b/python/ql/test/library-tests/dataflow/basic/sources.expected index 80055f9a2f2b..d516fa208956 100644 --- a/python/ql/test/library-tests/dataflow/basic/sources.expected +++ b/python/ql/test/library-tests/dataflow/basic/sources.expected @@ -3,23 +3,23 @@ | test.py:0:0:0:0 | ModuleVariableNode in Module test for a | | test.py:0:0:0:0 | ModuleVariableNode in Module test for b | | test.py:0:0:0:0 | ModuleVariableNode in Module test for obfuscated_id | -| test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | +| test.py:1:1:1:21 | FunctionExpr | | test.py:1:1:1:21 | SynthDictSplatParameterNode | -| test.py:1:5:1:17 | ControlFlowNode for obfuscated_id | -| test.py:1:19:1:19 | ControlFlowNode for x | -| test.py:2:3:2:3 | ControlFlowNode for y | -| test.py:2:7:2:7 | ControlFlowNode for x | -| test.py:3:3:3:3 | ControlFlowNode for z | -| test.py:3:7:3:7 | ControlFlowNode for y | -| test.py:4:10:4:10 | ControlFlowNode for z | -| test.py:6:1:6:1 | ControlFlowNode for a | -| test.py:6:5:6:6 | ControlFlowNode for IntegerLiteral | -| test.py:7:1:7:1 | ControlFlowNode for b | +| test.py:1:5:1:17 | obfuscated_id | +| test.py:1:19:1:19 | x | +| test.py:2:3:2:3 | y | +| test.py:2:7:2:7 | x | +| test.py:3:3:3:3 | z | +| test.py:3:7:3:7 | y | +| test.py:4:10:4:10 | z | +| test.py:6:1:6:1 | a | +| test.py:6:5:6:6 | IntegerLiteral | +| test.py:7:1:7:1 | b | | test.py:7:5:7:17 | Capturing closure argument | -| test.py:7:5:7:17 | ControlFlowNode for obfuscated_id | | test.py:7:5:7:17 | [post] Capturing closure argument | -| test.py:7:5:7:17 | [post] ControlFlowNode for obfuscated_id | -| test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | -| test.py:7:5:7:20 | [pre] ControlFlowNode for obfuscated_id() | -| test.py:7:19:7:19 | ControlFlowNode for a | -| test.py:7:19:7:19 | [post] ControlFlowNode for a | +| test.py:7:5:7:17 | [post] obfuscated_id | +| test.py:7:5:7:17 | obfuscated_id | +| test.py:7:5:7:20 | After obfuscated_id() | +| test.py:7:5:7:20 | [pre] After obfuscated_id() | +| test.py:7:19:7:19 | [post] a | +| test.py:7:19:7:19 | a | diff --git a/python/ql/test/library-tests/dataflow/callgraph_crosstalk/Arguments.expected b/python/ql/test/library-tests/dataflow/callgraph_crosstalk/Arguments.expected index 99c2d987d16d..c8efb96e5648 100644 --- a/python/ql/test/library-tests/dataflow/callgraph_crosstalk/Arguments.expected +++ b/python/ql/test/library-tests/dataflow/callgraph_crosstalk/Arguments.expected @@ -1,13 +1,13 @@ -| test.py:32:8:32:23 | CrosstalkTestX() | test.py:9:5:9:23 | Function __init__ | test.py:32:8:32:23 | [pre] ControlFlowNode for CrosstalkTestX() | self | -| test.py:33:8:33:23 | CrosstalkTestY() | test.py:21:5:21:23 | Function __init__ | test.py:33:8:33:23 | [pre] ControlFlowNode for CrosstalkTestY() | self | -| test.py:43:1:43:8 | func() | test.py:13:5:13:26 | Function setx | test.py:36:12:36:15 | ControlFlowNode for objx | self | -| test.py:43:1:43:8 | func() | test.py:13:5:13:26 | Function setx | test.py:43:6:43:7 | ControlFlowNode for IntegerLiteral | position 0 | -| test.py:43:1:43:8 | func() | test.py:25:5:25:26 | Function sety | test.py:38:12:38:15 | ControlFlowNode for objy | self | -| test.py:43:1:43:8 | func() | test.py:25:5:25:26 | Function sety | test.py:43:6:43:7 | ControlFlowNode for IntegerLiteral | position 0 | -| test.py:51:1:51:8 | func() | test.py:16:5:16:30 | Function setvalue | test.py:47:12:47:15 | ControlFlowNode for objx | self | -| test.py:51:1:51:8 | func() | test.py:16:5:16:30 | Function setvalue | test.py:51:6:51:7 | ControlFlowNode for IntegerLiteral | position 0 | -| test.py:51:1:51:8 | func() | test.py:28:5:28:30 | Function setvalue | test.py:49:12:49:15 | ControlFlowNode for objy | self | -| test.py:51:1:51:8 | func() | test.py:28:5:28:30 | Function setvalue | test.py:51:6:51:7 | ControlFlowNode for IntegerLiteral | position 0 | -| test.py:70:1:70:8 | func() | test.py:58:5:58:33 | Function foo | test.py:63:12:63:12 | ControlFlowNode for a | self | -| test.py:70:1:70:8 | func() | test.py:58:5:58:33 | Function foo | test.py:70:6:70:7 | ControlFlowNode for IntegerLiteral | position 0 | -| test.py:70:1:70:8 | func() | test.py:58:5:58:33 | Function foo | test.py:70:6:70:7 | ControlFlowNode for IntegerLiteral | self | +| test.py:32:8:32:23 | CrosstalkTestX() | test.py:9:5:9:23 | Function __init__ | test.py:32:8:32:23 | [pre] After CrosstalkTestX() | self | +| test.py:33:8:33:23 | CrosstalkTestY() | test.py:21:5:21:23 | Function __init__ | test.py:33:8:33:23 | [pre] After CrosstalkTestY() | self | +| test.py:43:1:43:8 | func() | test.py:13:5:13:26 | Function setx | test.py:36:12:36:15 | objx | self | +| test.py:43:1:43:8 | func() | test.py:13:5:13:26 | Function setx | test.py:43:6:43:7 | IntegerLiteral | position 0 | +| test.py:43:1:43:8 | func() | test.py:25:5:25:26 | Function sety | test.py:38:12:38:15 | objy | self | +| test.py:43:1:43:8 | func() | test.py:25:5:25:26 | Function sety | test.py:43:6:43:7 | IntegerLiteral | position 0 | +| test.py:51:1:51:8 | func() | test.py:16:5:16:30 | Function setvalue | test.py:47:12:47:15 | objx | self | +| test.py:51:1:51:8 | func() | test.py:16:5:16:30 | Function setvalue | test.py:51:6:51:7 | IntegerLiteral | position 0 | +| test.py:51:1:51:8 | func() | test.py:28:5:28:30 | Function setvalue | test.py:49:12:49:15 | objy | self | +| test.py:51:1:51:8 | func() | test.py:28:5:28:30 | Function setvalue | test.py:51:6:51:7 | IntegerLiteral | position 0 | +| test.py:70:1:70:8 | func() | test.py:58:5:58:33 | Function foo | test.py:63:12:63:12 | a | self | +| test.py:70:1:70:8 | func() | test.py:58:5:58:33 | Function foo | test.py:70:6:70:7 | IntegerLiteral | position 0 | +| test.py:70:1:70:8 | func() | test.py:58:5:58:33 | Function foo | test.py:70:6:70:7 | IntegerLiteral | self | diff --git a/python/ql/test/library-tests/dataflow/coverage/argumentRoutingTest.ql b/python/ql/test/library-tests/dataflow/coverage/argumentRoutingTest.ql index 7851dc4dda80..bd5ad3059f9b 100644 --- a/python/ql/test/library-tests/dataflow/coverage/argumentRoutingTest.ql +++ b/python/ql/test/library-tests/dataflow/coverage/argumentRoutingTest.ql @@ -1,4 +1,6 @@ import python +private import semmle.python.controlflow.internal.Cfg as Cfg +private import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.internal.DataFlowPrivate as DataFlowPrivate import utils.test.dataflow.RoutingTest @@ -26,21 +28,21 @@ class ArgNumber extends int { module ArgumentRoutingConfig implements DataFlow::ConfigSig { additional predicate isArgSource(DataFlow::Node node, ArgNumber argNumber) { - node.(DataFlow::CfgNode).getNode().(NameNode).getId() = "arg" + argNumber + node.(DataFlow::CfgNode).getNode().(Cfg::NameNode).getId() = "arg" + argNumber } predicate isSource(DataFlow::Node node) { isArgSource(node, _) } additional predicate isGoodSink(DataFlow::Node node, ArgNumber argNumber) { - exists(CallNode call | - call.getFunction().(NameNode).getId() = "SINK" + argNumber and + exists(Cfg::CallNode call | + call.getFunction().(Cfg::NameNode).getId() = "SINK" + argNumber and node.(DataFlow::CfgNode).getNode() = call.getAnArg() ) } additional predicate isBadSink(DataFlow::Node node, ArgNumber argNumber) { - exists(CallNode call | - call.getFunction().(NameNode).getId() = "SINK" + argNumber + "_F" and + exists(Cfg::CallNode call | + call.getFunction().(Cfg::NameNode).getId() = "SINK" + argNumber + "_F" and node.(DataFlow::CfgNode).getNode() = call.getAnArg() ) } @@ -60,17 +62,17 @@ module ArgumentRoutingFlow = DataFlow::Global; module Argument1ExtraRoutingConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node node) { - exists(AssignmentDefinition def, DataFlow::CallCfgNode call | + exists(SsaImpl::AssignmentDefinition def, DataFlow::CallCfgNode call | def.getDefiningNode() = node.(DataFlow::CfgNode).getNode() and def.getValue() = call.getNode() and - call.getFunction().asCfgNode().(NameNode).getId().matches("With\\_%") + call.getFunction().asCfgNode().(Cfg::NameNode).getId().matches("With\\_%") ) and - node.(DataFlow::CfgNode).getNode().(NameNode).getId().matches("with\\_%") + node.(DataFlow::CfgNode).getNode().(Cfg::NameNode).getId().matches("with\\_%") } predicate isSink(DataFlow::Node node) { - exists(CallNode call | - call.getFunction().(NameNode).getId() = "SINK1" and + exists(Cfg::CallNode call | + call.getFunction().(Cfg::NameNode).getId() = "SINK1" and node.(DataFlow::CfgNode).getNode() = call.getAnArg() ) } diff --git a/python/ql/test/library-tests/dataflow/coverage/localFlow.expected b/python/ql/test/library-tests/dataflow/coverage/localFlow.expected index 665fb1aa12b8..303a2736935d 100644 --- a/python/ql/test/library-tests/dataflow/coverage/localFlow.expected +++ b/python/ql/test/library-tests/dataflow/coverage/localFlow.expected @@ -1,11 +1,11 @@ -| test.py:41:1:41:33 | Entry definition for SsaSourceVariable NONSOURCE | test.py:42:10:42:18 | ControlFlowNode for NONSOURCE | -| test.py:41:1:41:33 | Entry definition for SsaSourceVariable SINK | test.py:44:5:44:8 | ControlFlowNode for SINK | -| test.py:41:1:41:33 | Entry definition for SsaSourceVariable SOURCE | test.py:42:21:42:26 | ControlFlowNode for SOURCE | -| test.py:42:5:42:5 | ControlFlowNode for x | test.py:43:9:43:9 | ControlFlowNode for x | -| test.py:42:10:42:26 | ControlFlowNode for Tuple | test.py:42:5:42:5 | ControlFlowNode for x | -| test.py:43:5:43:5 | ControlFlowNode for y | test.py:44:10:44:10 | ControlFlowNode for y | -| test.py:43:9:43:12 | ControlFlowNode for Subscript | test.py:43:5:43:5 | ControlFlowNode for y | -| test.py:208:1:208:53 | Entry definition for SsaSourceVariable SINK | test.py:210:5:210:8 | ControlFlowNode for SINK | -| test.py:208:1:208:53 | Entry definition for SsaSourceVariable SOURCE | test.py:209:25:209:30 | ControlFlowNode for SOURCE | -| test.py:209:5:209:5 | ControlFlowNode for x | test.py:210:10:210:10 | ControlFlowNode for x | -| test.py:209:9:209:68 | ControlFlowNode for ListComp | test.py:209:5:209:5 | ControlFlowNode for x | +| test.py:41:1:41:33 | Entry definition for Global Variable NONSOURCE | test.py:42:10:42:18 | NONSOURCE | +| test.py:41:1:41:33 | Entry definition for Global Variable SINK | test.py:44:5:44:8 | SINK | +| test.py:41:1:41:33 | Entry definition for Global Variable SOURCE | test.py:42:21:42:26 | SOURCE | +| test.py:42:5:42:5 | x | test.py:43:9:43:9 | x | +| test.py:42:10:42:26 | After Tuple | test.py:42:5:42:5 | x | +| test.py:43:5:43:5 | y | test.py:44:10:44:10 | y | +| test.py:43:9:43:12 | After Subscript | test.py:43:5:43:5 | y | +| test.py:208:1:208:53 | Entry definition for Global Variable SINK | test.py:210:5:210:8 | SINK | +| test.py:208:1:208:53 | Entry definition for Global Variable SOURCE | test.py:209:25:209:30 | SOURCE | +| test.py:209:5:209:5 | x | test.py:210:10:210:10 | x | +| test.py:209:9:209:68 | After ListComp | test.py:209:5:209:5 | x | diff --git a/python/ql/test/library-tests/dataflow/coverage/test.py b/python/ql/test/library-tests/dataflow/coverage/test.py index 5f13ba5a403c..0b6d6f1444ea 100644 --- a/python/ql/test/library-tests/dataflow/coverage/test.py +++ b/python/ql/test/library-tests/dataflow/coverage/test.py @@ -844,7 +844,7 @@ def return_from_inner_scope(x): return SOURCE def test_return_from_inner_scope(): - SINK(return_from_inner_scope([])) # $ flow="SOURCE, l:-3 -> return_from_inner_scope(..)" + SINK(return_from_inner_scope([])) # $ MISSING: flow="SOURCE, l:-3 -> return_from_inner_scope(..)" # Inspired by reverse read inconsistency check diff --git a/python/ql/test/library-tests/dataflow/def-use-flow/def_use_counts.expected b/python/ql/test/library-tests/dataflow/def-use-flow/def_use_counts.expected index 1fe1d1d105a5..a30ed74b89f6 100644 --- a/python/ql/test/library-tests/dataflow/def-use-flow/def_use_counts.expected +++ b/python/ql/test/library-tests/dataflow/def-use-flow/def_use_counts.expected @@ -1,31 +1,41 @@ def_count | 4 | def -| def_use_flow.py:10:5:10:5 | Essa node definition | -| def_use_flow.py:17:11:17:11 | Essa node definition | -| def_use_flow.py:19:9:19:9 | Essa node definition | -| def_use_flow.py:21:7:21:7 | Essa node definition | +| def_use_flow.py:10:5:10:5 | SSA def(Local Variable x) | +| def_use_flow.py:17:11:17:11 | SSA def(Local Variable x) | +| def_use_flow.py:19:9:19:9 | SSA def(Local Variable x) | +| def_use_flow.py:21:7:21:7 | SSA def(Local Variable x) | implicit_use_count -| 0 | +| 1 | implicit_use +| def_use_flow.py:9:1:9:12 | Normal Exit | source_use_count | 3 | source_use -| def_use_flow.py:28:15:28:15 | ControlFlowNode for x | -| def_use_flow.py:30:13:30:13 | ControlFlowNode for x | -| def_use_flow.py:32:11:32:11 | ControlFlowNode for x | +| def_use_flow.py:28:15:28:15 | x | +| def_use_flow.py:30:13:30:13 | x | +| def_use_flow.py:32:11:32:11 | x | def_use_edge_count -| 12 | +| 21 | def_use_edge -| def_use_flow.py:10:5:10:5 | SSA variable x | def_use_flow.py:28:15:28:15 | ControlFlowNode for x | -| def_use_flow.py:10:5:10:5 | SSA variable x | def_use_flow.py:30:13:30:13 | ControlFlowNode for x | -| def_use_flow.py:10:5:10:5 | SSA variable x | def_use_flow.py:32:11:32:11 | ControlFlowNode for x | -| def_use_flow.py:17:11:17:11 | SSA variable x | def_use_flow.py:28:15:28:15 | ControlFlowNode for x | -| def_use_flow.py:17:11:17:11 | SSA variable x | def_use_flow.py:30:13:30:13 | ControlFlowNode for x | -| def_use_flow.py:17:11:17:11 | SSA variable x | def_use_flow.py:32:11:32:11 | ControlFlowNode for x | -| def_use_flow.py:19:9:19:9 | SSA variable x | def_use_flow.py:28:15:28:15 | ControlFlowNode for x | -| def_use_flow.py:19:9:19:9 | SSA variable x | def_use_flow.py:30:13:30:13 | ControlFlowNode for x | -| def_use_flow.py:19:9:19:9 | SSA variable x | def_use_flow.py:32:11:32:11 | ControlFlowNode for x | -| def_use_flow.py:21:7:21:7 | SSA variable x | def_use_flow.py:28:15:28:15 | ControlFlowNode for x | -| def_use_flow.py:21:7:21:7 | SSA variable x | def_use_flow.py:30:13:30:13 | ControlFlowNode for x | -| def_use_flow.py:21:7:21:7 | SSA variable x | def_use_flow.py:32:11:32:11 | ControlFlowNode for x | +| def_use_flow.py:10:5:10:5 | SSA def(Local Variable x) | def_use_flow.py:28:15:28:15 | x | +| def_use_flow.py:10:5:10:5 | SSA def(Local Variable x) | def_use_flow.py:30:13:30:13 | x | +| def_use_flow.py:10:5:10:5 | SSA def(Local Variable x) | def_use_flow.py:32:11:32:11 | x | +| def_use_flow.py:12:5:12:17 | SSA phi(Local Variable x) | def_use_flow.py:28:15:28:15 | x | +| def_use_flow.py:12:5:12:17 | SSA phi(Local Variable x) | def_use_flow.py:30:13:30:13 | x | +| def_use_flow.py:12:5:12:17 | SSA phi(Local Variable x) | def_use_flow.py:32:11:32:11 | x | +| def_use_flow.py:13:7:13:19 | SSA phi(Local Variable x) | def_use_flow.py:28:15:28:15 | x | +| def_use_flow.py:13:7:13:19 | SSA phi(Local Variable x) | def_use_flow.py:30:13:30:13 | x | +| def_use_flow.py:13:7:13:19 | SSA phi(Local Variable x) | def_use_flow.py:32:11:32:11 | x | +| def_use_flow.py:14:9:14:21 | SSA phi(Local Variable x) | def_use_flow.py:28:15:28:15 | x | +| def_use_flow.py:14:9:14:21 | SSA phi(Local Variable x) | def_use_flow.py:30:13:30:13 | x | +| def_use_flow.py:14:9:14:21 | SSA phi(Local Variable x) | def_use_flow.py:32:11:32:11 | x | +| def_use_flow.py:17:11:17:11 | SSA def(Local Variable x) | def_use_flow.py:28:15:28:15 | x | +| def_use_flow.py:17:11:17:11 | SSA def(Local Variable x) | def_use_flow.py:30:13:30:13 | x | +| def_use_flow.py:17:11:17:11 | SSA def(Local Variable x) | def_use_flow.py:32:11:32:11 | x | +| def_use_flow.py:19:9:19:9 | SSA def(Local Variable x) | def_use_flow.py:28:15:28:15 | x | +| def_use_flow.py:19:9:19:9 | SSA def(Local Variable x) | def_use_flow.py:30:13:30:13 | x | +| def_use_flow.py:19:9:19:9 | SSA def(Local Variable x) | def_use_flow.py:32:11:32:11 | x | +| def_use_flow.py:21:7:21:7 | SSA def(Local Variable x) | def_use_flow.py:28:15:28:15 | x | +| def_use_flow.py:21:7:21:7 | SSA def(Local Variable x) | def_use_flow.py:30:13:30:13 | x | +| def_use_flow.py:21:7:21:7 | SSA def(Local Variable x) | def_use_flow.py:32:11:32:11 | x | diff --git a/python/ql/test/library-tests/dataflow/def-use-flow/def_use_counts.ql b/python/ql/test/library-tests/dataflow/def-use-flow/def_use_counts.ql index 0f0d5953a367..1e461a7caf1a 100644 --- a/python/ql/test/library-tests/dataflow/def-use-flow/def_use_counts.ql +++ b/python/ql/test/library-tests/dataflow/def-use-flow/def_use_counts.ql @@ -1,36 +1,38 @@ import python +private import semmle.python.controlflow.internal.Cfg as Cfg +private import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl private import semmle.python.dataflow.new.internal.DataFlowPrivate query int def_count() { - exists(SsaSourceVariable x | x.getName() = "x" | - result = count(EssaNodeDefinition def | def.getSourceVariable() = x) + exists(SsaImpl::SsaSourceVariable x | x.getName() = "x" | + result = count(SsaImpl::EssaNodeDefinition def | def.getSourceVariable() = x) ) } -query EssaNodeDefinition def() { - exists(SsaSourceVariable x | x.getName() = "x" | result.getSourceVariable() = x) +query SsaImpl::EssaNodeDefinition def() { + exists(SsaImpl::SsaSourceVariable x | x.getName() = "x" | result.getSourceVariable() = x) } query int implicit_use_count() { - exists(SsaSourceVariable x | x.getName() = "x" | result = count(x.getAnImplicitUse())) + exists(SsaImpl::SsaSourceVariable x | x.getName() = "x" | result = count(x.getAnImplicitUse())) } -query ControlFlowNode implicit_use() { - exists(SsaSourceVariable x | x.getName() = "x" | result = x.getAnImplicitUse()) +query Cfg::ControlFlowNode implicit_use() { + exists(SsaImpl::SsaSourceVariable x | x.getName() = "x" | result = x.getAnImplicitUse()) } query int source_use_count() { - exists(SsaSourceVariable x | x.getName() = "x" | result = count(x.getASourceUse())) + exists(SsaImpl::SsaSourceVariable x | x.getName() = "x" | result = count(x.getASourceUse())) } -query ControlFlowNode source_use() { - exists(SsaSourceVariable x | x.getName() = "x" | result = x.getASourceUse()) +query Cfg::ControlFlowNode source_use() { + exists(SsaImpl::SsaSourceVariable x | x.getName() = "x" | result = x.getASourceUse()) } query int def_use_edge_count() { - exists(SsaSourceVariable x | x.getName() = "x" | + exists(SsaImpl::SsaSourceVariable x | x.getName() = "x" | result = - count(EssaVariable v, NameNode use | + count(SsaImpl::EssaVariable v, Cfg::NameNode use | v.getSourceVariable() = x and use = x.getAUse() and LocalFlow::defToFirstUse(v, use) @@ -38,8 +40,8 @@ query int def_use_edge_count() { ) } -query predicate def_use_edge(EssaVariable v, NameNode use) { - exists(SsaSourceVariable x | x.getName() = "x" | +query predicate def_use_edge(SsaImpl::EssaVariable v, Cfg::NameNode use) { + exists(SsaImpl::SsaSourceVariable x | x.getName() = "x" | v.getSourceVariable() = x and use = x.getAUse() and LocalFlow::defToFirstUse(v, use) diff --git a/python/ql/test/library-tests/dataflow/enclosing-callable/EnclosingCallable.expected b/python/ql/test/library-tests/dataflow/enclosing-callable/EnclosingCallable.expected index c168ea8c3916..58bcf1bb44ee 100644 --- a/python/ql/test/library-tests/dataflow/enclosing-callable/EnclosingCallable.expected +++ b/python/ql/test/library-tests/dataflow/enclosing-callable/EnclosingCallable.expected @@ -1,24 +1,24 @@ -| class_example.py:0:0:0:0 | Module class_example | class_example.py:1:1:1:3 | ControlFlowNode for wat | -| class_example.py:0:0:0:0 | Module class_example | class_example.py:1:7:1:7 | ControlFlowNode for IntegerLiteral | -| class_example.py:0:0:0:0 | Module class_example | class_example.py:3:1:3:10 | ControlFlowNode for ClassExpr | -| class_example.py:0:0:0:0 | Module class_example | class_example.py:3:7:3:9 | ControlFlowNode for Wat | -| class_example.py:0:0:0:0 | Module class_example | class_example.py:4:5:4:7 | ControlFlowNode for wat | -| class_example.py:0:0:0:0 | Module class_example | class_example.py:4:11:4:11 | ControlFlowNode for IntegerLiteral | -| class_example.py:0:0:0:0 | Module class_example | class_example.py:5:5:5:9 | ControlFlowNode for print | -| class_example.py:0:0:0:0 | Module class_example | class_example.py:5:5:5:26 | ControlFlowNode for print() | -| class_example.py:0:0:0:0 | Module class_example | class_example.py:5:11:5:20 | ControlFlowNode for StringLiteral | -| class_example.py:0:0:0:0 | Module class_example | class_example.py:5:23:5:25 | ControlFlowNode for wat | -| class_example.py:0:0:0:0 | Module class_example | class_example.py:7:1:7:5 | ControlFlowNode for print | -| class_example.py:0:0:0:0 | Module class_example | class_example.py:7:1:7:23 | ControlFlowNode for print() | -| class_example.py:0:0:0:0 | Module class_example | class_example.py:7:7:7:17 | ControlFlowNode for StringLiteral | -| class_example.py:0:0:0:0 | Module class_example | class_example.py:7:20:7:22 | ControlFlowNode for wat | -| generator.py:0:0:0:0 | Module generator | generator.py:1:1:1:23 | ControlFlowNode for FunctionExpr | -| generator.py:0:0:0:0 | Module generator | generator.py:1:5:1:18 | ControlFlowNode for generator_func | -| generator.py:1:1:1:23 | Function generator_func | generator.py:1:20:1:21 | ControlFlowNode for xs | -| generator.py:1:1:1:23 | Function generator_func | generator.py:2:12:2:26 | ControlFlowNode for ListComp | -| generator.py:1:1:1:23 | Function generator_func | generator.py:2:24:2:25 | ControlFlowNode for xs | -| generator.py:2:12:2:26 | Function listcomp | generator.py:2:12:2:26 | ControlFlowNode for .0 | -| generator.py:2:12:2:26 | Function listcomp | generator.py:2:12:2:26 | ControlFlowNode for .0 | -| generator.py:2:12:2:26 | Function listcomp | generator.py:2:13:2:13 | ControlFlowNode for Yield | -| generator.py:2:12:2:26 | Function listcomp | generator.py:2:13:2:13 | ControlFlowNode for x | -| generator.py:2:12:2:26 | Function listcomp | generator.py:2:19:2:19 | ControlFlowNode for x | +| class_example.py:0:0:0:0 | Module class_example | class_example.py:1:1:1:3 | wat | +| class_example.py:0:0:0:0 | Module class_example | class_example.py:1:7:1:7 | IntegerLiteral | +| class_example.py:0:0:0:0 | Module class_example | class_example.py:3:1:3:10 | ClassExpr | +| class_example.py:0:0:0:0 | Module class_example | class_example.py:3:7:3:9 | Wat | +| class_example.py:0:0:0:0 | Module class_example | class_example.py:4:5:4:7 | wat | +| class_example.py:0:0:0:0 | Module class_example | class_example.py:4:11:4:11 | IntegerLiteral | +| class_example.py:0:0:0:0 | Module class_example | class_example.py:5:5:5:9 | print | +| class_example.py:0:0:0:0 | Module class_example | class_example.py:5:5:5:26 | After print() | +| class_example.py:0:0:0:0 | Module class_example | class_example.py:5:11:5:20 | StringLiteral | +| class_example.py:0:0:0:0 | Module class_example | class_example.py:5:23:5:25 | wat | +| class_example.py:0:0:0:0 | Module class_example | class_example.py:7:1:7:5 | print | +| class_example.py:0:0:0:0 | Module class_example | class_example.py:7:1:7:23 | After print() | +| class_example.py:0:0:0:0 | Module class_example | class_example.py:7:7:7:17 | StringLiteral | +| class_example.py:0:0:0:0 | Module class_example | class_example.py:7:20:7:22 | wat | +| generator.py:0:0:0:0 | Module generator | generator.py:1:1:1:23 | FunctionExpr | +| generator.py:0:0:0:0 | Module generator | generator.py:1:5:1:18 | generator_func | +| generator.py:1:1:1:23 | Function generator_func | generator.py:1:20:1:21 | xs | +| generator.py:1:1:1:23 | Function generator_func | generator.py:2:12:2:26 | After ListComp | +| generator.py:1:1:1:23 | Function generator_func | generator.py:2:24:2:25 | xs | +| generator.py:2:12:2:26 | Function listcomp | generator.py:2:12:2:26 | .0 | +| generator.py:2:12:2:26 | Function listcomp | generator.py:2:12:2:26 | After .0 [empty] | +| generator.py:2:12:2:26 | Function listcomp | generator.py:2:13:2:13 | After Yield | +| generator.py:2:12:2:26 | Function listcomp | generator.py:2:13:2:13 | x | +| generator.py:2:12:2:26 | Function listcomp | generator.py:2:19:2:19 | x | diff --git a/python/ql/test/library-tests/dataflow/fieldflow/UnresolvedCalls.expected b/python/ql/test/library-tests/dataflow/fieldflow/UnresolvedCalls.expected index 593b6e118874..4f4eebc6fb2b 100644 --- a/python/ql/test/library-tests/dataflow/fieldflow/UnresolvedCalls.expected +++ b/python/ql/test/library-tests/dataflow/fieldflow/UnresolvedCalls.expected @@ -1,4 +1,4 @@ -| test.py:4:17:4:60 | ControlFlowNode for Attribute() | Unexpected result: unresolved_call=os.path.dirname(..) | -| test.py:4:33:4:59 | ControlFlowNode for Attribute() | Unexpected result: unresolved_call=os.path.dirname(..) | -| test_dict.py:4:17:4:60 | ControlFlowNode for Attribute() | Unexpected result: unresolved_call=os.path.dirname(..) | -| test_dict.py:4:33:4:59 | ControlFlowNode for Attribute() | Unexpected result: unresolved_call=os.path.dirname(..) | +| test.py:4:17:4:60 | After Attribute() | Unexpected result: unresolved_call=os.path.dirname(..) | +| test.py:4:33:4:59 | After Attribute() | Unexpected result: unresolved_call=os.path.dirname(..) | +| test_dict.py:4:17:4:60 | After Attribute() | Unexpected result: unresolved_call=os.path.dirname(..) | +| test_dict.py:4:33:4:59 | After Attribute() | Unexpected result: unresolved_call=os.path.dirname(..) | diff --git a/python/ql/test/library-tests/dataflow/fieldflow/UnresolvedCalls.ql b/python/ql/test/library-tests/dataflow/fieldflow/UnresolvedCalls.ql index 57e8e7f880fd..13c8664cd34c 100644 --- a/python/ql/test/library-tests/dataflow/fieldflow/UnresolvedCalls.ql +++ b/python/ql/test/library-tests/dataflow/fieldflow/UnresolvedCalls.ql @@ -1,9 +1,10 @@ import python +private import semmle.python.controlflow.internal.Cfg as Cfg import utils.test.dataflow.UnresolvedCalls private import semmle.python.dataflow.new.DataFlow module IgnoreDictMethod implements UnresolvedCallExpectationsSig { - predicate unresolvedCall(CallNode call) { + predicate unresolvedCall(Cfg::CallNode call) { DefaultUnresolvedCallExpectations::unresolvedCall(call) and not any(DataFlow::MethodCallNode methodCall | methodCall.getMethodName() in ["get", "setdefault"] diff --git a/python/ql/test/library-tests/dataflow/global-flow/test.py b/python/ql/test/library-tests/dataflow/global-flow/test.py index 2f122364d37e..dde5b80f7399 100644 --- a/python/ql/test/library-tests/dataflow/global-flow/test.py +++ b/python/ql/test/library-tests/dataflow/global-flow/test.py @@ -17,7 +17,7 @@ # Modification by reassignment -g_mod = [] +g_mod = [] # $ SPURIOUS: writes=g_mod # This assignment does not produce any flow, since `g_mod` is immediately reassigned. # The following assignment should not be a `ModuleVariableNode`, diff --git a/python/ql/test/library-tests/dataflow/import-star/global.expected b/python/ql/test/library-tests/dataflow/import-star/global.expected index 95f2481489f3..2e7a4109218b 100644 --- a/python/ql/test/library-tests/dataflow/import-star/global.expected +++ b/python/ql/test/library-tests/dataflow/import-star/global.expected @@ -1,22 +1,22 @@ -| test3.py:1:17:1:19 | ControlFlowNode for ImportMember | test3.py:1:17:1:19 | ControlFlowNode for foo | -| test3.py:1:17:1:19 | ControlFlowNode for ImportMember | test3.py:2:7:2:9 | ControlFlowNode for foo | -| test3.py:1:17:1:19 | ControlFlowNode for foo | test3.py:2:7:2:9 | ControlFlowNode for foo | -| three.py:1:1:1:3 | ControlFlowNode for foo | test1.py:2:7:2:9 | ControlFlowNode for foo | -| three.py:1:1:1:3 | ControlFlowNode for foo | test3.py:1:17:1:19 | ControlFlowNode for ImportMember | -| three.py:1:1:1:3 | ControlFlowNode for foo | test3.py:1:17:1:19 | ControlFlowNode for foo | -| three.py:1:1:1:3 | ControlFlowNode for foo | test3.py:2:7:2:9 | ControlFlowNode for foo | -| three.py:1:1:1:3 | ControlFlowNode for foo | two.py:2:7:2:9 | ControlFlowNode for foo | -| three.py:1:7:1:7 | ControlFlowNode for IntegerLiteral | test1.py:2:7:2:9 | ControlFlowNode for foo | -| three.py:1:7:1:7 | ControlFlowNode for IntegerLiteral | test3.py:1:17:1:19 | ControlFlowNode for ImportMember | -| three.py:1:7:1:7 | ControlFlowNode for IntegerLiteral | test3.py:1:17:1:19 | ControlFlowNode for foo | -| three.py:1:7:1:7 | ControlFlowNode for IntegerLiteral | test3.py:2:7:2:9 | ControlFlowNode for foo | -| three.py:1:7:1:7 | ControlFlowNode for IntegerLiteral | three.py:1:1:1:3 | ControlFlowNode for foo | -| three.py:1:7:1:7 | ControlFlowNode for IntegerLiteral | two.py:2:7:2:9 | ControlFlowNode for foo | -| trois.py:1:1:1:3 | ControlFlowNode for foo | deux.py:2:7:2:9 | ControlFlowNode for foo | -| trois.py:1:1:1:3 | ControlFlowNode for foo | test2.py:2:7:2:9 | ControlFlowNode for foo | -| trois.py:1:7:1:7 | ControlFlowNode for IntegerLiteral | deux.py:2:7:2:9 | ControlFlowNode for foo | -| trois.py:1:7:1:7 | ControlFlowNode for IntegerLiteral | test2.py:2:7:2:9 | ControlFlowNode for foo | -| trois.py:1:7:1:7 | ControlFlowNode for IntegerLiteral | trois.py:1:1:1:3 | ControlFlowNode for foo | -| two.py:2:7:2:9 | ControlFlowNode for foo | test3.py:1:17:1:19 | ControlFlowNode for ImportMember | -| two.py:2:7:2:9 | ControlFlowNode for foo | test3.py:1:17:1:19 | ControlFlowNode for foo | -| two.py:2:7:2:9 | ControlFlowNode for foo | test3.py:2:7:2:9 | ControlFlowNode for foo | +| test3.py:1:17:1:19 | After ImportMember | test3.py:1:17:1:19 | foo | +| test3.py:1:17:1:19 | After ImportMember | test3.py:2:7:2:9 | foo | +| test3.py:1:17:1:19 | foo | test3.py:2:7:2:9 | foo | +| three.py:1:1:1:3 | foo | test1.py:2:7:2:9 | foo | +| three.py:1:1:1:3 | foo | test3.py:1:17:1:19 | After ImportMember | +| three.py:1:1:1:3 | foo | test3.py:1:17:1:19 | foo | +| three.py:1:1:1:3 | foo | test3.py:2:7:2:9 | foo | +| three.py:1:1:1:3 | foo | two.py:2:7:2:9 | foo | +| three.py:1:7:1:7 | IntegerLiteral | test1.py:2:7:2:9 | foo | +| three.py:1:7:1:7 | IntegerLiteral | test3.py:1:17:1:19 | After ImportMember | +| three.py:1:7:1:7 | IntegerLiteral | test3.py:1:17:1:19 | foo | +| three.py:1:7:1:7 | IntegerLiteral | test3.py:2:7:2:9 | foo | +| three.py:1:7:1:7 | IntegerLiteral | three.py:1:1:1:3 | foo | +| three.py:1:7:1:7 | IntegerLiteral | two.py:2:7:2:9 | foo | +| trois.py:1:1:1:3 | foo | deux.py:2:7:2:9 | foo | +| trois.py:1:1:1:3 | foo | test2.py:2:7:2:9 | foo | +| trois.py:1:7:1:7 | IntegerLiteral | deux.py:2:7:2:9 | foo | +| trois.py:1:7:1:7 | IntegerLiteral | test2.py:2:7:2:9 | foo | +| trois.py:1:7:1:7 | IntegerLiteral | trois.py:1:1:1:3 | foo | +| two.py:2:7:2:9 | foo | test3.py:1:17:1:19 | After ImportMember | +| two.py:2:7:2:9 | foo | test3.py:1:17:1:19 | foo | +| two.py:2:7:2:9 | foo | test3.py:2:7:2:9 | foo | diff --git a/python/ql/test/library-tests/dataflow/method-calls/test.expected b/python/ql/test/library-tests/dataflow/method-calls/test.expected index 588c934e8597..d0551ac94771 100644 --- a/python/ql/test/library-tests/dataflow/method-calls/test.expected +++ b/python/ql/test/library-tests/dataflow/method-calls/test.expected @@ -1,8 +1,8 @@ conjunctive_lookup -| test.py:6:1:6:6 | ControlFlowNode for meth() | meth() | obj1 | bar | -| test.py:6:1:6:6 | ControlFlowNode for meth() | meth() | obj1 | foo | -| test.py:6:1:6:6 | ControlFlowNode for meth() | meth() | obj2 | bar | -| test.py:6:1:6:6 | ControlFlowNode for meth() | meth() | obj2 | foo | +| test.py:6:1:6:6 | After meth() | meth() | obj1 | bar | +| test.py:6:1:6:6 | After meth() | meth() | obj1 | foo | +| test.py:6:1:6:6 | After meth() | meth() | obj2 | bar | +| test.py:6:1:6:6 | After meth() | meth() | obj2 | foo | calls_lookup -| test.py:6:1:6:6 | ControlFlowNode for meth() | meth() | obj1 | foo | -| test.py:6:1:6:6 | ControlFlowNode for meth() | meth() | obj2 | bar | +| test.py:6:1:6:6 | After meth() | meth() | obj1 | foo | +| test.py:6:1:6:6 | After meth() | meth() | obj2 | bar | diff --git a/python/ql/test/library-tests/dataflow/module-initialization/localFlow.ql b/python/ql/test/library-tests/dataflow/module-initialization/localFlow.ql index e3ca2484e529..b71ef4fcf247 100644 --- a/python/ql/test/library-tests/dataflow/module-initialization/localFlow.ql +++ b/python/ql/test/library-tests/dataflow/module-initialization/localFlow.ql @@ -3,6 +3,7 @@ import python import utils.test.dataflow.FlowTest private import semmle.python.dataflow.new.internal.PrintNode private import semmle.python.dataflow.new.internal.DataFlowPrivate as DP +private import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl module ImportTimeLocalFlowTest implements FlowTestSig { string flowTag() { result = "importTimeFlow" } @@ -11,8 +12,9 @@ module ImportTimeLocalFlowTest implements FlowTestSig { nodeFrom.getLocation().getFile().getBaseName() = "multiphase.py" and // results are displayed next to `nodeTo`, so we need a line to write on nodeTo.getLocation().getStartLine() > 0 and - exists(GlobalSsaVariable g | - nodeTo.asCfgNode() = g.getDefinition().(EssaNodeDefinition).getDefiningNode() + exists(SsaImpl::EssaVariable g | + g.getSourceVariable().getVariable() instanceof GlobalVariable and + nodeTo.asCfgNode() = g.getDefinition().(SsaImpl::EssaNodeDefinition).getDefiningNode() ) and // nodeTo.asVar() instanceof GlobalSsaVariable and DP::PhaseDependentFlow::importTimeStep(nodeFrom, nodeTo) diff --git a/python/ql/test/library-tests/dataflow/regression/custom_dataflow.expected b/python/ql/test/library-tests/dataflow/regression/custom_dataflow.expected index 0ae109d52aea..2fdbbf2b85cc 100644 --- a/python/ql/test/library-tests/dataflow/regression/custom_dataflow.expected +++ b/python/ql/test/library-tests/dataflow/regression/custom_dataflow.expected @@ -1 +1,2 @@ -| test.py:126:13:126:25 | ControlFlowNode for CUSTOM_SOURCE | test.py:130:21:130:21 | ControlFlowNode for t | +| test.py:126:13:126:25 | CUSTOM_SOURCE | test.py:130:21:130:21 | t | +| test.py:136:13:136:25 | CUSTOM_SOURCE | test.py:140:23:140:23 | t | diff --git a/python/ql/test/library-tests/dataflow/regression/custom_dataflow.ql b/python/ql/test/library-tests/dataflow/regression/custom_dataflow.ql index 69cf6def9996..b89d2ddad230 100644 --- a/python/ql/test/library-tests/dataflow/regression/custom_dataflow.ql +++ b/python/ql/test/library-tests/dataflow/regression/custom_dataflow.ql @@ -8,14 +8,17 @@ */ import python +private import semmle.python.controlflow.internal.Cfg as Cfg import semmle.python.dataflow.new.DataFlow module CustomTestConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node node) { node.asCfgNode().(NameNode).getId() = "CUSTOM_SOURCE" } + predicate isSource(DataFlow::Node node) { + node.asCfgNode().(Cfg::NameNode).getId() = "CUSTOM_SOURCE" + } predicate isSink(DataFlow::Node node) { - exists(CallNode call | - call.getFunction().(NameNode).getId() in ["CUSTOM_SINK", "CUSTOM_SINK_F"] and + exists(Cfg::CallNode call | + call.getFunction().(Cfg::NameNode).getId() in ["CUSTOM_SINK", "CUSTOM_SINK_F"] and node.asCfgNode() = call.getAnArg() ) } diff --git a/python/ql/test/library-tests/dataflow/regression/dataflow.expected b/python/ql/test/library-tests/dataflow/regression/dataflow.expected index c8ffed514463..4591f33922d7 100644 --- a/python/ql/test/library-tests/dataflow/regression/dataflow.expected +++ b/python/ql/test/library-tests/dataflow/regression/dataflow.expected @@ -1,26 +1,28 @@ -| module.py:1:13:1:18 | ControlFlowNode for SOURCE | test.py:89:10:89:10 | ControlFlowNode for t | -| module.py:1:13:1:18 | ControlFlowNode for SOURCE | test.py:106:10:106:14 | ControlFlowNode for Attribute | -| module.py:1:13:1:18 | ControlFlowNode for SOURCE | test.py:111:10:111:12 | ControlFlowNode for Attribute | -| module.py:1:13:1:18 | ControlFlowNode for SOURCE | test.py:156:6:156:11 | ControlFlowNode for unsafe | -| module.py:6:12:6:17 | ControlFlowNode for SOURCE | test.py:101:10:101:10 | ControlFlowNode for t | -| test.py:3:10:3:15 | ControlFlowNode for SOURCE | test.py:3:10:3:15 | ControlFlowNode for SOURCE | -| test.py:6:9:6:14 | ControlFlowNode for SOURCE | test.py:7:10:7:10 | ControlFlowNode for s | -| test.py:10:12:10:17 | ControlFlowNode for SOURCE | test.py:13:10:13:12 | ControlFlowNode for arg | -| test.py:10:12:10:17 | ControlFlowNode for SOURCE | test.py:17:10:17:10 | ControlFlowNode for t | -| test.py:20:9:20:14 | ControlFlowNode for SOURCE | test.py:13:10:13:12 | ControlFlowNode for arg | -| test.py:37:13:37:18 | ControlFlowNode for SOURCE | test.py:41:14:41:14 | ControlFlowNode for t | -| test.py:62:13:62:18 | ControlFlowNode for SOURCE | test.py:13:10:13:12 | ControlFlowNode for arg | -| test.py:67:13:67:18 | ControlFlowNode for SOURCE | test.py:13:10:13:12 | ControlFlowNode for arg | -| test.py:76:9:76:14 | ControlFlowNode for SOURCE | test.py:78:10:78:10 | ControlFlowNode for t | -| test.py:128:13:128:18 | ControlFlowNode for SOURCE | test.py:132:14:132:14 | ControlFlowNode for t | -| test.py:159:10:159:15 | ControlFlowNode for SOURCE | test.py:160:14:160:14 | ControlFlowNode for t | -| test.py:163:9:163:14 | ControlFlowNode for SOURCE | test.py:165:12:165:12 | ControlFlowNode for s | -| test.py:178:9:178:14 | ControlFlowNode for SOURCE | test.py:180:14:180:14 | ControlFlowNode for t | -| test.py:178:9:178:14 | ControlFlowNode for SOURCE | test.py:182:16:182:16 | ControlFlowNode for t | -| test.py:178:9:178:14 | ControlFlowNode for SOURCE | test.py:184:16:184:16 | ControlFlowNode for t | -| test.py:178:9:178:14 | ControlFlowNode for SOURCE | test.py:186:14:186:14 | ControlFlowNode for t | -| test.py:195:9:195:14 | ControlFlowNode for SOURCE | test.py:197:14:197:14 | ControlFlowNode for t | -| test.py:195:9:195:14 | ControlFlowNode for SOURCE | test.py:199:14:199:14 | ControlFlowNode for t | -| test.py:202:10:202:15 | ControlFlowNode for SOURCE | test.py:204:14:204:14 | ControlFlowNode for i | -| test.py:202:10:202:15 | ControlFlowNode for SOURCE | test.py:205:10:205:10 | ControlFlowNode for i | -| test.py:208:12:208:17 | ControlFlowNode for SOURCE | test.py:214:14:214:14 | ControlFlowNode for x | +| module.py:1:13:1:18 | SOURCE | test.py:89:10:89:10 | t | +| module.py:1:13:1:18 | SOURCE | test.py:106:10:106:14 | After Attribute | +| module.py:1:13:1:18 | SOURCE | test.py:111:10:111:12 | After Attribute | +| module.py:1:13:1:18 | SOURCE | test.py:156:6:156:11 | unsafe | +| module.py:6:12:6:17 | SOURCE | test.py:101:10:101:10 | t | +| test.py:3:10:3:15 | SOURCE | test.py:3:10:3:15 | SOURCE | +| test.py:6:9:6:14 | SOURCE | test.py:7:10:7:10 | s | +| test.py:10:12:10:17 | SOURCE | test.py:13:10:13:12 | arg | +| test.py:10:12:10:17 | SOURCE | test.py:17:10:17:10 | t | +| test.py:20:9:20:14 | SOURCE | test.py:13:10:13:12 | arg | +| test.py:31:13:31:18 | SOURCE | test.py:33:16:33:16 | t | +| test.py:37:13:37:18 | SOURCE | test.py:41:14:41:14 | t | +| test.py:62:13:62:18 | SOURCE | test.py:13:10:13:12 | arg | +| test.py:67:13:67:18 | SOURCE | test.py:13:10:13:12 | arg | +| test.py:76:9:76:14 | SOURCE | test.py:78:10:78:10 | t | +| test.py:128:13:128:18 | SOURCE | test.py:132:14:132:14 | t | +| test.py:138:13:138:18 | SOURCE | test.py:142:16:142:16 | t | +| test.py:159:10:159:15 | SOURCE | test.py:160:14:160:14 | t | +| test.py:163:9:163:14 | SOURCE | test.py:165:12:165:12 | s | +| test.py:178:9:178:14 | SOURCE | test.py:180:14:180:14 | t | +| test.py:178:9:178:14 | SOURCE | test.py:182:16:182:16 | t | +| test.py:178:9:178:14 | SOURCE | test.py:184:16:184:16 | t | +| test.py:178:9:178:14 | SOURCE | test.py:186:14:186:14 | t | +| test.py:195:9:195:14 | SOURCE | test.py:197:14:197:14 | t | +| test.py:195:9:195:14 | SOURCE | test.py:199:14:199:14 | t | +| test.py:202:10:202:15 | SOURCE | test.py:204:14:204:14 | i | +| test.py:202:10:202:15 | SOURCE | test.py:205:10:205:10 | i | +| test.py:208:12:208:17 | SOURCE | test.py:214:14:214:14 | x | diff --git a/python/ql/test/library-tests/dataflow/strange-essaflow/testFlow.expected b/python/ql/test/library-tests/dataflow/strange-essaflow/testFlow.expected index bff38b71fc96..88315c9a13f5 100644 --- a/python/ql/test/library-tests/dataflow/strange-essaflow/testFlow.expected +++ b/python/ql/test/library-tests/dataflow/strange-essaflow/testFlow.expected @@ -1,6 +1,6 @@ os_import -| test.py:2:8:2:9 | ControlFlowNode for os | +| test.py:2:8:2:9 | os | flowstep jumpStep -| test.py:2:8:2:9 | ControlFlowNode for os | test.py:0:0:0:0 | ModuleVariableNode in Module test for os | +| test.py:2:8:2:9 | os | test.py:0:0:0:0 | ModuleVariableNode in Module test for os | essaFlowStep diff --git a/python/ql/test/library-tests/dataflow/strange-essaflow/testFlow.ql b/python/ql/test/library-tests/dataflow/strange-essaflow/testFlow.ql index 056e6ae815af..0f80fc3b89b0 100644 --- a/python/ql/test/library-tests/dataflow/strange-essaflow/testFlow.ql +++ b/python/ql/test/library-tests/dataflow/strange-essaflow/testFlow.ql @@ -1,11 +1,12 @@ import python import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.internal.DataFlowPrivate as DataFlowPrivate +private import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl /** Gets the `CfgNode` that holds the module imported by the fully qualified module name `name`. */ DataFlow::CfgNode module_import(string name) { - exists(Variable var, AssignmentDefinition def, Import imp, Alias alias | - var = def.getSourceVariable() and + exists(Variable var, SsaImpl::AssignmentDefinition def, Import imp, Alias alias | + var = def.getSourceVariable().getVariable() and result.getNode() = def.getDefiningNode() and alias = imp.getAName() and alias.getAsname() = var.getAStore() diff --git a/python/ql/test/library-tests/dataflow/summaries/TestSummaries.qll b/python/ql/test/library-tests/dataflow/summaries/TestSummaries.qll index 14d68455d621..87e1d7871010 100644 --- a/python/ql/test/library-tests/dataflow/summaries/TestSummaries.qll +++ b/python/ql/test/library-tests/dataflow/summaries/TestSummaries.qll @@ -2,6 +2,7 @@ overlay[local?] module; private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.FlowSummary private import semmle.python.ApiGraphs @@ -17,7 +18,7 @@ module RecursionGuard { RecursionGuard() { this = "RecursionGuard" } override DataFlow::CallCfgNode getACall() { - result.getFunction().asCfgNode().(NameNode).getId() = this and + result.getFunction().asCfgNode().(Cfg::NameNode).getId() = this and (TT::callStep(_, _) implies any()) } @@ -33,7 +34,7 @@ private class SummarizedCallableIdentity extends SummarizedCallable::Range { SummarizedCallableIdentity() { this = "identity" } override DataFlow::CallCfgNode getACall() { - result.getFunction().asCfgNode().(NameNode).getId() = this + result.getFunction().asCfgNode().(Cfg::NameNode).getId() = this } override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this } @@ -50,7 +51,7 @@ private class SummarizedCallableApplyLambda extends SummarizedCallable::Range { SummarizedCallableApplyLambda() { this = "apply_lambda" } override DataFlow::CallCfgNode getACall() { - result.getFunction().asCfgNode().(NameNode).getId() = this + result.getFunction().asCfgNode().(Cfg::NameNode).getId() = this } override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this } @@ -70,7 +71,7 @@ private class SummarizedCallableReversed extends SummarizedCallable::Range { SummarizedCallableReversed() { this = "list_reversed" } override DataFlow::CallCfgNode getACall() { - result.getFunction().asCfgNode().(NameNode).getId() = this + result.getFunction().asCfgNode().(Cfg::NameNode).getId() = this } override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this } @@ -86,7 +87,7 @@ private class SummarizedCallableMap extends SummarizedCallable::Range { SummarizedCallableMap() { this = "list_map" } override DataFlow::CallCfgNode getACall() { - result.getFunction().asCfgNode().(NameNode).getId() = this + result.getFunction().asCfgNode().(Cfg::NameNode).getId() = this } override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this } @@ -106,7 +107,7 @@ private class SummarizedCallableAppend extends SummarizedCallable::Range { SummarizedCallableAppend() { this = "append_to_list" } override DataFlow::CallCfgNode getACall() { - result.getFunction().asCfgNode().(NameNode).getId() = this + result.getFunction().asCfgNode().(Cfg::NameNode).getId() = this } override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this } diff --git a/python/ql/test/library-tests/dataflow/summaries/summaries.expected b/python/ql/test/library-tests/dataflow/summaries/summaries.expected index 4a97116f8cd1..535c449545a1 100644 --- a/python/ql/test/library-tests/dataflow/summaries/summaries.expected +++ b/python/ql/test/library-tests/dataflow/summaries/summaries.expected @@ -1,114 +1,114 @@ edges -| summaries.py:32:1:32:7 | ControlFlowNode for tainted | summaries.py:33:6:33:12 | ControlFlowNode for tainted | provenance | | -| summaries.py:32:11:32:26 | ControlFlowNode for identity() | summaries.py:32:1:32:7 | ControlFlowNode for tainted | provenance | | -| summaries.py:32:20:32:25 | ControlFlowNode for SOURCE | summaries.py:32:11:32:26 | ControlFlowNode for identity() | provenance | identity | -| summaries.py:36:1:36:14 | ControlFlowNode for tainted_lambda | summaries.py:37:6:37:19 | ControlFlowNode for tainted_lambda | provenance | | -| summaries.py:36:18:36:54 | ControlFlowNode for apply_lambda() | summaries.py:36:1:36:14 | ControlFlowNode for tainted_lambda | provenance | | -| summaries.py:36:38:36:38 | ControlFlowNode for x | summaries.py:36:41:36:45 | ControlFlowNode for BinaryExpr | provenance | | -| summaries.py:36:48:36:53 | ControlFlowNode for SOURCE | summaries.py:36:18:36:54 | ControlFlowNode for apply_lambda() | provenance | apply_lambda | -| summaries.py:36:48:36:53 | ControlFlowNode for SOURCE | summaries.py:36:38:36:38 | ControlFlowNode for x | provenance | apply_lambda | -| summaries.py:44:1:44:12 | ControlFlowNode for tainted_list | summaries.py:45:6:45:20 | ControlFlowNode for Subscript | provenance | | -| summaries.py:44:1:44:12 | ControlFlowNode for tainted_list [List element] | summaries.py:45:6:45:17 | ControlFlowNode for tainted_list [List element] | provenance | | -| summaries.py:44:16:44:33 | ControlFlowNode for reversed() | summaries.py:44:1:44:12 | ControlFlowNode for tainted_list | provenance | | -| summaries.py:44:16:44:33 | ControlFlowNode for reversed() [List element] | summaries.py:44:1:44:12 | ControlFlowNode for tainted_list [List element] | provenance | | -| summaries.py:44:25:44:32 | ControlFlowNode for List | summaries.py:44:16:44:33 | ControlFlowNode for reversed() | provenance | builtins.reversed | -| summaries.py:44:25:44:32 | ControlFlowNode for List [List element] | summaries.py:44:16:44:33 | ControlFlowNode for reversed() [List element] | provenance | builtins.reversed | -| summaries.py:44:26:44:31 | ControlFlowNode for SOURCE | summaries.py:44:25:44:32 | ControlFlowNode for List | provenance | | -| summaries.py:44:26:44:31 | ControlFlowNode for SOURCE | summaries.py:44:25:44:32 | ControlFlowNode for List [List element] | provenance | | -| summaries.py:45:6:45:17 | ControlFlowNode for tainted_list [List element] | summaries.py:45:6:45:20 | ControlFlowNode for Subscript | provenance | | -| summaries.py:48:15:48:15 | ControlFlowNode for x | summaries.py:49:12:49:18 | ControlFlowNode for BinaryExpr | provenance | | -| summaries.py:51:1:51:14 | ControlFlowNode for tainted_mapped [List element] | summaries.py:52:6:52:19 | ControlFlowNode for tainted_mapped [List element] | provenance | | -| summaries.py:51:18:51:46 | ControlFlowNode for list_map() [List element] | summaries.py:51:1:51:14 | ControlFlowNode for tainted_mapped [List element] | provenance | | -| summaries.py:51:38:51:45 | ControlFlowNode for List [List element] | summaries.py:48:15:48:15 | ControlFlowNode for x | provenance | list_map | -| summaries.py:51:38:51:45 | ControlFlowNode for List [List element] | summaries.py:51:18:51:46 | ControlFlowNode for list_map() [List element] | provenance | list_map | -| summaries.py:51:39:51:44 | ControlFlowNode for SOURCE | summaries.py:51:38:51:45 | ControlFlowNode for List [List element] | provenance | | -| summaries.py:52:6:52:19 | ControlFlowNode for tainted_mapped [List element] | summaries.py:52:6:52:22 | ControlFlowNode for Subscript | provenance | | -| summaries.py:54:23:54:23 | ControlFlowNode for x | summaries.py:55:12:55:12 | ControlFlowNode for x | provenance | | -| summaries.py:57:1:57:23 | ControlFlowNode for tainted_mapped_explicit [List element] | summaries.py:58:6:58:28 | ControlFlowNode for tainted_mapped_explicit [List element] | provenance | | -| summaries.py:57:27:57:63 | ControlFlowNode for list_map() [List element] | summaries.py:57:1:57:23 | ControlFlowNode for tainted_mapped_explicit [List element] | provenance | | -| summaries.py:57:55:57:62 | ControlFlowNode for List [List element] | summaries.py:54:23:54:23 | ControlFlowNode for x | provenance | list_map | -| summaries.py:57:55:57:62 | ControlFlowNode for List [List element] | summaries.py:57:27:57:63 | ControlFlowNode for list_map() [List element] | provenance | list_map | -| summaries.py:57:56:57:61 | ControlFlowNode for SOURCE | summaries.py:57:55:57:62 | ControlFlowNode for List [List element] | provenance | | -| summaries.py:58:6:58:28 | ControlFlowNode for tainted_mapped_explicit [List element] | summaries.py:58:6:58:31 | ControlFlowNode for Subscript | provenance | | -| summaries.py:60:1:60:22 | ControlFlowNode for tainted_mapped_summary [List element] | summaries.py:61:6:61:27 | ControlFlowNode for tainted_mapped_summary [List element] | provenance | | -| summaries.py:60:26:60:53 | ControlFlowNode for list_map() [List element] | summaries.py:60:1:60:22 | ControlFlowNode for tainted_mapped_summary [List element] | provenance | | -| summaries.py:60:45:60:52 | ControlFlowNode for List [List element] | summaries.py:60:26:60:53 | ControlFlowNode for list_map() [List element] | provenance | list_map | -| summaries.py:60:46:60:51 | ControlFlowNode for SOURCE | summaries.py:60:45:60:52 | ControlFlowNode for List [List element] | provenance | | -| summaries.py:61:6:61:27 | ControlFlowNode for tainted_mapped_summary [List element] | summaries.py:61:6:61:30 | ControlFlowNode for Subscript | provenance | | -| summaries.py:63:1:63:12 | ControlFlowNode for tainted_list [List element] | summaries.py:64:6:64:17 | ControlFlowNode for tainted_list [List element] | provenance | | -| summaries.py:63:16:63:41 | ControlFlowNode for append_to_list() [List element] | summaries.py:63:1:63:12 | ControlFlowNode for tainted_list [List element] | provenance | | -| summaries.py:63:35:63:40 | ControlFlowNode for SOURCE | summaries.py:63:16:63:41 | ControlFlowNode for append_to_list() [List element] | provenance | append_to_list | -| summaries.py:64:6:64:17 | ControlFlowNode for tainted_list [List element] | summaries.py:64:6:64:20 | ControlFlowNode for Subscript | provenance | | -| summaries.py:67:1:67:18 | ControlFlowNode for tainted_resultlist | summaries.py:68:6:68:26 | ControlFlowNode for Subscript | provenance | | -| summaries.py:67:1:67:18 | ControlFlowNode for tainted_resultlist [List element] | summaries.py:68:6:68:23 | ControlFlowNode for tainted_resultlist [List element] | provenance | | -| summaries.py:67:22:67:39 | ControlFlowNode for json_loads() [List element] | summaries.py:67:1:67:18 | ControlFlowNode for tainted_resultlist [List element] | provenance | | -| summaries.py:67:33:67:38 | ControlFlowNode for SOURCE | summaries.py:67:1:67:18 | ControlFlowNode for tainted_resultlist | provenance | Decoding-JSON | -| summaries.py:67:33:67:38 | ControlFlowNode for SOURCE | summaries.py:67:22:67:39 | ControlFlowNode for json_loads() [List element] | provenance | json.loads | -| summaries.py:68:6:68:23 | ControlFlowNode for tainted_resultlist [List element] | summaries.py:68:6:68:26 | ControlFlowNode for Subscript | provenance | | +| summaries.py:32:1:32:7 | tainted | summaries.py:33:6:33:12 | tainted | provenance | | +| summaries.py:32:11:32:26 | After identity() | summaries.py:32:1:32:7 | tainted | provenance | | +| summaries.py:32:20:32:25 | SOURCE | summaries.py:32:11:32:26 | After identity() | provenance | identity | +| summaries.py:36:1:36:14 | tainted_lambda | summaries.py:37:6:37:19 | tainted_lambda | provenance | | +| summaries.py:36:18:36:54 | After apply_lambda() | summaries.py:36:1:36:14 | tainted_lambda | provenance | | +| summaries.py:36:38:36:38 | x | summaries.py:36:41:36:45 | After BinaryExpr | provenance | | +| summaries.py:36:48:36:53 | SOURCE | summaries.py:36:18:36:54 | After apply_lambda() | provenance | apply_lambda | +| summaries.py:36:48:36:53 | SOURCE | summaries.py:36:38:36:38 | x | provenance | apply_lambda | +| summaries.py:44:1:44:12 | tainted_list | summaries.py:45:6:45:20 | After Subscript | provenance | | +| summaries.py:44:1:44:12 | tainted_list [List element] | summaries.py:45:6:45:17 | tainted_list [List element] | provenance | | +| summaries.py:44:16:44:33 | After reversed() | summaries.py:44:1:44:12 | tainted_list | provenance | | +| summaries.py:44:16:44:33 | After reversed() [List element] | summaries.py:44:1:44:12 | tainted_list [List element] | provenance | | +| summaries.py:44:25:44:32 | After List | summaries.py:44:16:44:33 | After reversed() | provenance | builtins.reversed | +| summaries.py:44:25:44:32 | After List [List element] | summaries.py:44:16:44:33 | After reversed() [List element] | provenance | builtins.reversed | +| summaries.py:44:26:44:31 | SOURCE | summaries.py:44:25:44:32 | After List | provenance | | +| summaries.py:44:26:44:31 | SOURCE | summaries.py:44:25:44:32 | After List [List element] | provenance | | +| summaries.py:45:6:45:17 | tainted_list [List element] | summaries.py:45:6:45:20 | After Subscript | provenance | | +| summaries.py:48:15:48:15 | x | summaries.py:49:12:49:18 | After BinaryExpr | provenance | | +| summaries.py:51:1:51:14 | tainted_mapped [List element] | summaries.py:52:6:52:19 | tainted_mapped [List element] | provenance | | +| summaries.py:51:18:51:46 | After list_map() [List element] | summaries.py:51:1:51:14 | tainted_mapped [List element] | provenance | | +| summaries.py:51:38:51:45 | After List [List element] | summaries.py:48:15:48:15 | x | provenance | list_map | +| summaries.py:51:38:51:45 | After List [List element] | summaries.py:51:18:51:46 | After list_map() [List element] | provenance | list_map | +| summaries.py:51:39:51:44 | SOURCE | summaries.py:51:38:51:45 | After List [List element] | provenance | | +| summaries.py:52:6:52:19 | tainted_mapped [List element] | summaries.py:52:6:52:22 | After Subscript | provenance | | +| summaries.py:54:23:54:23 | x | summaries.py:55:12:55:12 | x | provenance | | +| summaries.py:57:1:57:23 | tainted_mapped_explicit [List element] | summaries.py:58:6:58:28 | tainted_mapped_explicit [List element] | provenance | | +| summaries.py:57:27:57:63 | After list_map() [List element] | summaries.py:57:1:57:23 | tainted_mapped_explicit [List element] | provenance | | +| summaries.py:57:55:57:62 | After List [List element] | summaries.py:54:23:54:23 | x | provenance | list_map | +| summaries.py:57:55:57:62 | After List [List element] | summaries.py:57:27:57:63 | After list_map() [List element] | provenance | list_map | +| summaries.py:57:56:57:61 | SOURCE | summaries.py:57:55:57:62 | After List [List element] | provenance | | +| summaries.py:58:6:58:28 | tainted_mapped_explicit [List element] | summaries.py:58:6:58:31 | After Subscript | provenance | | +| summaries.py:60:1:60:22 | tainted_mapped_summary [List element] | summaries.py:61:6:61:27 | tainted_mapped_summary [List element] | provenance | | +| summaries.py:60:26:60:53 | After list_map() [List element] | summaries.py:60:1:60:22 | tainted_mapped_summary [List element] | provenance | | +| summaries.py:60:45:60:52 | After List [List element] | summaries.py:60:26:60:53 | After list_map() [List element] | provenance | list_map | +| summaries.py:60:46:60:51 | SOURCE | summaries.py:60:45:60:52 | After List [List element] | provenance | | +| summaries.py:61:6:61:27 | tainted_mapped_summary [List element] | summaries.py:61:6:61:30 | After Subscript | provenance | | +| summaries.py:63:1:63:12 | tainted_list [List element] | summaries.py:64:6:64:17 | tainted_list [List element] | provenance | | +| summaries.py:63:16:63:41 | After append_to_list() [List element] | summaries.py:63:1:63:12 | tainted_list [List element] | provenance | | +| summaries.py:63:35:63:40 | SOURCE | summaries.py:63:16:63:41 | After append_to_list() [List element] | provenance | append_to_list | +| summaries.py:64:6:64:17 | tainted_list [List element] | summaries.py:64:6:64:20 | After Subscript | provenance | | +| summaries.py:67:1:67:18 | tainted_resultlist | summaries.py:68:6:68:26 | After Subscript | provenance | | +| summaries.py:67:1:67:18 | tainted_resultlist [List element] | summaries.py:68:6:68:23 | tainted_resultlist [List element] | provenance | | +| summaries.py:67:22:67:39 | After json_loads() [List element] | summaries.py:67:1:67:18 | tainted_resultlist [List element] | provenance | | +| summaries.py:67:33:67:38 | SOURCE | summaries.py:67:1:67:18 | tainted_resultlist | provenance | Decoding-JSON | +| summaries.py:67:33:67:38 | SOURCE | summaries.py:67:22:67:39 | After json_loads() [List element] | provenance | json.loads | +| summaries.py:68:6:68:23 | tainted_resultlist [List element] | summaries.py:68:6:68:26 | After Subscript | provenance | | nodes -| summaries.py:32:1:32:7 | ControlFlowNode for tainted | semmle.label | ControlFlowNode for tainted | -| summaries.py:32:11:32:26 | ControlFlowNode for identity() | semmle.label | ControlFlowNode for identity() | -| summaries.py:32:20:32:25 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE | -| summaries.py:33:6:33:12 | ControlFlowNode for tainted | semmle.label | ControlFlowNode for tainted | -| summaries.py:36:1:36:14 | ControlFlowNode for tainted_lambda | semmle.label | ControlFlowNode for tainted_lambda | -| summaries.py:36:18:36:54 | ControlFlowNode for apply_lambda() | semmle.label | ControlFlowNode for apply_lambda() | -| summaries.py:36:38:36:38 | ControlFlowNode for x | semmle.label | ControlFlowNode for x | -| summaries.py:36:41:36:45 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | -| summaries.py:36:48:36:53 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE | -| summaries.py:37:6:37:19 | ControlFlowNode for tainted_lambda | semmle.label | ControlFlowNode for tainted_lambda | -| summaries.py:44:1:44:12 | ControlFlowNode for tainted_list | semmle.label | ControlFlowNode for tainted_list | -| summaries.py:44:1:44:12 | ControlFlowNode for tainted_list [List element] | semmle.label | ControlFlowNode for tainted_list [List element] | -| summaries.py:44:16:44:33 | ControlFlowNode for reversed() | semmle.label | ControlFlowNode for reversed() | -| summaries.py:44:16:44:33 | ControlFlowNode for reversed() [List element] | semmle.label | ControlFlowNode for reversed() [List element] | -| summaries.py:44:25:44:32 | ControlFlowNode for List | semmle.label | ControlFlowNode for List | -| summaries.py:44:25:44:32 | ControlFlowNode for List [List element] | semmle.label | ControlFlowNode for List [List element] | -| summaries.py:44:26:44:31 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE | -| summaries.py:45:6:45:17 | ControlFlowNode for tainted_list [List element] | semmle.label | ControlFlowNode for tainted_list [List element] | -| summaries.py:45:6:45:20 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | -| summaries.py:48:15:48:15 | ControlFlowNode for x | semmle.label | ControlFlowNode for x | -| summaries.py:49:12:49:18 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | -| summaries.py:51:1:51:14 | ControlFlowNode for tainted_mapped [List element] | semmle.label | ControlFlowNode for tainted_mapped [List element] | -| summaries.py:51:18:51:46 | ControlFlowNode for list_map() [List element] | semmle.label | ControlFlowNode for list_map() [List element] | -| summaries.py:51:38:51:45 | ControlFlowNode for List [List element] | semmle.label | ControlFlowNode for List [List element] | -| summaries.py:51:39:51:44 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE | -| summaries.py:52:6:52:19 | ControlFlowNode for tainted_mapped [List element] | semmle.label | ControlFlowNode for tainted_mapped [List element] | -| summaries.py:52:6:52:22 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | -| summaries.py:54:23:54:23 | ControlFlowNode for x | semmle.label | ControlFlowNode for x | -| summaries.py:55:12:55:12 | ControlFlowNode for x | semmle.label | ControlFlowNode for x | -| summaries.py:57:1:57:23 | ControlFlowNode for tainted_mapped_explicit [List element] | semmle.label | ControlFlowNode for tainted_mapped_explicit [List element] | -| summaries.py:57:27:57:63 | ControlFlowNode for list_map() [List element] | semmle.label | ControlFlowNode for list_map() [List element] | -| summaries.py:57:55:57:62 | ControlFlowNode for List [List element] | semmle.label | ControlFlowNode for List [List element] | -| summaries.py:57:56:57:61 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE | -| summaries.py:58:6:58:28 | ControlFlowNode for tainted_mapped_explicit [List element] | semmle.label | ControlFlowNode for tainted_mapped_explicit [List element] | -| summaries.py:58:6:58:31 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | -| summaries.py:60:1:60:22 | ControlFlowNode for tainted_mapped_summary [List element] | semmle.label | ControlFlowNode for tainted_mapped_summary [List element] | -| summaries.py:60:26:60:53 | ControlFlowNode for list_map() [List element] | semmle.label | ControlFlowNode for list_map() [List element] | -| summaries.py:60:45:60:52 | ControlFlowNode for List [List element] | semmle.label | ControlFlowNode for List [List element] | -| summaries.py:60:46:60:51 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE | -| summaries.py:61:6:61:27 | ControlFlowNode for tainted_mapped_summary [List element] | semmle.label | ControlFlowNode for tainted_mapped_summary [List element] | -| summaries.py:61:6:61:30 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | -| summaries.py:63:1:63:12 | ControlFlowNode for tainted_list [List element] | semmle.label | ControlFlowNode for tainted_list [List element] | -| summaries.py:63:16:63:41 | ControlFlowNode for append_to_list() [List element] | semmle.label | ControlFlowNode for append_to_list() [List element] | -| summaries.py:63:35:63:40 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE | -| summaries.py:64:6:64:17 | ControlFlowNode for tainted_list [List element] | semmle.label | ControlFlowNode for tainted_list [List element] | -| summaries.py:64:6:64:20 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | -| summaries.py:67:1:67:18 | ControlFlowNode for tainted_resultlist | semmle.label | ControlFlowNode for tainted_resultlist | -| summaries.py:67:1:67:18 | ControlFlowNode for tainted_resultlist [List element] | semmle.label | ControlFlowNode for tainted_resultlist [List element] | -| summaries.py:67:22:67:39 | ControlFlowNode for json_loads() [List element] | semmle.label | ControlFlowNode for json_loads() [List element] | -| summaries.py:67:33:67:38 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE | -| summaries.py:68:6:68:23 | ControlFlowNode for tainted_resultlist [List element] | semmle.label | ControlFlowNode for tainted_resultlist [List element] | -| summaries.py:68:6:68:26 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| summaries.py:32:1:32:7 | tainted | semmle.label | tainted | +| summaries.py:32:11:32:26 | After identity() | semmle.label | After identity() | +| summaries.py:32:20:32:25 | SOURCE | semmle.label | SOURCE | +| summaries.py:33:6:33:12 | tainted | semmle.label | tainted | +| summaries.py:36:1:36:14 | tainted_lambda | semmle.label | tainted_lambda | +| summaries.py:36:18:36:54 | After apply_lambda() | semmle.label | After apply_lambda() | +| summaries.py:36:38:36:38 | x | semmle.label | x | +| summaries.py:36:41:36:45 | After BinaryExpr | semmle.label | After BinaryExpr | +| summaries.py:36:48:36:53 | SOURCE | semmle.label | SOURCE | +| summaries.py:37:6:37:19 | tainted_lambda | semmle.label | tainted_lambda | +| summaries.py:44:1:44:12 | tainted_list | semmle.label | tainted_list | +| summaries.py:44:1:44:12 | tainted_list [List element] | semmle.label | tainted_list [List element] | +| summaries.py:44:16:44:33 | After reversed() | semmle.label | After reversed() | +| summaries.py:44:16:44:33 | After reversed() [List element] | semmle.label | After reversed() [List element] | +| summaries.py:44:25:44:32 | After List | semmle.label | After List | +| summaries.py:44:25:44:32 | After List [List element] | semmle.label | After List [List element] | +| summaries.py:44:26:44:31 | SOURCE | semmle.label | SOURCE | +| summaries.py:45:6:45:17 | tainted_list [List element] | semmle.label | tainted_list [List element] | +| summaries.py:45:6:45:20 | After Subscript | semmle.label | After Subscript | +| summaries.py:48:15:48:15 | x | semmle.label | x | +| summaries.py:49:12:49:18 | After BinaryExpr | semmle.label | After BinaryExpr | +| summaries.py:51:1:51:14 | tainted_mapped [List element] | semmle.label | tainted_mapped [List element] | +| summaries.py:51:18:51:46 | After list_map() [List element] | semmle.label | After list_map() [List element] | +| summaries.py:51:38:51:45 | After List [List element] | semmle.label | After List [List element] | +| summaries.py:51:39:51:44 | SOURCE | semmle.label | SOURCE | +| summaries.py:52:6:52:19 | tainted_mapped [List element] | semmle.label | tainted_mapped [List element] | +| summaries.py:52:6:52:22 | After Subscript | semmle.label | After Subscript | +| summaries.py:54:23:54:23 | x | semmle.label | x | +| summaries.py:55:12:55:12 | x | semmle.label | x | +| summaries.py:57:1:57:23 | tainted_mapped_explicit [List element] | semmle.label | tainted_mapped_explicit [List element] | +| summaries.py:57:27:57:63 | After list_map() [List element] | semmle.label | After list_map() [List element] | +| summaries.py:57:55:57:62 | After List [List element] | semmle.label | After List [List element] | +| summaries.py:57:56:57:61 | SOURCE | semmle.label | SOURCE | +| summaries.py:58:6:58:28 | tainted_mapped_explicit [List element] | semmle.label | tainted_mapped_explicit [List element] | +| summaries.py:58:6:58:31 | After Subscript | semmle.label | After Subscript | +| summaries.py:60:1:60:22 | tainted_mapped_summary [List element] | semmle.label | tainted_mapped_summary [List element] | +| summaries.py:60:26:60:53 | After list_map() [List element] | semmle.label | After list_map() [List element] | +| summaries.py:60:45:60:52 | After List [List element] | semmle.label | After List [List element] | +| summaries.py:60:46:60:51 | SOURCE | semmle.label | SOURCE | +| summaries.py:61:6:61:27 | tainted_mapped_summary [List element] | semmle.label | tainted_mapped_summary [List element] | +| summaries.py:61:6:61:30 | After Subscript | semmle.label | After Subscript | +| summaries.py:63:1:63:12 | tainted_list [List element] | semmle.label | tainted_list [List element] | +| summaries.py:63:16:63:41 | After append_to_list() [List element] | semmle.label | After append_to_list() [List element] | +| summaries.py:63:35:63:40 | SOURCE | semmle.label | SOURCE | +| summaries.py:64:6:64:17 | tainted_list [List element] | semmle.label | tainted_list [List element] | +| summaries.py:64:6:64:20 | After Subscript | semmle.label | After Subscript | +| summaries.py:67:1:67:18 | tainted_resultlist | semmle.label | tainted_resultlist | +| summaries.py:67:1:67:18 | tainted_resultlist [List element] | semmle.label | tainted_resultlist [List element] | +| summaries.py:67:22:67:39 | After json_loads() [List element] | semmle.label | After json_loads() [List element] | +| summaries.py:67:33:67:38 | SOURCE | semmle.label | SOURCE | +| summaries.py:68:6:68:23 | tainted_resultlist [List element] | semmle.label | tainted_resultlist [List element] | +| summaries.py:68:6:68:26 | After Subscript | semmle.label | After Subscript | subpaths -| summaries.py:36:48:36:53 | ControlFlowNode for SOURCE | summaries.py:36:38:36:38 | ControlFlowNode for x | summaries.py:36:41:36:45 | ControlFlowNode for BinaryExpr | summaries.py:36:18:36:54 | ControlFlowNode for apply_lambda() | -| summaries.py:51:38:51:45 | ControlFlowNode for List [List element] | summaries.py:48:15:48:15 | ControlFlowNode for x | summaries.py:49:12:49:18 | ControlFlowNode for BinaryExpr | summaries.py:51:18:51:46 | ControlFlowNode for list_map() [List element] | -| summaries.py:57:55:57:62 | ControlFlowNode for List [List element] | summaries.py:54:23:54:23 | ControlFlowNode for x | summaries.py:55:12:55:12 | ControlFlowNode for x | summaries.py:57:27:57:63 | ControlFlowNode for list_map() [List element] | +| summaries.py:36:48:36:53 | SOURCE | summaries.py:36:38:36:38 | x | summaries.py:36:41:36:45 | After BinaryExpr | summaries.py:36:18:36:54 | After apply_lambda() | +| summaries.py:51:38:51:45 | After List [List element] | summaries.py:48:15:48:15 | x | summaries.py:49:12:49:18 | After BinaryExpr | summaries.py:51:18:51:46 | After list_map() [List element] | +| summaries.py:57:55:57:62 | After List [List element] | summaries.py:54:23:54:23 | x | summaries.py:55:12:55:12 | x | summaries.py:57:27:57:63 | After list_map() [List element] | invalidSpecComponent #select -| summaries.py:33:6:33:12 | ControlFlowNode for tainted | summaries.py:32:20:32:25 | ControlFlowNode for SOURCE | summaries.py:33:6:33:12 | ControlFlowNode for tainted | $@ | summaries.py:32:20:32:25 | ControlFlowNode for SOURCE | ControlFlowNode for SOURCE | -| summaries.py:37:6:37:19 | ControlFlowNode for tainted_lambda | summaries.py:36:48:36:53 | ControlFlowNode for SOURCE | summaries.py:37:6:37:19 | ControlFlowNode for tainted_lambda | $@ | summaries.py:36:48:36:53 | ControlFlowNode for SOURCE | ControlFlowNode for SOURCE | -| summaries.py:45:6:45:20 | ControlFlowNode for Subscript | summaries.py:44:26:44:31 | ControlFlowNode for SOURCE | summaries.py:45:6:45:20 | ControlFlowNode for Subscript | $@ | summaries.py:44:26:44:31 | ControlFlowNode for SOURCE | ControlFlowNode for SOURCE | -| summaries.py:52:6:52:22 | ControlFlowNode for Subscript | summaries.py:51:39:51:44 | ControlFlowNode for SOURCE | summaries.py:52:6:52:22 | ControlFlowNode for Subscript | $@ | summaries.py:51:39:51:44 | ControlFlowNode for SOURCE | ControlFlowNode for SOURCE | -| summaries.py:58:6:58:31 | ControlFlowNode for Subscript | summaries.py:57:56:57:61 | ControlFlowNode for SOURCE | summaries.py:58:6:58:31 | ControlFlowNode for Subscript | $@ | summaries.py:57:56:57:61 | ControlFlowNode for SOURCE | ControlFlowNode for SOURCE | -| summaries.py:61:6:61:30 | ControlFlowNode for Subscript | summaries.py:60:46:60:51 | ControlFlowNode for SOURCE | summaries.py:61:6:61:30 | ControlFlowNode for Subscript | $@ | summaries.py:60:46:60:51 | ControlFlowNode for SOURCE | ControlFlowNode for SOURCE | -| summaries.py:64:6:64:20 | ControlFlowNode for Subscript | summaries.py:63:35:63:40 | ControlFlowNode for SOURCE | summaries.py:64:6:64:20 | ControlFlowNode for Subscript | $@ | summaries.py:63:35:63:40 | ControlFlowNode for SOURCE | ControlFlowNode for SOURCE | -| summaries.py:68:6:68:26 | ControlFlowNode for Subscript | summaries.py:67:33:67:38 | ControlFlowNode for SOURCE | summaries.py:68:6:68:26 | ControlFlowNode for Subscript | $@ | summaries.py:67:33:67:38 | ControlFlowNode for SOURCE | ControlFlowNode for SOURCE | +| summaries.py:33:6:33:12 | tainted | summaries.py:32:20:32:25 | SOURCE | summaries.py:33:6:33:12 | tainted | $@ | summaries.py:32:20:32:25 | SOURCE | SOURCE | +| summaries.py:37:6:37:19 | tainted_lambda | summaries.py:36:48:36:53 | SOURCE | summaries.py:37:6:37:19 | tainted_lambda | $@ | summaries.py:36:48:36:53 | SOURCE | SOURCE | +| summaries.py:45:6:45:20 | After Subscript | summaries.py:44:26:44:31 | SOURCE | summaries.py:45:6:45:20 | After Subscript | $@ | summaries.py:44:26:44:31 | SOURCE | SOURCE | +| summaries.py:52:6:52:22 | After Subscript | summaries.py:51:39:51:44 | SOURCE | summaries.py:52:6:52:22 | After Subscript | $@ | summaries.py:51:39:51:44 | SOURCE | SOURCE | +| summaries.py:58:6:58:31 | After Subscript | summaries.py:57:56:57:61 | SOURCE | summaries.py:58:6:58:31 | After Subscript | $@ | summaries.py:57:56:57:61 | SOURCE | SOURCE | +| summaries.py:61:6:61:30 | After Subscript | summaries.py:60:46:60:51 | SOURCE | summaries.py:61:6:61:30 | After Subscript | $@ | summaries.py:60:46:60:51 | SOURCE | SOURCE | +| summaries.py:64:6:64:20 | After Subscript | summaries.py:63:35:63:40 | SOURCE | summaries.py:64:6:64:20 | After Subscript | $@ | summaries.py:63:35:63:40 | SOURCE | SOURCE | +| summaries.py:68:6:68:26 | After Subscript | summaries.py:67:33:67:38 | SOURCE | summaries.py:68:6:68:26 | After Subscript | $@ | summaries.py:67:33:67:38 | SOURCE | SOURCE | diff --git a/python/ql/test/library-tests/dataflow/tainttracking/TestTaintLib.qll b/python/ql/test/library-tests/dataflow/tainttracking/TestTaintLib.qll index 67a9f576cc75..f46f08aa509c 100644 --- a/python/ql/test/library-tests/dataflow/tainttracking/TestTaintLib.qll +++ b/python/ql/test/library-tests/dataflow/tainttracking/TestTaintLib.qll @@ -1,4 +1,5 @@ import python +private import semmle.python.controlflow.internal.Cfg as Cfg import semmle.python.dataflow.new.TaintTracking import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.internal.PrintNode @@ -6,20 +7,20 @@ private import semmle.python.dataflow.new.internal.PrintNode module TestTaintTrackingConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { // Standard sources - source.(DataFlow::CfgNode).getNode().(NameNode).getId() in [ + source.(DataFlow::CfgNode).getNode().(Cfg::NameNode).getId() in [ "TAINTED_STRING", "TAINTED_BYTES", "TAINTED_LIST", "TAINTED_DICT" ] or // User defined sources - exists(CallNode call | - call.getFunction().(NameNode).getId() = "taint" and + exists(Cfg::CallNode call | + call.getFunction().(Cfg::NameNode).getId() = "taint" and source.(DataFlow::CfgNode).getNode() = call.getAnArg() ) } predicate isSink(DataFlow::Node sink) { - exists(CallNode call | - call.getFunction().(NameNode).getId() in ["ensure_tainted", "ensure_not_tainted"] and + exists(Cfg::CallNode call | + call.getFunction().(Cfg::NameNode).getId() in ["ensure_tainted", "ensure_not_tainted"] and sink.(DataFlow::CfgNode).getNode() = call.getAnArg() ) } diff --git a/python/ql/test/library-tests/dataflow/tainttracking/basic/GlobalTaintTracking.expected b/python/ql/test/library-tests/dataflow/tainttracking/basic/GlobalTaintTracking.expected index 23bb0fcce7a4..9d1b4cb7c72a 100644 --- a/python/ql/test/library-tests/dataflow/tainttracking/basic/GlobalTaintTracking.expected +++ b/python/ql/test/library-tests/dataflow/tainttracking/basic/GlobalTaintTracking.expected @@ -1,2 +1,2 @@ -| test.py:3:11:3:16 | ControlFlowNode for SOURCE | test.py:4:6:4:12 | ControlFlowNode for tainted | -| test.py:7:20:7:25 | ControlFlowNode for SOURCE | test.py:8:10:8:21 | ControlFlowNode for also_tainted | +| test.py:3:11:3:16 | SOURCE | test.py:4:6:4:12 | tainted | +| test.py:7:20:7:25 | SOURCE | test.py:8:10:8:21 | also_tainted | diff --git a/python/ql/test/library-tests/dataflow/tainttracking/basic/GlobalTaintTracking.ql b/python/ql/test/library-tests/dataflow/tainttracking/basic/GlobalTaintTracking.ql index 1fd0a9600b3a..710cd61ac262 100644 --- a/python/ql/test/library-tests/dataflow/tainttracking/basic/GlobalTaintTracking.ql +++ b/python/ql/test/library-tests/dataflow/tainttracking/basic/GlobalTaintTracking.ql @@ -1,15 +1,16 @@ import python +private import semmle.python.controlflow.internal.Cfg as Cfg import semmle.python.dataflow.new.TaintTracking import semmle.python.dataflow.new.DataFlow module TestTaintTrackingConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { - source.(DataFlow::CfgNode).getNode().(NameNode).getId() = "SOURCE" + source.(DataFlow::CfgNode).getNode().(Cfg::NameNode).getId() = "SOURCE" } predicate isSink(DataFlow::Node sink) { - exists(CallNode call | - call.getFunction().(NameNode).getId() = "SINK" and + exists(Cfg::CallNode call | + call.getFunction().(Cfg::NameNode).getId() = "SINK" and sink.(DataFlow::CfgNode).getNode() = call.getAnArg() ) } diff --git a/python/ql/test/library-tests/dataflow/tainttracking/basic/LocalTaintStep.expected b/python/ql/test/library-tests/dataflow/tainttracking/basic/LocalTaintStep.expected index b2b151f6dedb..1b6d2d2cbd4b 100644 --- a/python/ql/test/library-tests/dataflow/tainttracking/basic/LocalTaintStep.expected +++ b/python/ql/test/library-tests/dataflow/tainttracking/basic/LocalTaintStep.expected @@ -1,5 +1,5 @@ -| test.py:3:1:3:7 | ControlFlowNode for tainted | test.py:4:6:4:12 | ControlFlowNode for tainted | -| test.py:3:11:3:16 | ControlFlowNode for SOURCE | test.py:3:1:3:7 | ControlFlowNode for tainted | -| test.py:6:1:6:11 | ControlFlowNode for FunctionExpr | test.py:6:5:6:8 | ControlFlowNode for func | -| test.py:7:5:7:16 | ControlFlowNode for also_tainted | test.py:8:10:8:21 | ControlFlowNode for also_tainted | -| test.py:7:20:7:25 | ControlFlowNode for SOURCE | test.py:7:5:7:16 | ControlFlowNode for also_tainted | +| test.py:3:1:3:7 | tainted | test.py:4:6:4:12 | tainted | +| test.py:3:11:3:16 | SOURCE | test.py:3:1:3:7 | tainted | +| test.py:6:1:6:11 | FunctionExpr | test.py:6:5:6:8 | func | +| test.py:7:5:7:16 | also_tainted | test.py:8:10:8:21 | also_tainted | +| test.py:7:20:7:25 | SOURCE | test.py:7:5:7:16 | also_tainted | diff --git a/python/ql/test/library-tests/dataflow/tainttracking/customSanitizer/InlineTaintTest.expected b/python/ql/test/library-tests/dataflow/tainttracking/customSanitizer/InlineTaintTest.expected index 89849279d446..f981b3e1d740 100644 --- a/python/ql/test/library-tests/dataflow/tainttracking/customSanitizer/InlineTaintTest.expected +++ b/python/ql/test/library-tests/dataflow/tainttracking/customSanitizer/InlineTaintTest.expected @@ -2,32 +2,32 @@ argumentToEnsureNotTaintedNotMarkedAsSpurious untaintedArgumentToEnsureTaintedNotMarkedAsMissing testFailures isSanitizer -| test.py:21:39:21:39 | ControlFlowNode for s | -| test.py:34:39:34:39 | ControlFlowNode for s | -| test.py:52:28:52:28 | ControlFlowNode for s | -| test.py:66:10:66:29 | ControlFlowNode for emulated_escaping() | -| test_logical.py:33:28:33:28 | ControlFlowNode for s | -| test_logical.py:40:28:40:28 | ControlFlowNode for s | -| test_logical.py:48:28:48:28 | ControlFlowNode for s | -| test_logical.py:53:28:53:28 | ControlFlowNode for s | -| test_logical.py:92:28:92:28 | ControlFlowNode for s | -| test_logical.py:103:28:103:28 | ControlFlowNode for s | -| test_logical.py:111:28:111:28 | ControlFlowNode for s | -| test_logical.py:130:28:130:28 | ControlFlowNode for s | -| test_logical.py:137:28:137:28 | ControlFlowNode for s | -| test_logical.py:148:28:148:28 | ControlFlowNode for s | -| test_logical.py:151:28:151:28 | ControlFlowNode for s | -| test_logical.py:158:28:158:28 | ControlFlowNode for s | -| test_logical.py:167:24:167:24 | ControlFlowNode for s | -| test_logical.py:176:24:176:24 | ControlFlowNode for s | -| test_logical.py:185:24:185:24 | ControlFlowNode for s | -| test_logical.py:193:24:193:24 | ControlFlowNode for s | -| test_logical.py:199:28:199:28 | ControlFlowNode for s | -| test_logical.py:206:28:206:28 | ControlFlowNode for s | -| test_logical.py:211:28:211:28 | ControlFlowNode for s | -| test_logical.py:214:28:214:28 | ControlFlowNode for s | -| test_logical.py:219:28:219:28 | ControlFlowNode for s | -| test_logical.py:226:28:226:28 | ControlFlowNode for s | -| test_logical.py:231:28:231:28 | ControlFlowNode for s | -| test_logical.py:234:28:234:28 | ControlFlowNode for s | -| test_reference.py:31:28:31:28 | ControlFlowNode for s | +| test.py:21:39:21:39 | s | +| test.py:34:39:34:39 | s | +| test.py:52:28:52:28 | s | +| test.py:66:10:66:29 | After emulated_escaping() | +| test_logical.py:33:28:33:28 | s | +| test_logical.py:40:28:40:28 | s | +| test_logical.py:48:28:48:28 | s | +| test_logical.py:53:28:53:28 | s | +| test_logical.py:92:28:92:28 | s | +| test_logical.py:103:28:103:28 | s | +| test_logical.py:111:28:111:28 | s | +| test_logical.py:130:28:130:28 | s | +| test_logical.py:137:28:137:28 | s | +| test_logical.py:148:28:148:28 | s | +| test_logical.py:151:28:151:28 | s | +| test_logical.py:158:28:158:28 | s | +| test_logical.py:167:24:167:24 | s | +| test_logical.py:176:24:176:24 | s | +| test_logical.py:185:24:185:24 | s | +| test_logical.py:193:24:193:24 | s | +| test_logical.py:199:28:199:28 | s | +| test_logical.py:206:28:206:28 | s | +| test_logical.py:211:28:211:28 | s | +| test_logical.py:214:28:214:28 | s | +| test_logical.py:219:28:219:28 | s | +| test_logical.py:226:28:226:28 | s | +| test_logical.py:231:28:231:28 | s | +| test_logical.py:234:28:234:28 | s | +| test_reference.py:31:28:31:28 | s | diff --git a/python/ql/test/library-tests/dataflow/tainttracking/customSanitizer/InlineTaintTest.ql b/python/ql/test/library-tests/dataflow/tainttracking/customSanitizer/InlineTaintTest.ql index 597f368b02ff..346bdbcd6026 100644 --- a/python/ql/test/library-tests/dataflow/tainttracking/customSanitizer/InlineTaintTest.ql +++ b/python/ql/test/library-tests/dataflow/tainttracking/customSanitizer/InlineTaintTest.ql @@ -1,14 +1,15 @@ import experimental.meta.InlineTaintTest +private import semmle.python.controlflow.internal.Cfg as Cfg -predicate isSafeCheck(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) { - g.(CallNode).getNode().getFunc().(Name).getId() in ["is_safe", "emulated_is_safe"] and - node = g.(CallNode).getAnArg() and +predicate isSafeCheck(DataFlow::GuardNode g, Cfg::ControlFlowNode node, boolean branch) { + g.(Cfg::CallNode).getNode().getFunc().(Name).getId() in ["is_safe", "emulated_is_safe"] and + node = g.(Cfg::CallNode).getAnArg() and branch = true } -predicate isUnsafeCheck(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) { - g.(CallNode).getNode().getFunc().(Name).getId() in ["is_unsafe", "emulated_is_unsafe"] and - node = g.(CallNode).getAnArg() and +predicate isUnsafeCheck(DataFlow::GuardNode g, Cfg::ControlFlowNode node, boolean branch) { + g.(Cfg::CallNode).getNode().getFunc().(Name).getId() in ["is_unsafe", "emulated_is_unsafe"] and + node = g.(Cfg::CallNode).getAnArg() and branch = false } diff --git a/python/ql/test/library-tests/dataflow/tainttracking/customSanitizer/test.py b/python/ql/test/library-tests/dataflow/tainttracking/customSanitizer/test.py index 27b5c59827ad..f99998e586b9 100644 --- a/python/ql/test/library-tests/dataflow/tainttracking/customSanitizer/test.py +++ b/python/ql/test/library-tests/dataflow/tainttracking/customSanitizer/test.py @@ -21,7 +21,7 @@ def test_custom_sanitizer_exception_raise(): emulated_authentication_check(s) ensure_not_tainted(s) except: - ensure_tainted(s) # $ tainted + ensure_tainted(s) # $ MISSING: tainted raise ensure_not_tainted(s) @@ -34,10 +34,10 @@ def test_custom_sanitizer_exception_pass(): emulated_authentication_check(s) ensure_not_tainted(s) except: - ensure_tainted(s) # $ tainted + ensure_tainted(s) # $ MISSING: tainted pass - ensure_tainted(s) # $ tainted + ensure_tainted(s) # $ MISSING: tainted def emulated_is_safe(arg): diff --git a/python/ql/test/library-tests/dataflow/typetracking-summaries/TestSummaries.qll b/python/ql/test/library-tests/dataflow/typetracking-summaries/TestSummaries.qll index 57e0013b6e0e..4651d5c6180c 100644 --- a/python/ql/test/library-tests/dataflow/typetracking-summaries/TestSummaries.qll +++ b/python/ql/test/library-tests/dataflow/typetracking-summaries/TestSummaries.qll @@ -2,6 +2,7 @@ overlay[local?] module; private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.FlowSummary private import semmle.python.ApiGraphs @@ -17,7 +18,7 @@ module RecursionGuard { RecursionGuard() { this = "TypeTrackingSummariesRecursionGuard" } override DataFlow::CallCfgNode getACall() { - result.getFunction().asCfgNode().(NameNode).getId() = this and + result.getFunction().asCfgNode().(Cfg::NameNode).getId() = this and (TT::callStep(_, _) implies any()) } @@ -41,7 +42,7 @@ private class SummarizedCallableIdentity extends SummarizedCallable::Range { override DataFlow::CallCfgNode getACall() { none() } override DataFlow::CallCfgNode getACallSimple() { - result.getFunction().asCfgNode().(NameNode).getId() = this + result.getFunction().asCfgNode().(Cfg::NameNode).getId() = this } override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this } @@ -60,7 +61,7 @@ private class SummarizedCallableApplyLambda extends SummarizedCallable::Range { override DataFlow::CallCfgNode getACall() { none() } override DataFlow::CallCfgNode getACallSimple() { - result.getFunction().asCfgNode().(NameNode).getId() = this + result.getFunction().asCfgNode().(Cfg::NameNode).getId() = this } override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this } @@ -82,7 +83,7 @@ private class SummarizedCallableReversed extends SummarizedCallable::Range { override DataFlow::CallCfgNode getACall() { none() } override DataFlow::CallCfgNode getACallSimple() { - result.getFunction().asCfgNode().(NameNode).getId() = this + result.getFunction().asCfgNode().(Cfg::NameNode).getId() = this } override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this } @@ -100,7 +101,7 @@ private class SummarizedCallableMap extends SummarizedCallable::Range { override DataFlow::CallCfgNode getACall() { none() } override DataFlow::CallCfgNode getACallSimple() { - result.getFunction().asCfgNode().(NameNode).getId() = this + result.getFunction().asCfgNode().(Cfg::NameNode).getId() = this } override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this } @@ -122,7 +123,7 @@ private class SummarizedCallableAppend extends SummarizedCallable::Range { override DataFlow::CallCfgNode getACall() { none() } override DataFlow::CallCfgNode getACallSimple() { - result.getFunction().asCfgNode().(NameNode).getId() = this + result.getFunction().asCfgNode().(Cfg::NameNode).getId() = this } override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this } @@ -165,7 +166,7 @@ private class SummarizedCallableReadSecret extends SummarizedCallable::Range { override DataFlow::CallCfgNode getACall() { none() } override DataFlow::CallCfgNode getACallSimple() { - result.getFunction().asCfgNode().(NameNode).getId() = this + result.getFunction().asCfgNode().(Cfg::NameNode).getId() = this } override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this } @@ -183,7 +184,7 @@ private class SummarizedCallableSetSecret extends SummarizedCallable::Range { override DataFlow::CallCfgNode getACall() { none() } override DataFlow::CallCfgNode getACallSimple() { - result.getFunction().asCfgNode().(NameNode).getId() = this + result.getFunction().asCfgNode().(Cfg::NameNode).getId() = this } override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this } diff --git a/python/ql/test/library-tests/dataflow/typetracking-summaries/tracked.ql b/python/ql/test/library-tests/dataflow/typetracking-summaries/tracked.ql index c4ed2522092f..893194ead710 100644 --- a/python/ql/test/library-tests/dataflow/typetracking-summaries/tracked.ql +++ b/python/ql/test/library-tests/dataflow/typetracking-summaries/tracked.ql @@ -1,4 +1,5 @@ import python +private import semmle.python.controlflow.internal.Cfg as Cfg import semmle.python.dataflow.new.DataFlow import semmle.python.dataflow.new.TypeTracking import utils.test.InlineExpectationsTest @@ -10,7 +11,7 @@ import TestSummaries // ----------------------------------------------------------------------------- private DataFlow::TypeTrackingNode tracked(TypeTracker t) { t.start() and - result.asCfgNode() = any(NameNode n | n.getId() = "tracked") + result.asCfgNode() = any(Cfg::NameNode n | n.getId() = "tracked") or exists(TypeTracker t2 | result = tracked(t2).track(t2, t)) } diff --git a/python/ql/test/library-tests/dataflow/typetracking/moduleattr.expected b/python/ql/test/library-tests/dataflow/typetracking/moduleattr.expected index 06b560623929..30589f705c87 100644 --- a/python/ql/test/library-tests/dataflow/typetracking/moduleattr.expected +++ b/python/ql/test/library-tests/dataflow/typetracking/moduleattr.expected @@ -1,12 +1,12 @@ module_tracker -| import_as_attr.py:1:6:1:11 | ControlFlowNode for ImportExpr | +| import_as_attr.py:1:6:1:11 | ImportExpr | module_attr_tracker | import_as_attr.py:0:0:0:0 | ModuleVariableNode in Module import_as_attr for attr_ref | | import_as_attr.py:0:0:0:0 | ModuleVariableNode in Module import_as_attr for x | -| import_as_attr.py:1:20:1:35 | ControlFlowNode for ImportMember | -| import_as_attr.py:1:28:1:35 | ControlFlowNode for attr_ref | -| import_as_attr.py:3:1:3:1 | ControlFlowNode for x | -| import_as_attr.py:3:5:3:12 | ControlFlowNode for attr_ref | -| import_as_attr.py:5:1:5:10 | Entry definition for SsaSourceVariable attr_ref | -| import_as_attr.py:6:5:6:5 | ControlFlowNode for y | -| import_as_attr.py:6:9:6:16 | ControlFlowNode for attr_ref | +| import_as_attr.py:1:20:1:35 | After ImportMember | +| import_as_attr.py:1:28:1:35 | attr_ref | +| import_as_attr.py:3:1:3:1 | x | +| import_as_attr.py:3:5:3:12 | attr_ref | +| import_as_attr.py:5:1:5:10 | Entry definition for Global Variable attr_ref | +| import_as_attr.py:6:5:6:5 | y | +| import_as_attr.py:6:9:6:16 | attr_ref | diff --git a/python/ql/test/library-tests/dataflow/typetracking/test.py b/python/ql/test/library-tests/dataflow/typetracking/test.py index 74ee091cc230..19d5bd76f5fa 100644 --- a/python/ql/test/library-tests/dataflow/typetracking/test.py +++ b/python/ql/test/library-tests/dataflow/typetracking/test.py @@ -90,9 +90,9 @@ def my_decorator(func): def wrapper(): print("before function call") - val = func() # $ MISSING: tracked + val = func() # $ tracked print("after function call") - return val # $ MISSING: tracked + return val # $ tracked return wrapper @my_decorator @@ -105,7 +105,7 @@ def unrelated_func(): def use_funcs_with_decorators(): x = get_tracked2() # $ tracked - y = unrelated_func() + y = unrelated_func() # $ SPURIOUS: tracked # ------------------------------------------------------------------------------ diff --git a/python/ql/test/library-tests/dataflow/typetracking/tracked.ql b/python/ql/test/library-tests/dataflow/typetracking/tracked.ql index e720fd3c451b..c6168985da2f 100644 --- a/python/ql/test/library-tests/dataflow/typetracking/tracked.ql +++ b/python/ql/test/library-tests/dataflow/typetracking/tracked.ql @@ -10,7 +10,7 @@ private import semmle.python.dataflow.new.internal.DataFlowPrivate as DP // ----------------------------------------------------------------------------- private DataFlow::TypeTrackingNode tracked(TypeTracker t) { t.start() and - result.asCfgNode() = any(NameNode n | n.getId() = "tracked") + result.asCfgNode().getNode() = any(Name n | n.getId() = "tracked") or exists(TypeTracker t2 | result = tracked(t2).track(t2, t)) } @@ -51,14 +51,14 @@ module TrackedTest implements TestSig { // ----------------------------------------------------------------------------- private DataFlow::TypeTrackingNode int_type(TypeTracker t) { t.start() and - result.asCfgNode() = any(CallNode c | c.getFunction().(NameNode).getId() = "int") + result.asCfgNode().getNode() = any(Call c | c.getFunc().(Name).getId() = "int") or exists(TypeTracker t2 | result = int_type(t2).track(t2, t)) } private DataFlow::TypeTrackingNode string_type(TypeTracker t) { t.start() and - result.asCfgNode() = any(CallNode c | c.getFunction().(NameNode).getId() = "str") + result.asCfgNode().getNode() = any(Call c | c.getFunc().(Name).getId() = "str") or exists(TypeTracker t2 | result = string_type(t2).track(t2, t)) } diff --git a/python/ql/test/library-tests/dataflow/typetracking_imports/highlight_problem.expected b/python/ql/test/library-tests/dataflow/typetracking_imports/highlight_problem.expected index 9aa55c97f1cb..61426b651467 100644 --- a/python/ql/test/library-tests/dataflow/typetracking_imports/highlight_problem.expected +++ b/python/ql/test/library-tests/dataflow/typetracking_imports/highlight_problem.expected @@ -1,9 +1,7 @@ -| pkg/alias_only_direct.py:0:0:0:0 | Module pkg.alias_only_direct | pkg/alias_only_direct.py:1:22:1:24 | GSSA Variable foo | use to normal exit | -| pkg/alias_problem.py:0:0:0:0 | Module pkg.alias_problem | pkg/alias_problem.py:1:22:1:24 | GSSA Variable foo | no use to normal exit | -| pkg/alias_problem.py:0:0:0:0 | Module pkg.alias_problem | pkg/alias_problem.py:2:1:2:20 | GSSA Variable foo | use to normal exit | -| pkg/alias_problem_fixed.py:0:0:0:0 | Module pkg.alias_problem_fixed | pkg/alias_problem_fixed.py:0:0:0:0 | GSSA Variable foo | no use to normal exit | -| pkg/alias_problem_fixed.py:0:0:0:0 | Module pkg.alias_problem_fixed | pkg/alias_problem_fixed.py:3:22:3:24 | GSSA Variable foo | use to normal exit | -| pkg/problem_absolute_import.py:0:0:0:0 | Module pkg.problem_absolute_import | pkg/problem_absolute_import.py:1:25:1:27 | GSSA Variable foo | no use to normal exit | -| pkg/problem_absolute_import.py:0:0:0:0 | Module pkg.problem_absolute_import | pkg/problem_absolute_import.py:2:1:2:23 | GSSA Variable foo | use to normal exit | -| pkg/works_absolute_import.py:0:0:0:0 | Module pkg.works_absolute_import | pkg/works_absolute_import.py:0:0:0:0 | GSSA Variable foo | no use to normal exit | -| pkg/works_absolute_import.py:0:0:0:0 | Module pkg.works_absolute_import | pkg/works_absolute_import.py:2:25:2:27 | GSSA Variable foo | use to normal exit | +| pkg/alias_only_direct.py:0:0:0:0 | Module pkg.alias_only_direct | pkg/alias_only_direct.py:1:22:1:24 | SSA def(Global Variable foo) | use to normal exit | +| pkg/alias_problem.py:0:0:0:0 | Module pkg.alias_problem | pkg/alias_problem.py:1:22:1:24 | SSA def(Global Variable foo) | no use to normal exit | +| pkg/alias_problem.py:0:0:0:0 | Module pkg.alias_problem | pkg/alias_problem.py:2:1:2:20 | SSA def(Global Variable foo) | use to normal exit | +| pkg/alias_problem_fixed.py:0:0:0:0 | Module pkg.alias_problem_fixed | pkg/alias_problem_fixed.py:3:22:3:24 | SSA def(Global Variable foo) | use to normal exit | +| pkg/problem_absolute_import.py:0:0:0:0 | Module pkg.problem_absolute_import | pkg/problem_absolute_import.py:1:25:1:27 | SSA def(Global Variable foo) | no use to normal exit | +| pkg/problem_absolute_import.py:0:0:0:0 | Module pkg.problem_absolute_import | pkg/problem_absolute_import.py:2:1:2:23 | SSA def(Global Variable foo) | use to normal exit | +| pkg/works_absolute_import.py:0:0:0:0 | Module pkg.works_absolute_import | pkg/works_absolute_import.py:2:25:2:27 | SSA def(Global Variable foo) | use to normal exit | diff --git a/python/ql/test/library-tests/dataflow/typetracking_imports/highlight_problem.ql b/python/ql/test/library-tests/dataflow/typetracking_imports/highlight_problem.ql index fc1441be4797..cc8049e00d08 100644 --- a/python/ql/test/library-tests/dataflow/typetracking_imports/highlight_problem.ql +++ b/python/ql/test/library-tests/dataflow/typetracking_imports/highlight_problem.ql @@ -1,15 +1,20 @@ import python +private import semmle.python.controlflow.internal.Cfg as Cfg +private import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl // looking at `module_export` predicate in DataFlowPrivate, the core of the problem is // that in alias_problem.py, the direct import of `foo` does not flow to a normal exit of // the module. Instead there is a second variable foo coming from `from .other import*` that // goes to the normal exit of the module. -from Module m, EssaVariable v, string useToNormalExit +from Module m, SsaImpl::EssaVariable v, string useToNormalExit where m = v.getScope().getEnclosingModule() and not m.getName() in ["pkg.use", "pkg.foo_def"] and v.getName() = "foo" and - if v.getAUse() = m.getANormalExit() + if + exists(Cfg::ControlFlowNode exit | + exit.isNormalExit() and exit.getScope() = m and v.getAUse() = exit + ) then useToNormalExit = "use to normal exit" else useToNormalExit = "no use to normal exit" select m, v, useToNormalExit diff --git a/python/ql/test/library-tests/dataflow/use-use-flow/use-use-counts.expected b/python/ql/test/library-tests/dataflow/use-use-flow/use-use-counts.expected index 92f5114fb8f4..ee3c09b7995d 100644 --- a/python/ql/test/library-tests/dataflow/use-use-flow/use-use-counts.expected +++ b/python/ql/test/library-tests/dataflow/use-use-flow/use-use-counts.expected @@ -1,24 +1,25 @@ implicit_use_count -| 0 | +| 1 | implicit_use +| read_explosion.py:9:1:9:12 | Normal Exit | source_use_count | 6 | source_use -| read_explosion.py:17:15:17:15 | ControlFlowNode for x | -| read_explosion.py:19:13:19:13 | ControlFlowNode for x | -| read_explosion.py:21:11:21:11 | ControlFlowNode for x | -| read_explosion.py:28:15:28:15 | ControlFlowNode for x | -| read_explosion.py:30:13:30:13 | ControlFlowNode for x | -| read_explosion.py:32:11:32:11 | ControlFlowNode for x | +| read_explosion.py:17:15:17:15 | x | +| read_explosion.py:19:13:19:13 | x | +| read_explosion.py:21:11:21:11 | x | +| read_explosion.py:28:15:28:15 | x | +| read_explosion.py:30:13:30:13 | x | +| read_explosion.py:32:11:32:11 | x | use_use_edge_count | 9 | use_use_edge -| read_explosion.py:17:15:17:15 | ControlFlowNode for x | read_explosion.py:28:15:28:15 | ControlFlowNode for x | -| read_explosion.py:17:15:17:15 | ControlFlowNode for x | read_explosion.py:30:13:30:13 | ControlFlowNode for x | -| read_explosion.py:17:15:17:15 | ControlFlowNode for x | read_explosion.py:32:11:32:11 | ControlFlowNode for x | -| read_explosion.py:19:13:19:13 | ControlFlowNode for x | read_explosion.py:28:15:28:15 | ControlFlowNode for x | -| read_explosion.py:19:13:19:13 | ControlFlowNode for x | read_explosion.py:30:13:30:13 | ControlFlowNode for x | -| read_explosion.py:19:13:19:13 | ControlFlowNode for x | read_explosion.py:32:11:32:11 | ControlFlowNode for x | -| read_explosion.py:21:11:21:11 | ControlFlowNode for x | read_explosion.py:28:15:28:15 | ControlFlowNode for x | -| read_explosion.py:21:11:21:11 | ControlFlowNode for x | read_explosion.py:30:13:30:13 | ControlFlowNode for x | -| read_explosion.py:21:11:21:11 | ControlFlowNode for x | read_explosion.py:32:11:32:11 | ControlFlowNode for x | +| read_explosion.py:17:15:17:15 | x | read_explosion.py:28:15:28:15 | x | +| read_explosion.py:17:15:17:15 | x | read_explosion.py:30:13:30:13 | x | +| read_explosion.py:17:15:17:15 | x | read_explosion.py:32:11:32:11 | x | +| read_explosion.py:19:13:19:13 | x | read_explosion.py:28:15:28:15 | x | +| read_explosion.py:19:13:19:13 | x | read_explosion.py:30:13:30:13 | x | +| read_explosion.py:19:13:19:13 | x | read_explosion.py:32:11:32:11 | x | +| read_explosion.py:21:11:21:11 | x | read_explosion.py:28:15:28:15 | x | +| read_explosion.py:21:11:21:11 | x | read_explosion.py:30:13:30:13 | x | +| read_explosion.py:21:11:21:11 | x | read_explosion.py:32:11:32:11 | x | diff --git a/python/ql/test/library-tests/dataflow/use-use-flow/use-use-counts.ql b/python/ql/test/library-tests/dataflow/use-use-flow/use-use-counts.ql index ff18cc66f68b..f00e808b574c 100644 --- a/python/ql/test/library-tests/dataflow/use-use-flow/use-use-counts.ql +++ b/python/ql/test/library-tests/dataflow/use-use-flow/use-use-counts.ql @@ -1,26 +1,28 @@ import python +private import semmle.python.controlflow.internal.Cfg as Cfg +private import semmle.python.dataflow.new.internal.SsaImpl as SsaImpl private import semmle.python.dataflow.new.internal.DataFlowPrivate query int implicit_use_count() { - exists(SsaSourceVariable x | x.getName() = "x" | result = count(x.getAnImplicitUse())) + exists(SsaImpl::SsaSourceVariable x | x.getName() = "x" | result = count(x.getAnImplicitUse())) } -query ControlFlowNode implicit_use() { - exists(SsaSourceVariable x | x.getName() = "x" | result = x.getAnImplicitUse()) +query Cfg::ControlFlowNode implicit_use() { + exists(SsaImpl::SsaSourceVariable x | x.getName() = "x" | result = x.getAnImplicitUse()) } query int source_use_count() { - exists(SsaSourceVariable x | x.getName() = "x" | result = count(x.getASourceUse())) + exists(SsaImpl::SsaSourceVariable x | x.getName() = "x" | result = count(x.getASourceUse())) } -query ControlFlowNode source_use() { - exists(SsaSourceVariable x | x.getName() = "x" | result = x.getASourceUse()) +query Cfg::ControlFlowNode source_use() { + exists(SsaImpl::SsaSourceVariable x | x.getName() = "x" | result = x.getASourceUse()) } query int use_use_edge_count() { - exists(SsaSourceVariable x | x.getName() = "x" | + exists(SsaImpl::SsaSourceVariable x | x.getName() = "x" | result = - count(NameNode use1, NameNode use2 | + count(Cfg::NameNode use1, Cfg::NameNode use2 | use1 = x.getAUse() and use2 = x.getAUse() and LocalFlow::useToNextUse(use1, use2) @@ -28,8 +30,8 @@ query int use_use_edge_count() { ) } -query predicate use_use_edge(NameNode use1, NameNode use2) { - exists(SsaSourceVariable x | x.getName() = "x" | +query predicate use_use_edge(Cfg::NameNode use1, Cfg::NameNode use2) { + exists(SsaImpl::SsaSourceVariable x | x.getName() = "x" | use1 = x.getAUse() and use2 = x.getAUse() and LocalFlow::useToNextUse(use1, use2) diff --git a/python/ql/test/library-tests/frameworks/aiohttp/InlineTaintTest.ql b/python/ql/test/library-tests/frameworks/aiohttp/InlineTaintTest.ql index caaa22ef1942..9b9604760054 100644 --- a/python/ql/test/library-tests/frameworks/aiohttp/InlineTaintTest.ql +++ b/python/ql/test/library-tests/frameworks/aiohttp/InlineTaintTest.ql @@ -1,8 +1,9 @@ import experimental.meta.InlineTaintTest +private import semmle.python.controlflow.internal.Cfg as Cfg -predicate isSafe(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) { - g.(CallNode).getFunction().(NameNode).getId() = "is_safe" and - node = g.(CallNode).getArg(_) and +predicate isSafe(DataFlow::GuardNode g, Cfg::ControlFlowNode node, boolean branch) { + g.(Cfg::CallNode).getFunction().(Cfg::NameNode).getId() = "is_safe" and + node = g.(Cfg::CallNode).getArg(_) and branch = true } diff --git a/python/ql/test/library-tests/frameworks/modeling-example/NaiveModel.expected b/python/ql/test/library-tests/frameworks/modeling-example/NaiveModel.expected index 178f63b4aabb..93475614b77d 100644 --- a/python/ql/test/library-tests/frameworks/modeling-example/NaiveModel.expected +++ b/python/ql/test/library-tests/frameworks/modeling-example/NaiveModel.expected @@ -1,46 +1,46 @@ edges -| test.py:21:11:21:18 | ControlFlowNode for source() | test.py:22:10:22:24 | ControlFlowNode for Attribute() | provenance | AdditionalTaintStep | -| test.py:29:11:29:18 | ControlFlowNode for source() | test.py:32:5:32:7 | ControlFlowNode for val | provenance | AdditionalTaintStep | -| test.py:32:5:32:7 | ControlFlowNode for val | test.py:33:10:33:12 | ControlFlowNode for val | provenance | | -| test.py:40:5:40:7 | ControlFlowNode for val | test.py:41:10:41:12 | ControlFlowNode for val | provenance | | -| test.py:40:11:40:25 | ControlFlowNode for Attribute() | test.py:40:5:40:7 | ControlFlowNode for val | provenance | | -| test.py:45:11:45:18 | ControlFlowNode for source() | test.py:40:11:40:25 | ControlFlowNode for Attribute() | provenance | AdditionalTaintStep | -| test.py:53:5:53:7 | ControlFlowNode for val | test.py:54:10:54:12 | ControlFlowNode for val | provenance | | -| test.py:53:11:53:25 | ControlFlowNode for Attribute() | test.py:53:5:53:7 | ControlFlowNode for val | provenance | | -| test.py:70:11:70:18 | ControlFlowNode for source() | test.py:53:11:53:25 | ControlFlowNode for Attribute() | provenance | AdditionalTaintStep | -| test.py:78:5:78:7 | ControlFlowNode for val | test.py:79:10:79:12 | ControlFlowNode for val | provenance | | -| test.py:78:11:78:14 | ControlFlowNode for bm() | test.py:78:5:78:7 | ControlFlowNode for val | provenance | | -| test.py:83:11:83:18 | ControlFlowNode for source() | test.py:78:11:78:14 | ControlFlowNode for bm() | provenance | AdditionalTaintStep | -| test.py:90:5:90:7 | ControlFlowNode for val | test.py:91:10:91:12 | ControlFlowNode for val | provenance | | -| test.py:90:11:90:14 | ControlFlowNode for bm() | test.py:90:5:90:7 | ControlFlowNode for val | provenance | | -| test.py:107:11:107:18 | ControlFlowNode for source() | test.py:90:11:90:14 | ControlFlowNode for bm() | provenance | AdditionalTaintStep | +| test.py:21:11:21:18 | After source() | test.py:22:10:22:24 | After Attribute() | provenance | AdditionalTaintStep | +| test.py:29:11:29:18 | After source() | test.py:32:5:32:7 | val | provenance | AdditionalTaintStep | +| test.py:32:5:32:7 | val | test.py:33:10:33:12 | val | provenance | | +| test.py:40:5:40:7 | val | test.py:41:10:41:12 | val | provenance | | +| test.py:40:11:40:25 | After Attribute() | test.py:40:5:40:7 | val | provenance | | +| test.py:45:11:45:18 | After source() | test.py:40:11:40:25 | After Attribute() | provenance | AdditionalTaintStep | +| test.py:53:5:53:7 | val | test.py:54:10:54:12 | val | provenance | | +| test.py:53:11:53:25 | After Attribute() | test.py:53:5:53:7 | val | provenance | | +| test.py:70:11:70:18 | After source() | test.py:53:11:53:25 | After Attribute() | provenance | AdditionalTaintStep | +| test.py:78:5:78:7 | val | test.py:79:10:79:12 | val | provenance | | +| test.py:78:11:78:14 | After bm() | test.py:78:5:78:7 | val | provenance | | +| test.py:83:11:83:18 | After source() | test.py:78:11:78:14 | After bm() | provenance | AdditionalTaintStep | +| test.py:90:5:90:7 | val | test.py:91:10:91:12 | val | provenance | | +| test.py:90:11:90:14 | After bm() | test.py:90:5:90:7 | val | provenance | | +| test.py:107:11:107:18 | After source() | test.py:90:11:90:14 | After bm() | provenance | AdditionalTaintStep | nodes -| test.py:21:11:21:18 | ControlFlowNode for source() | semmle.label | ControlFlowNode for source() | -| test.py:22:10:22:24 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | -| test.py:29:11:29:18 | ControlFlowNode for source() | semmle.label | ControlFlowNode for source() | -| test.py:32:5:32:7 | ControlFlowNode for val | semmle.label | ControlFlowNode for val | -| test.py:33:10:33:12 | ControlFlowNode for val | semmle.label | ControlFlowNode for val | -| test.py:40:5:40:7 | ControlFlowNode for val | semmle.label | ControlFlowNode for val | -| test.py:40:11:40:25 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | -| test.py:41:10:41:12 | ControlFlowNode for val | semmle.label | ControlFlowNode for val | -| test.py:45:11:45:18 | ControlFlowNode for source() | semmle.label | ControlFlowNode for source() | -| test.py:53:5:53:7 | ControlFlowNode for val | semmle.label | ControlFlowNode for val | -| test.py:53:11:53:25 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | -| test.py:54:10:54:12 | ControlFlowNode for val | semmle.label | ControlFlowNode for val | -| test.py:70:11:70:18 | ControlFlowNode for source() | semmle.label | ControlFlowNode for source() | -| test.py:78:5:78:7 | ControlFlowNode for val | semmle.label | ControlFlowNode for val | -| test.py:78:11:78:14 | ControlFlowNode for bm() | semmle.label | ControlFlowNode for bm() | -| test.py:79:10:79:12 | ControlFlowNode for val | semmle.label | ControlFlowNode for val | -| test.py:83:11:83:18 | ControlFlowNode for source() | semmle.label | ControlFlowNode for source() | -| test.py:90:5:90:7 | ControlFlowNode for val | semmle.label | ControlFlowNode for val | -| test.py:90:11:90:14 | ControlFlowNode for bm() | semmle.label | ControlFlowNode for bm() | -| test.py:91:10:91:12 | ControlFlowNode for val | semmle.label | ControlFlowNode for val | -| test.py:107:11:107:18 | ControlFlowNode for source() | semmle.label | ControlFlowNode for source() | +| test.py:21:11:21:18 | After source() | semmle.label | After source() | +| test.py:22:10:22:24 | After Attribute() | semmle.label | After Attribute() | +| test.py:29:11:29:18 | After source() | semmle.label | After source() | +| test.py:32:5:32:7 | val | semmle.label | val | +| test.py:33:10:33:12 | val | semmle.label | val | +| test.py:40:5:40:7 | val | semmle.label | val | +| test.py:40:11:40:25 | After Attribute() | semmle.label | After Attribute() | +| test.py:41:10:41:12 | val | semmle.label | val | +| test.py:45:11:45:18 | After source() | semmle.label | After source() | +| test.py:53:5:53:7 | val | semmle.label | val | +| test.py:53:11:53:25 | After Attribute() | semmle.label | After Attribute() | +| test.py:54:10:54:12 | val | semmle.label | val | +| test.py:70:11:70:18 | After source() | semmle.label | After source() | +| test.py:78:5:78:7 | val | semmle.label | val | +| test.py:78:11:78:14 | After bm() | semmle.label | After bm() | +| test.py:79:10:79:12 | val | semmle.label | val | +| test.py:83:11:83:18 | After source() | semmle.label | After source() | +| test.py:90:5:90:7 | val | semmle.label | val | +| test.py:90:11:90:14 | After bm() | semmle.label | After bm() | +| test.py:91:10:91:12 | val | semmle.label | val | +| test.py:107:11:107:18 | After source() | semmle.label | After source() | subpaths #select -| test.py:22:10:22:24 | ControlFlowNode for Attribute() | test.py:21:11:21:18 | ControlFlowNode for source() | test.py:22:10:22:24 | ControlFlowNode for Attribute() | test flow (naive): test_simple | -| test.py:33:10:33:12 | ControlFlowNode for val | test.py:29:11:29:18 | ControlFlowNode for source() | test.py:33:10:33:12 | ControlFlowNode for val | test flow (naive): test_alias | -| test.py:41:10:41:12 | ControlFlowNode for val | test.py:45:11:45:18 | ControlFlowNode for source() | test.py:41:10:41:12 | ControlFlowNode for val | test flow (naive): test_across_functions | -| test.py:54:10:54:12 | ControlFlowNode for val | test.py:70:11:70:18 | ControlFlowNode for source() | test.py:54:10:54:12 | ControlFlowNode for val | test flow (naive): test_deeply_nested | -| test.py:79:10:79:12 | ControlFlowNode for val | test.py:83:11:83:18 | ControlFlowNode for source() | test.py:79:10:79:12 | ControlFlowNode for val | test flow (naive): test_pass_bound_method | -| test.py:91:10:91:12 | ControlFlowNode for val | test.py:107:11:107:18 | ControlFlowNode for source() | test.py:91:10:91:12 | ControlFlowNode for val | test flow (naive): test_deeply_nested_bound_method | +| test.py:22:10:22:24 | After Attribute() | test.py:21:11:21:18 | After source() | test.py:22:10:22:24 | After Attribute() | test flow (naive): test_simple | +| test.py:33:10:33:12 | val | test.py:29:11:29:18 | After source() | test.py:33:10:33:12 | val | test flow (naive): test_alias | +| test.py:41:10:41:12 | val | test.py:45:11:45:18 | After source() | test.py:41:10:41:12 | val | test flow (naive): test_across_functions | +| test.py:54:10:54:12 | val | test.py:70:11:70:18 | After source() | test.py:54:10:54:12 | val | test flow (naive): test_deeply_nested | +| test.py:79:10:79:12 | val | test.py:83:11:83:18 | After source() | test.py:79:10:79:12 | val | test flow (naive): test_pass_bound_method | +| test.py:91:10:91:12 | val | test.py:107:11:107:18 | After source() | test.py:91:10:91:12 | val | test flow (naive): test_deeply_nested_bound_method | diff --git a/python/ql/test/library-tests/frameworks/modeling-example/NaiveModel.ql b/python/ql/test/library-tests/frameworks/modeling-example/NaiveModel.ql index 8b6eee5113fc..56d352eef4cb 100644 --- a/python/ql/test/library-tests/frameworks/modeling-example/NaiveModel.ql +++ b/python/ql/test/library-tests/frameworks/modeling-example/NaiveModel.ql @@ -3,6 +3,7 @@ */ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.TaintTracking import SharedFlow::PathGraph @@ -13,7 +14,7 @@ class MyClassGetValueAdditionalTaintStep extends TaintTracking::AdditionalTaintS // obj -> obj.get_value() exists(DataFlow::Node bound_method | bound_method = myClassGetValue(nodeFrom) and - nodeTo.asCfgNode().(CallNode).getFunction() = bound_method.asCfgNode() + nodeTo.asCfgNode().(Cfg::CallNode).getFunction() = bound_method.asCfgNode() ) } } diff --git a/python/ql/test/library-tests/frameworks/modeling-example/ProperModel.expected b/python/ql/test/library-tests/frameworks/modeling-example/ProperModel.expected index 86e0a1958f28..3355dfea9879 100644 --- a/python/ql/test/library-tests/frameworks/modeling-example/ProperModel.expected +++ b/python/ql/test/library-tests/frameworks/modeling-example/ProperModel.expected @@ -1,94 +1,94 @@ edges -| test.py:21:5:21:7 | ControlFlowNode for src | test.py:22:10:22:24 | ControlFlowNode for Attribute() | provenance | AdditionalTaintStep | -| test.py:21:11:21:18 | ControlFlowNode for source() | test.py:21:5:21:7 | ControlFlowNode for src | provenance | | -| test.py:29:5:29:7 | ControlFlowNode for src | test.py:30:5:30:7 | ControlFlowNode for foo | provenance | | -| test.py:29:11:29:18 | ControlFlowNode for source() | test.py:29:5:29:7 | ControlFlowNode for src | provenance | | -| test.py:30:5:30:7 | ControlFlowNode for foo | test.py:31:5:31:16 | ControlFlowNode for bound_method | provenance | AdditionalTaintStep | -| test.py:31:5:31:16 | ControlFlowNode for bound_method | test.py:32:5:32:7 | ControlFlowNode for val | provenance | AdditionalTaintStep | -| test.py:32:5:32:7 | ControlFlowNode for val | test.py:33:10:33:12 | ControlFlowNode for val | provenance | | -| test.py:39:15:39:17 | ControlFlowNode for arg | test.py:40:5:40:7 | ControlFlowNode for val | provenance | AdditionalTaintStep | -| test.py:40:5:40:7 | ControlFlowNode for val | test.py:41:10:41:12 | ControlFlowNode for val | provenance | | -| test.py:45:5:45:7 | ControlFlowNode for src | test.py:46:15:46:17 | ControlFlowNode for src | provenance | | -| test.py:45:11:45:18 | ControlFlowNode for source() | test.py:45:5:45:7 | ControlFlowNode for src | provenance | | -| test.py:46:15:46:17 | ControlFlowNode for src | test.py:39:15:39:17 | ControlFlowNode for arg | provenance | | -| test.py:52:24:52:26 | ControlFlowNode for arg | test.py:53:5:53:7 | ControlFlowNode for val | provenance | AdditionalTaintStep | -| test.py:53:5:53:7 | ControlFlowNode for val | test.py:54:10:54:12 | ControlFlowNode for val | provenance | | -| test.py:57:33:57:35 | ControlFlowNode for arg | test.py:58:24:58:26 | ControlFlowNode for arg | provenance | | -| test.py:58:24:58:26 | ControlFlowNode for arg | test.py:52:24:52:26 | ControlFlowNode for arg | provenance | | -| test.py:61:33:61:35 | ControlFlowNode for arg | test.py:62:33:62:35 | ControlFlowNode for arg | provenance | | -| test.py:62:33:62:35 | ControlFlowNode for arg | test.py:57:33:57:35 | ControlFlowNode for arg | provenance | | -| test.py:65:33:65:35 | ControlFlowNode for arg | test.py:66:33:66:35 | ControlFlowNode for arg | provenance | | -| test.py:66:33:66:35 | ControlFlowNode for arg | test.py:61:33:61:35 | ControlFlowNode for arg | provenance | | -| test.py:70:5:70:7 | ControlFlowNode for src | test.py:71:33:71:35 | ControlFlowNode for src | provenance | | -| test.py:70:11:70:18 | ControlFlowNode for source() | test.py:70:5:70:7 | ControlFlowNode for src | provenance | | -| test.py:71:33:71:35 | ControlFlowNode for src | test.py:65:33:65:35 | ControlFlowNode for arg | provenance | | -| test.py:77:23:77:24 | ControlFlowNode for bm | test.py:78:5:78:7 | ControlFlowNode for val | provenance | AdditionalTaintStep | -| test.py:78:5:78:7 | ControlFlowNode for val | test.py:79:10:79:12 | ControlFlowNode for val | provenance | | -| test.py:83:5:83:7 | ControlFlowNode for src | test.py:84:23:84:35 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | -| test.py:83:11:83:18 | ControlFlowNode for source() | test.py:83:5:83:7 | ControlFlowNode for src | provenance | | -| test.py:84:23:84:35 | ControlFlowNode for Attribute | test.py:77:23:77:24 | ControlFlowNode for bm | provenance | | -| test.py:89:37:89:38 | ControlFlowNode for bm | test.py:90:5:90:7 | ControlFlowNode for val | provenance | AdditionalTaintStep | -| test.py:90:5:90:7 | ControlFlowNode for val | test.py:91:10:91:12 | ControlFlowNode for val | provenance | | -| test.py:94:46:94:47 | ControlFlowNode for bm | test.py:95:37:95:38 | ControlFlowNode for bm | provenance | | -| test.py:95:37:95:38 | ControlFlowNode for bm | test.py:89:37:89:38 | ControlFlowNode for bm | provenance | | -| test.py:98:46:98:47 | ControlFlowNode for bm | test.py:99:46:99:47 | ControlFlowNode for bm | provenance | | -| test.py:99:46:99:47 | ControlFlowNode for bm | test.py:94:46:94:47 | ControlFlowNode for bm | provenance | | -| test.py:102:46:102:47 | ControlFlowNode for bm | test.py:103:46:103:47 | ControlFlowNode for bm | provenance | | -| test.py:103:46:103:47 | ControlFlowNode for bm | test.py:98:46:98:47 | ControlFlowNode for bm | provenance | | -| test.py:107:5:107:7 | ControlFlowNode for src | test.py:108:46:108:58 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | -| test.py:107:11:107:18 | ControlFlowNode for source() | test.py:107:5:107:7 | ControlFlowNode for src | provenance | | -| test.py:108:46:108:58 | ControlFlowNode for Attribute | test.py:102:46:102:47 | ControlFlowNode for bm | provenance | | +| test.py:21:5:21:7 | src | test.py:22:10:22:24 | After Attribute() | provenance | AdditionalTaintStep | +| test.py:21:11:21:18 | After source() | test.py:21:5:21:7 | src | provenance | | +| test.py:29:5:29:7 | src | test.py:30:5:30:7 | foo | provenance | | +| test.py:29:11:29:18 | After source() | test.py:29:5:29:7 | src | provenance | | +| test.py:30:5:30:7 | foo | test.py:31:5:31:16 | bound_method | provenance | AdditionalTaintStep | +| test.py:31:5:31:16 | bound_method | test.py:32:5:32:7 | val | provenance | AdditionalTaintStep | +| test.py:32:5:32:7 | val | test.py:33:10:33:12 | val | provenance | | +| test.py:39:15:39:17 | arg | test.py:40:5:40:7 | val | provenance | AdditionalTaintStep | +| test.py:40:5:40:7 | val | test.py:41:10:41:12 | val | provenance | | +| test.py:45:5:45:7 | src | test.py:46:15:46:17 | src | provenance | | +| test.py:45:11:45:18 | After source() | test.py:45:5:45:7 | src | provenance | | +| test.py:46:15:46:17 | src | test.py:39:15:39:17 | arg | provenance | | +| test.py:52:24:52:26 | arg | test.py:53:5:53:7 | val | provenance | AdditionalTaintStep | +| test.py:53:5:53:7 | val | test.py:54:10:54:12 | val | provenance | | +| test.py:57:33:57:35 | arg | test.py:58:24:58:26 | arg | provenance | | +| test.py:58:24:58:26 | arg | test.py:52:24:52:26 | arg | provenance | | +| test.py:61:33:61:35 | arg | test.py:62:33:62:35 | arg | provenance | | +| test.py:62:33:62:35 | arg | test.py:57:33:57:35 | arg | provenance | | +| test.py:65:33:65:35 | arg | test.py:66:33:66:35 | arg | provenance | | +| test.py:66:33:66:35 | arg | test.py:61:33:61:35 | arg | provenance | | +| test.py:70:5:70:7 | src | test.py:71:33:71:35 | src | provenance | | +| test.py:70:11:70:18 | After source() | test.py:70:5:70:7 | src | provenance | | +| test.py:71:33:71:35 | src | test.py:65:33:65:35 | arg | provenance | | +| test.py:77:23:77:24 | bm | test.py:78:5:78:7 | val | provenance | AdditionalTaintStep | +| test.py:78:5:78:7 | val | test.py:79:10:79:12 | val | provenance | | +| test.py:83:5:83:7 | src | test.py:84:23:84:35 | After Attribute | provenance | AdditionalTaintStep | +| test.py:83:11:83:18 | After source() | test.py:83:5:83:7 | src | provenance | | +| test.py:84:23:84:35 | After Attribute | test.py:77:23:77:24 | bm | provenance | | +| test.py:89:37:89:38 | bm | test.py:90:5:90:7 | val | provenance | AdditionalTaintStep | +| test.py:90:5:90:7 | val | test.py:91:10:91:12 | val | provenance | | +| test.py:94:46:94:47 | bm | test.py:95:37:95:38 | bm | provenance | | +| test.py:95:37:95:38 | bm | test.py:89:37:89:38 | bm | provenance | | +| test.py:98:46:98:47 | bm | test.py:99:46:99:47 | bm | provenance | | +| test.py:99:46:99:47 | bm | test.py:94:46:94:47 | bm | provenance | | +| test.py:102:46:102:47 | bm | test.py:103:46:103:47 | bm | provenance | | +| test.py:103:46:103:47 | bm | test.py:98:46:98:47 | bm | provenance | | +| test.py:107:5:107:7 | src | test.py:108:46:108:58 | After Attribute | provenance | AdditionalTaintStep | +| test.py:107:11:107:18 | After source() | test.py:107:5:107:7 | src | provenance | | +| test.py:108:46:108:58 | After Attribute | test.py:102:46:102:47 | bm | provenance | | nodes -| test.py:21:5:21:7 | ControlFlowNode for src | semmle.label | ControlFlowNode for src | -| test.py:21:11:21:18 | ControlFlowNode for source() | semmle.label | ControlFlowNode for source() | -| test.py:22:10:22:24 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | -| test.py:29:5:29:7 | ControlFlowNode for src | semmle.label | ControlFlowNode for src | -| test.py:29:11:29:18 | ControlFlowNode for source() | semmle.label | ControlFlowNode for source() | -| test.py:30:5:30:7 | ControlFlowNode for foo | semmle.label | ControlFlowNode for foo | -| test.py:31:5:31:16 | ControlFlowNode for bound_method | semmle.label | ControlFlowNode for bound_method | -| test.py:32:5:32:7 | ControlFlowNode for val | semmle.label | ControlFlowNode for val | -| test.py:33:10:33:12 | ControlFlowNode for val | semmle.label | ControlFlowNode for val | -| test.py:39:15:39:17 | ControlFlowNode for arg | semmle.label | ControlFlowNode for arg | -| test.py:40:5:40:7 | ControlFlowNode for val | semmle.label | ControlFlowNode for val | -| test.py:41:10:41:12 | ControlFlowNode for val | semmle.label | ControlFlowNode for val | -| test.py:45:5:45:7 | ControlFlowNode for src | semmle.label | ControlFlowNode for src | -| test.py:45:11:45:18 | ControlFlowNode for source() | semmle.label | ControlFlowNode for source() | -| test.py:46:15:46:17 | ControlFlowNode for src | semmle.label | ControlFlowNode for src | -| test.py:52:24:52:26 | ControlFlowNode for arg | semmle.label | ControlFlowNode for arg | -| test.py:53:5:53:7 | ControlFlowNode for val | semmle.label | ControlFlowNode for val | -| test.py:54:10:54:12 | ControlFlowNode for val | semmle.label | ControlFlowNode for val | -| test.py:57:33:57:35 | ControlFlowNode for arg | semmle.label | ControlFlowNode for arg | -| test.py:58:24:58:26 | ControlFlowNode for arg | semmle.label | ControlFlowNode for arg | -| test.py:61:33:61:35 | ControlFlowNode for arg | semmle.label | ControlFlowNode for arg | -| test.py:62:33:62:35 | ControlFlowNode for arg | semmle.label | ControlFlowNode for arg | -| test.py:65:33:65:35 | ControlFlowNode for arg | semmle.label | ControlFlowNode for arg | -| test.py:66:33:66:35 | ControlFlowNode for arg | semmle.label | ControlFlowNode for arg | -| test.py:70:5:70:7 | ControlFlowNode for src | semmle.label | ControlFlowNode for src | -| test.py:70:11:70:18 | ControlFlowNode for source() | semmle.label | ControlFlowNode for source() | -| test.py:71:33:71:35 | ControlFlowNode for src | semmle.label | ControlFlowNode for src | -| test.py:77:23:77:24 | ControlFlowNode for bm | semmle.label | ControlFlowNode for bm | -| test.py:78:5:78:7 | ControlFlowNode for val | semmle.label | ControlFlowNode for val | -| test.py:79:10:79:12 | ControlFlowNode for val | semmle.label | ControlFlowNode for val | -| test.py:83:5:83:7 | ControlFlowNode for src | semmle.label | ControlFlowNode for src | -| test.py:83:11:83:18 | ControlFlowNode for source() | semmle.label | ControlFlowNode for source() | -| test.py:84:23:84:35 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | -| test.py:89:37:89:38 | ControlFlowNode for bm | semmle.label | ControlFlowNode for bm | -| test.py:90:5:90:7 | ControlFlowNode for val | semmle.label | ControlFlowNode for val | -| test.py:91:10:91:12 | ControlFlowNode for val | semmle.label | ControlFlowNode for val | -| test.py:94:46:94:47 | ControlFlowNode for bm | semmle.label | ControlFlowNode for bm | -| test.py:95:37:95:38 | ControlFlowNode for bm | semmle.label | ControlFlowNode for bm | -| test.py:98:46:98:47 | ControlFlowNode for bm | semmle.label | ControlFlowNode for bm | -| test.py:99:46:99:47 | ControlFlowNode for bm | semmle.label | ControlFlowNode for bm | -| test.py:102:46:102:47 | ControlFlowNode for bm | semmle.label | ControlFlowNode for bm | -| test.py:103:46:103:47 | ControlFlowNode for bm | semmle.label | ControlFlowNode for bm | -| test.py:107:5:107:7 | ControlFlowNode for src | semmle.label | ControlFlowNode for src | -| test.py:107:11:107:18 | ControlFlowNode for source() | semmle.label | ControlFlowNode for source() | -| test.py:108:46:108:58 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:21:5:21:7 | src | semmle.label | src | +| test.py:21:11:21:18 | After source() | semmle.label | After source() | +| test.py:22:10:22:24 | After Attribute() | semmle.label | After Attribute() | +| test.py:29:5:29:7 | src | semmle.label | src | +| test.py:29:11:29:18 | After source() | semmle.label | After source() | +| test.py:30:5:30:7 | foo | semmle.label | foo | +| test.py:31:5:31:16 | bound_method | semmle.label | bound_method | +| test.py:32:5:32:7 | val | semmle.label | val | +| test.py:33:10:33:12 | val | semmle.label | val | +| test.py:39:15:39:17 | arg | semmle.label | arg | +| test.py:40:5:40:7 | val | semmle.label | val | +| test.py:41:10:41:12 | val | semmle.label | val | +| test.py:45:5:45:7 | src | semmle.label | src | +| test.py:45:11:45:18 | After source() | semmle.label | After source() | +| test.py:46:15:46:17 | src | semmle.label | src | +| test.py:52:24:52:26 | arg | semmle.label | arg | +| test.py:53:5:53:7 | val | semmle.label | val | +| test.py:54:10:54:12 | val | semmle.label | val | +| test.py:57:33:57:35 | arg | semmle.label | arg | +| test.py:58:24:58:26 | arg | semmle.label | arg | +| test.py:61:33:61:35 | arg | semmle.label | arg | +| test.py:62:33:62:35 | arg | semmle.label | arg | +| test.py:65:33:65:35 | arg | semmle.label | arg | +| test.py:66:33:66:35 | arg | semmle.label | arg | +| test.py:70:5:70:7 | src | semmle.label | src | +| test.py:70:11:70:18 | After source() | semmle.label | After source() | +| test.py:71:33:71:35 | src | semmle.label | src | +| test.py:77:23:77:24 | bm | semmle.label | bm | +| test.py:78:5:78:7 | val | semmle.label | val | +| test.py:79:10:79:12 | val | semmle.label | val | +| test.py:83:5:83:7 | src | semmle.label | src | +| test.py:83:11:83:18 | After source() | semmle.label | After source() | +| test.py:84:23:84:35 | After Attribute | semmle.label | After Attribute | +| test.py:89:37:89:38 | bm | semmle.label | bm | +| test.py:90:5:90:7 | val | semmle.label | val | +| test.py:91:10:91:12 | val | semmle.label | val | +| test.py:94:46:94:47 | bm | semmle.label | bm | +| test.py:95:37:95:38 | bm | semmle.label | bm | +| test.py:98:46:98:47 | bm | semmle.label | bm | +| test.py:99:46:99:47 | bm | semmle.label | bm | +| test.py:102:46:102:47 | bm | semmle.label | bm | +| test.py:103:46:103:47 | bm | semmle.label | bm | +| test.py:107:5:107:7 | src | semmle.label | src | +| test.py:107:11:107:18 | After source() | semmle.label | After source() | +| test.py:108:46:108:58 | After Attribute | semmle.label | After Attribute | subpaths #select -| test.py:22:10:22:24 | ControlFlowNode for Attribute() | test.py:21:11:21:18 | ControlFlowNode for source() | test.py:22:10:22:24 | ControlFlowNode for Attribute() | test flow (proper): test_simple | -| test.py:33:10:33:12 | ControlFlowNode for val | test.py:29:11:29:18 | ControlFlowNode for source() | test.py:33:10:33:12 | ControlFlowNode for val | test flow (proper): test_alias | -| test.py:41:10:41:12 | ControlFlowNode for val | test.py:45:11:45:18 | ControlFlowNode for source() | test.py:41:10:41:12 | ControlFlowNode for val | test flow (proper): test_across_functions | -| test.py:54:10:54:12 | ControlFlowNode for val | test.py:70:11:70:18 | ControlFlowNode for source() | test.py:54:10:54:12 | ControlFlowNode for val | test flow (proper): test_deeply_nested | -| test.py:79:10:79:12 | ControlFlowNode for val | test.py:83:11:83:18 | ControlFlowNode for source() | test.py:79:10:79:12 | ControlFlowNode for val | test flow (proper): test_pass_bound_method | -| test.py:91:10:91:12 | ControlFlowNode for val | test.py:107:11:107:18 | ControlFlowNode for source() | test.py:91:10:91:12 | ControlFlowNode for val | test flow (proper): test_deeply_nested_bound_method | +| test.py:22:10:22:24 | After Attribute() | test.py:21:11:21:18 | After source() | test.py:22:10:22:24 | After Attribute() | test flow (proper): test_simple | +| test.py:33:10:33:12 | val | test.py:29:11:29:18 | After source() | test.py:33:10:33:12 | val | test flow (proper): test_alias | +| test.py:41:10:41:12 | val | test.py:45:11:45:18 | After source() | test.py:41:10:41:12 | val | test flow (proper): test_across_functions | +| test.py:54:10:54:12 | val | test.py:70:11:70:18 | After source() | test.py:54:10:54:12 | val | test flow (proper): test_deeply_nested | +| test.py:79:10:79:12 | val | test.py:83:11:83:18 | After source() | test.py:79:10:79:12 | val | test flow (proper): test_pass_bound_method | +| test.py:91:10:91:12 | val | test.py:107:11:107:18 | After source() | test.py:91:10:91:12 | val | test flow (proper): test_deeply_nested_bound_method | diff --git a/python/ql/test/library-tests/frameworks/modeling-example/ProperModel.ql b/python/ql/test/library-tests/frameworks/modeling-example/ProperModel.ql index 98bb40501b83..b246847b3b49 100644 --- a/python/ql/test/library-tests/frameworks/modeling-example/ProperModel.ql +++ b/python/ql/test/library-tests/frameworks/modeling-example/ProperModel.ql @@ -3,6 +3,7 @@ */ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.TaintTracking import SharedFlow::PathGraph @@ -11,12 +12,12 @@ import SharedCode class MyClassGetValueAdditionalTaintStep extends TaintTracking::AdditionalTaintStep { override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { // obj -> obj.get_value - nodeTo.asCfgNode().(AttrNode).getObject("get_value") = nodeFrom.asCfgNode() and + nodeTo.asCfgNode().(Cfg::AttrNode).getObject("get_value") = nodeFrom.asCfgNode() and nodeTo = myClassGetValue(_) or // get_value -> get_value() nodeFrom = myClassGetValue(_) and - nodeTo.asCfgNode().(CallNode).getFunction() = nodeFrom.asCfgNode() + nodeTo.asCfgNode().(Cfg::CallNode).getFunction() = nodeFrom.asCfgNode() } } diff --git a/python/ql/test/library-tests/frameworks/modeling-example/SharedCode.qll b/python/ql/test/library-tests/frameworks/modeling-example/SharedCode.qll index a541a7e2c818..3a5938ea8f30 100644 --- a/python/ql/test/library-tests/frameworks/modeling-example/SharedCode.qll +++ b/python/ql/test/library-tests/frameworks/modeling-example/SharedCode.qll @@ -1,4 +1,5 @@ private import python +private import semmle.python.controlflow.internal.Cfg as Cfg private import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.TaintTracking @@ -19,15 +20,15 @@ DataFlow::Node myClassGetValue(MyClass qualifier) { // Config class SourceCall extends DataFlow::Node, MyClass { - SourceCall() { this.asCfgNode().(CallNode).getFunction().(NameNode).getId() = "source" } + SourceCall() { this.asCfgNode().(Cfg::CallNode).getFunction().(Cfg::NameNode).getId() = "source" } } private module SharedConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { source instanceof SourceCall } predicate isSink(DataFlow::Node sink) { - exists(CallNode call | - call.getFunction().(NameNode).getId() = "sink" and + exists(Cfg::CallNode call | + call.getFunction().(Cfg::NameNode).getId() = "sink" and call.getArg(0) = sink.asCfgNode() ) } diff --git a/python/ql/test/query-tests/Security/CWE-022-TarSlip/TarSlip.expected b/python/ql/test/query-tests/Security/CWE-022-TarSlip/TarSlip.expected index 6f98ea1aae2b..412ac072854c 100644 --- a/python/ql/test/query-tests/Security/CWE-022-TarSlip/TarSlip.expected +++ b/python/ql/test/query-tests/Security/CWE-022-TarSlip/TarSlip.expected @@ -1,66 +1,66 @@ edges -| tarslip.py:14:1:14:3 | ControlFlowNode for tar | tarslip.py:15:1:15:3 | ControlFlowNode for tar | provenance | | -| tarslip.py:14:7:14:39 | ControlFlowNode for Attribute() | tarslip.py:14:1:14:3 | ControlFlowNode for tar | provenance | | -| tarslip.py:18:1:18:3 | ControlFlowNode for tar | tarslip.py:19:5:19:9 | ControlFlowNode for entry | provenance | | -| tarslip.py:18:7:18:39 | ControlFlowNode for Attribute() | tarslip.py:18:1:18:3 | ControlFlowNode for tar | provenance | | -| tarslip.py:19:5:19:9 | ControlFlowNode for entry | tarslip.py:20:17:20:21 | ControlFlowNode for entry | provenance | | -| tarslip.py:35:1:35:3 | ControlFlowNode for tar | tarslip.py:36:5:36:9 | ControlFlowNode for entry | provenance | | -| tarslip.py:35:7:35:39 | ControlFlowNode for Attribute() | tarslip.py:35:1:35:3 | ControlFlowNode for tar | provenance | | -| tarslip.py:36:5:36:9 | ControlFlowNode for entry | tarslip.py:39:17:39:21 | ControlFlowNode for entry | provenance | | -| tarslip.py:42:1:42:3 | ControlFlowNode for tar | tarslip.py:43:24:43:26 | ControlFlowNode for tar | provenance | | -| tarslip.py:42:7:42:39 | ControlFlowNode for Attribute() | tarslip.py:42:1:42:3 | ControlFlowNode for tar | provenance | | -| tarslip.py:58:1:58:3 | ControlFlowNode for tar | tarslip.py:59:5:59:9 | ControlFlowNode for entry | provenance | | -| tarslip.py:58:7:58:39 | ControlFlowNode for Attribute() | tarslip.py:58:1:58:3 | ControlFlowNode for tar | provenance | | -| tarslip.py:59:5:59:9 | ControlFlowNode for entry | tarslip.py:61:21:61:25 | ControlFlowNode for entry | provenance | | -| tarslip.py:90:1:90:3 | ControlFlowNode for tar | tarslip.py:91:1:91:3 | ControlFlowNode for tar | provenance | | -| tarslip.py:90:7:90:39 | ControlFlowNode for Attribute() | tarslip.py:90:1:90:3 | ControlFlowNode for tar | provenance | | -| tarslip.py:94:1:94:3 | ControlFlowNode for tar | tarslip.py:95:5:95:9 | ControlFlowNode for entry | provenance | | -| tarslip.py:94:7:94:39 | ControlFlowNode for Attribute() | tarslip.py:94:1:94:3 | ControlFlowNode for tar | provenance | | -| tarslip.py:95:5:95:9 | ControlFlowNode for entry | tarslip.py:96:17:96:21 | ControlFlowNode for entry | provenance | | -| tarslip.py:109:1:109:3 | ControlFlowNode for tar | tarslip.py:110:1:110:3 | ControlFlowNode for tar | provenance | | -| tarslip.py:109:7:109:39 | ControlFlowNode for Attribute() | tarslip.py:109:1:109:3 | ControlFlowNode for tar | provenance | | -| tarslip.py:112:1:112:3 | ControlFlowNode for tar | tarslip.py:113:24:113:26 | ControlFlowNode for tar | provenance | | -| tarslip.py:112:7:112:39 | ControlFlowNode for Attribute() | tarslip.py:112:1:112:3 | ControlFlowNode for tar | provenance | | +| tarslip.py:14:1:14:3 | tar | tarslip.py:15:1:15:3 | tar | provenance | | +| tarslip.py:14:7:14:39 | After Attribute() | tarslip.py:14:1:14:3 | tar | provenance | | +| tarslip.py:18:1:18:3 | tar | tarslip.py:19:5:19:9 | entry | provenance | | +| tarslip.py:18:7:18:39 | After Attribute() | tarslip.py:18:1:18:3 | tar | provenance | | +| tarslip.py:19:5:19:9 | entry | tarslip.py:20:17:20:21 | entry | provenance | | +| tarslip.py:35:1:35:3 | tar | tarslip.py:36:5:36:9 | entry | provenance | | +| tarslip.py:35:7:35:39 | After Attribute() | tarslip.py:35:1:35:3 | tar | provenance | | +| tarslip.py:36:5:36:9 | entry | tarslip.py:39:17:39:21 | entry | provenance | | +| tarslip.py:42:1:42:3 | tar | tarslip.py:43:24:43:26 | tar | provenance | | +| tarslip.py:42:7:42:39 | After Attribute() | tarslip.py:42:1:42:3 | tar | provenance | | +| tarslip.py:58:1:58:3 | tar | tarslip.py:59:5:59:9 | entry | provenance | | +| tarslip.py:58:7:58:39 | After Attribute() | tarslip.py:58:1:58:3 | tar | provenance | | +| tarslip.py:59:5:59:9 | entry | tarslip.py:61:21:61:25 | entry | provenance | | +| tarslip.py:90:1:90:3 | tar | tarslip.py:91:1:91:3 | tar | provenance | | +| tarslip.py:90:7:90:39 | After Attribute() | tarslip.py:90:1:90:3 | tar | provenance | | +| tarslip.py:94:1:94:3 | tar | tarslip.py:95:5:95:9 | entry | provenance | | +| tarslip.py:94:7:94:39 | After Attribute() | tarslip.py:94:1:94:3 | tar | provenance | | +| tarslip.py:95:5:95:9 | entry | tarslip.py:96:17:96:21 | entry | provenance | | +| tarslip.py:109:1:109:3 | tar | tarslip.py:110:1:110:3 | tar | provenance | | +| tarslip.py:109:7:109:39 | After Attribute() | tarslip.py:109:1:109:3 | tar | provenance | | +| tarslip.py:112:1:112:3 | tar | tarslip.py:113:24:113:26 | tar | provenance | | +| tarslip.py:112:7:112:39 | After Attribute() | tarslip.py:112:1:112:3 | tar | provenance | | nodes -| tarslip.py:14:1:14:3 | ControlFlowNode for tar | semmle.label | ControlFlowNode for tar | -| tarslip.py:14:7:14:39 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | -| tarslip.py:15:1:15:3 | ControlFlowNode for tar | semmle.label | ControlFlowNode for tar | -| tarslip.py:18:1:18:3 | ControlFlowNode for tar | semmle.label | ControlFlowNode for tar | -| tarslip.py:18:7:18:39 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | -| tarslip.py:19:5:19:9 | ControlFlowNode for entry | semmle.label | ControlFlowNode for entry | -| tarslip.py:20:17:20:21 | ControlFlowNode for entry | semmle.label | ControlFlowNode for entry | -| tarslip.py:35:1:35:3 | ControlFlowNode for tar | semmle.label | ControlFlowNode for tar | -| tarslip.py:35:7:35:39 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | -| tarslip.py:36:5:36:9 | ControlFlowNode for entry | semmle.label | ControlFlowNode for entry | -| tarslip.py:39:17:39:21 | ControlFlowNode for entry | semmle.label | ControlFlowNode for entry | -| tarslip.py:42:1:42:3 | ControlFlowNode for tar | semmle.label | ControlFlowNode for tar | -| tarslip.py:42:7:42:39 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | -| tarslip.py:43:24:43:26 | ControlFlowNode for tar | semmle.label | ControlFlowNode for tar | -| tarslip.py:58:1:58:3 | ControlFlowNode for tar | semmle.label | ControlFlowNode for tar | -| tarslip.py:58:7:58:39 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | -| tarslip.py:59:5:59:9 | ControlFlowNode for entry | semmle.label | ControlFlowNode for entry | -| tarslip.py:61:21:61:25 | ControlFlowNode for entry | semmle.label | ControlFlowNode for entry | -| tarslip.py:90:1:90:3 | ControlFlowNode for tar | semmle.label | ControlFlowNode for tar | -| tarslip.py:90:7:90:39 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | -| tarslip.py:91:1:91:3 | ControlFlowNode for tar | semmle.label | ControlFlowNode for tar | -| tarslip.py:94:1:94:3 | ControlFlowNode for tar | semmle.label | ControlFlowNode for tar | -| tarslip.py:94:7:94:39 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | -| tarslip.py:95:5:95:9 | ControlFlowNode for entry | semmle.label | ControlFlowNode for entry | -| tarslip.py:96:17:96:21 | ControlFlowNode for entry | semmle.label | ControlFlowNode for entry | -| tarslip.py:109:1:109:3 | ControlFlowNode for tar | semmle.label | ControlFlowNode for tar | -| tarslip.py:109:7:109:39 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | -| tarslip.py:110:1:110:3 | ControlFlowNode for tar | semmle.label | ControlFlowNode for tar | -| tarslip.py:112:1:112:3 | ControlFlowNode for tar | semmle.label | ControlFlowNode for tar | -| tarslip.py:112:7:112:39 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | -| tarslip.py:113:24:113:26 | ControlFlowNode for tar | semmle.label | ControlFlowNode for tar | +| tarslip.py:14:1:14:3 | tar | semmle.label | tar | +| tarslip.py:14:7:14:39 | After Attribute() | semmle.label | After Attribute() | +| tarslip.py:15:1:15:3 | tar | semmle.label | tar | +| tarslip.py:18:1:18:3 | tar | semmle.label | tar | +| tarslip.py:18:7:18:39 | After Attribute() | semmle.label | After Attribute() | +| tarslip.py:19:5:19:9 | entry | semmle.label | entry | +| tarslip.py:20:17:20:21 | entry | semmle.label | entry | +| tarslip.py:35:1:35:3 | tar | semmle.label | tar | +| tarslip.py:35:7:35:39 | After Attribute() | semmle.label | After Attribute() | +| tarslip.py:36:5:36:9 | entry | semmle.label | entry | +| tarslip.py:39:17:39:21 | entry | semmle.label | entry | +| tarslip.py:42:1:42:3 | tar | semmle.label | tar | +| tarslip.py:42:7:42:39 | After Attribute() | semmle.label | After Attribute() | +| tarslip.py:43:24:43:26 | tar | semmle.label | tar | +| tarslip.py:58:1:58:3 | tar | semmle.label | tar | +| tarslip.py:58:7:58:39 | After Attribute() | semmle.label | After Attribute() | +| tarslip.py:59:5:59:9 | entry | semmle.label | entry | +| tarslip.py:61:21:61:25 | entry | semmle.label | entry | +| tarslip.py:90:1:90:3 | tar | semmle.label | tar | +| tarslip.py:90:7:90:39 | After Attribute() | semmle.label | After Attribute() | +| tarslip.py:91:1:91:3 | tar | semmle.label | tar | +| tarslip.py:94:1:94:3 | tar | semmle.label | tar | +| tarslip.py:94:7:94:39 | After Attribute() | semmle.label | After Attribute() | +| tarslip.py:95:5:95:9 | entry | semmle.label | entry | +| tarslip.py:96:17:96:21 | entry | semmle.label | entry | +| tarslip.py:109:1:109:3 | tar | semmle.label | tar | +| tarslip.py:109:7:109:39 | After Attribute() | semmle.label | After Attribute() | +| tarslip.py:110:1:110:3 | tar | semmle.label | tar | +| tarslip.py:112:1:112:3 | tar | semmle.label | tar | +| tarslip.py:112:7:112:39 | After Attribute() | semmle.label | After Attribute() | +| tarslip.py:113:24:113:26 | tar | semmle.label | tar | subpaths #select -| tarslip.py:15:1:15:3 | ControlFlowNode for tar | tarslip.py:14:7:14:39 | ControlFlowNode for Attribute() | tarslip.py:15:1:15:3 | ControlFlowNode for tar | This file extraction depends on a $@. | tarslip.py:14:7:14:39 | ControlFlowNode for Attribute() | potentially untrusted source | -| tarslip.py:20:17:20:21 | ControlFlowNode for entry | tarslip.py:18:7:18:39 | ControlFlowNode for Attribute() | tarslip.py:20:17:20:21 | ControlFlowNode for entry | This file extraction depends on a $@. | tarslip.py:18:7:18:39 | ControlFlowNode for Attribute() | potentially untrusted source | -| tarslip.py:39:17:39:21 | ControlFlowNode for entry | tarslip.py:35:7:35:39 | ControlFlowNode for Attribute() | tarslip.py:39:17:39:21 | ControlFlowNode for entry | This file extraction depends on a $@. | tarslip.py:35:7:35:39 | ControlFlowNode for Attribute() | potentially untrusted source | -| tarslip.py:43:24:43:26 | ControlFlowNode for tar | tarslip.py:42:7:42:39 | ControlFlowNode for Attribute() | tarslip.py:43:24:43:26 | ControlFlowNode for tar | This file extraction depends on a $@. | tarslip.py:42:7:42:39 | ControlFlowNode for Attribute() | potentially untrusted source | -| tarslip.py:61:21:61:25 | ControlFlowNode for entry | tarslip.py:58:7:58:39 | ControlFlowNode for Attribute() | tarslip.py:61:21:61:25 | ControlFlowNode for entry | This file extraction depends on a $@. | tarslip.py:58:7:58:39 | ControlFlowNode for Attribute() | potentially untrusted source | -| tarslip.py:91:1:91:3 | ControlFlowNode for tar | tarslip.py:90:7:90:39 | ControlFlowNode for Attribute() | tarslip.py:91:1:91:3 | ControlFlowNode for tar | This file extraction depends on a $@. | tarslip.py:90:7:90:39 | ControlFlowNode for Attribute() | potentially untrusted source | -| tarslip.py:96:17:96:21 | ControlFlowNode for entry | tarslip.py:94:7:94:39 | ControlFlowNode for Attribute() | tarslip.py:96:17:96:21 | ControlFlowNode for entry | This file extraction depends on a $@. | tarslip.py:94:7:94:39 | ControlFlowNode for Attribute() | potentially untrusted source | -| tarslip.py:110:1:110:3 | ControlFlowNode for tar | tarslip.py:109:7:109:39 | ControlFlowNode for Attribute() | tarslip.py:110:1:110:3 | ControlFlowNode for tar | This file extraction depends on a $@. | tarslip.py:109:7:109:39 | ControlFlowNode for Attribute() | potentially untrusted source | -| tarslip.py:113:24:113:26 | ControlFlowNode for tar | tarslip.py:112:7:112:39 | ControlFlowNode for Attribute() | tarslip.py:113:24:113:26 | ControlFlowNode for tar | This file extraction depends on a $@. | tarslip.py:112:7:112:39 | ControlFlowNode for Attribute() | potentially untrusted source | +| tarslip.py:15:1:15:3 | tar | tarslip.py:14:7:14:39 | After Attribute() | tarslip.py:15:1:15:3 | tar | This file extraction depends on a $@. | tarslip.py:14:7:14:39 | After Attribute() | potentially untrusted source | +| tarslip.py:20:17:20:21 | entry | tarslip.py:18:7:18:39 | After Attribute() | tarslip.py:20:17:20:21 | entry | This file extraction depends on a $@. | tarslip.py:18:7:18:39 | After Attribute() | potentially untrusted source | +| tarslip.py:39:17:39:21 | entry | tarslip.py:35:7:35:39 | After Attribute() | tarslip.py:39:17:39:21 | entry | This file extraction depends on a $@. | tarslip.py:35:7:35:39 | After Attribute() | potentially untrusted source | +| tarslip.py:43:24:43:26 | tar | tarslip.py:42:7:42:39 | After Attribute() | tarslip.py:43:24:43:26 | tar | This file extraction depends on a $@. | tarslip.py:42:7:42:39 | After Attribute() | potentially untrusted source | +| tarslip.py:61:21:61:25 | entry | tarslip.py:58:7:58:39 | After Attribute() | tarslip.py:61:21:61:25 | entry | This file extraction depends on a $@. | tarslip.py:58:7:58:39 | After Attribute() | potentially untrusted source | +| tarslip.py:91:1:91:3 | tar | tarslip.py:90:7:90:39 | After Attribute() | tarslip.py:91:1:91:3 | tar | This file extraction depends on a $@. | tarslip.py:90:7:90:39 | After Attribute() | potentially untrusted source | +| tarslip.py:96:17:96:21 | entry | tarslip.py:94:7:94:39 | After Attribute() | tarslip.py:96:17:96:21 | entry | This file extraction depends on a $@. | tarslip.py:94:7:94:39 | After Attribute() | potentially untrusted source | +| tarslip.py:110:1:110:3 | tar | tarslip.py:109:7:109:39 | After Attribute() | tarslip.py:110:1:110:3 | tar | This file extraction depends on a $@. | tarslip.py:109:7:109:39 | After Attribute() | potentially untrusted source | +| tarslip.py:113:24:113:26 | tar | tarslip.py:112:7:112:39 | After Attribute() | tarslip.py:113:24:113:26 | tar | This file extraction depends on a $@. | tarslip.py:112:7:112:39 | After Attribute() | potentially untrusted source | diff --git a/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/FullServerSideRequestForgery.expected b/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/FullServerSideRequestForgery.expected index 7434eca6978b..e84845da7b2b 100644 --- a/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/FullServerSideRequestForgery.expected +++ b/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/FullServerSideRequestForgery.expected @@ -1,153 +1,153 @@ #select -| full_partial_test.py:11:5:11:28 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:11:18:11:27 | ControlFlowNode for user_input | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| full_partial_test.py:15:5:15:21 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:15:18:15:20 | ControlFlowNode for url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| full_partial_test.py:22:5:22:21 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:22:18:22:20 | ControlFlowNode for url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| full_partial_test.py:27:5:27:21 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:27:18:27:20 | ControlFlowNode for url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| full_partial_test.py:47:5:47:21 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:47:18:47:20 | ControlFlowNode for url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| full_partial_test.py:51:5:51:21 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:51:18:51:20 | ControlFlowNode for url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| full_partial_test.py:55:5:55:21 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:55:18:55:20 | ControlFlowNode for url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| full_partial_test.py:59:5:59:21 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:59:18:59:20 | ControlFlowNode for url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| full_partial_test.py:63:5:63:21 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:63:18:63:20 | ControlFlowNode for url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| full_partial_test.py:72:5:72:21 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:72:18:72:20 | ControlFlowNode for url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| full_partial_test.py:76:5:76:21 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:76:18:76:20 | ControlFlowNode for url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| full_partial_test.py:89:5:89:21 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:89:18:89:20 | ControlFlowNode for url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| full_partial_test.py:93:5:93:21 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:93:18:93:20 | ControlFlowNode for url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| full_partial_test.py:97:5:97:21 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:97:18:97:20 | ControlFlowNode for url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| test_azure_client.py:16:5:16:59 | ControlFlowNode for SecretClient() | test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | test_azure_client.py:16:28:16:35 | ControlFlowNode for full_url | The full URL of this request depends on a $@. | test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | user-provided value | -| test_azure_client.py:18:5:18:43 | ControlFlowNode for Attribute() | test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | test_azure_client.py:18:35:18:42 | ControlFlowNode for full_url | The full URL of this request depends on a $@. | test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | user-provided value | -| test_azure_client.py:20:5:20:35 | ControlFlowNode for KeyClient() | test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | test_azure_client.py:20:15:20:22 | ControlFlowNode for full_url | The full URL of this request depends on a $@. | test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | user-provided value | -| test_azure_client.py:22:5:22:85 | ControlFlowNode for Attribute() | test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | test_azure_client.py:22:54:22:61 | ControlFlowNode for full_url | The full URL of this request depends on a $@. | test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | user-provided value | -| test_azure_client.py:25:5:25:104 | ControlFlowNode for download_blob_from_url() | test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | test_azure_client.py:25:37:25:44 | ControlFlowNode for full_url | The full URL of this request depends on a $@. | test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | user-provided value | -| test_http_client.py:15:5:15:36 | ControlFlowNode for Attribute() | test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | test_http_client.py:13:27:13:37 | ControlFlowNode for unsafe_host | The full URL of this request depends on a $@. | test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| test_http_client.py:15:5:15:36 | ControlFlowNode for Attribute() | test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | test_http_client.py:15:25:15:35 | ControlFlowNode for unsafe_path | The full URL of this request depends on a $@. | test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| test_http_client.py:21:5:21:36 | ControlFlowNode for Attribute() | test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | test_http_client.py:19:27:19:37 | ControlFlowNode for unsafe_host | The full URL of this request depends on a $@. | test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| test_http_client.py:21:5:21:36 | ControlFlowNode for Attribute() | test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | test_http_client.py:21:25:21:35 | ControlFlowNode for unsafe_path | The full URL of this request depends on a $@. | test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:21:9:21:63 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:21:32:21:39 | ControlFlowNode for full_url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:37:9:37:60 | ControlFlowNode for KeyClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:37:29:37:36 | ControlFlowNode for full_url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:53:9:53:47 | ControlFlowNode for Attribute() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:53:39:53:46 | ControlFlowNode for full_url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:64:9:64:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:64:32:64:34 | ControlFlowNode for url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:71:9:71:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:71:32:71:34 | ControlFlowNode for url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:74:9:74:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:74:32:74:34 | ControlFlowNode for url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:79:9:79:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:79:32:79:34 | ControlFlowNode for url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:87:9:87:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:87:32:87:34 | ControlFlowNode for url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:90:9:90:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:90:32:90:34 | ControlFlowNode for url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:95:9:95:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:95:32:95:34 | ControlFlowNode for url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:102:9:102:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:102:32:102:34 | ControlFlowNode for url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:107:9:107:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:107:32:107:34 | ControlFlowNode for url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:110:9:110:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:110:32:110:34 | ControlFlowNode for url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:115:9:115:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:115:32:115:34 | ControlFlowNode for url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:122:9:122:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:122:32:122:34 | ControlFlowNode for url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:125:9:125:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:125:32:125:34 | ControlFlowNode for url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:132:9:132:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:132:32:132:34 | ControlFlowNode for url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_requests.py:9:5:9:28 | ControlFlowNode for Attribute() | test_requests.py:1:19:1:25 | ControlFlowNode for ImportMember | test_requests.py:9:18:9:27 | ControlFlowNode for user_input | The full URL of this request depends on a $@. | test_requests.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| test_requests.py:17:5:17:27 | ControlFlowNode for Attribute() | test_requests.py:1:19:1:25 | ControlFlowNode for ImportMember | test_requests.py:17:17:17:26 | ControlFlowNode for user_input | The full URL of this request depends on a $@. | test_requests.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| test_requests.py:22:5:22:44 | ControlFlowNode for Attribute() | test_requests.py:1:19:1:25 | ControlFlowNode for ImportMember | test_requests.py:22:34:22:43 | ControlFlowNode for user_input | The full URL of this request depends on a $@. | test_requests.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | +| full_partial_test.py:11:5:11:28 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:11:18:11:27 | user_input | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| full_partial_test.py:15:5:15:21 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:15:18:15:20 | url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| full_partial_test.py:22:5:22:21 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:22:18:22:20 | url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| full_partial_test.py:27:5:27:21 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:27:18:27:20 | url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| full_partial_test.py:47:5:47:21 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:47:18:47:20 | url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| full_partial_test.py:51:5:51:21 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:51:18:51:20 | url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| full_partial_test.py:55:5:55:21 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:55:18:55:20 | url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| full_partial_test.py:59:5:59:21 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:59:18:59:20 | url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| full_partial_test.py:63:5:63:21 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:63:18:63:20 | url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| full_partial_test.py:72:5:72:21 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:72:18:72:20 | url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| full_partial_test.py:76:5:76:21 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:76:18:76:20 | url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| full_partial_test.py:89:5:89:21 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:89:18:89:20 | url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| full_partial_test.py:93:5:93:21 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:93:18:93:20 | url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| full_partial_test.py:97:5:97:21 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:97:18:97:20 | url | The full URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| test_azure_client.py:16:5:16:59 | After SecretClient() | test_azure_client.py:6:19:6:25 | After ImportMember | test_azure_client.py:16:28:16:35 | full_url | The full URL of this request depends on a $@. | test_azure_client.py:6:19:6:25 | After ImportMember | user-provided value | +| test_azure_client.py:18:5:18:43 | After Attribute() | test_azure_client.py:6:19:6:25 | After ImportMember | test_azure_client.py:18:35:18:42 | full_url | The full URL of this request depends on a $@. | test_azure_client.py:6:19:6:25 | After ImportMember | user-provided value | +| test_azure_client.py:20:5:20:35 | After KeyClient() | test_azure_client.py:6:19:6:25 | After ImportMember | test_azure_client.py:20:15:20:22 | full_url | The full URL of this request depends on a $@. | test_azure_client.py:6:19:6:25 | After ImportMember | user-provided value | +| test_azure_client.py:22:5:22:85 | After Attribute() | test_azure_client.py:6:19:6:25 | After ImportMember | test_azure_client.py:22:54:22:61 | full_url | The full URL of this request depends on a $@. | test_azure_client.py:6:19:6:25 | After ImportMember | user-provided value | +| test_azure_client.py:25:5:25:104 | After download_blob_from_url() | test_azure_client.py:6:19:6:25 | After ImportMember | test_azure_client.py:25:37:25:44 | full_url | The full URL of this request depends on a $@. | test_azure_client.py:6:19:6:25 | After ImportMember | user-provided value | +| test_http_client.py:15:5:15:36 | After Attribute() | test_http_client.py:1:19:1:25 | After ImportMember | test_http_client.py:13:27:13:37 | unsafe_host | The full URL of this request depends on a $@. | test_http_client.py:1:19:1:25 | After ImportMember | user-provided value | +| test_http_client.py:15:5:15:36 | After Attribute() | test_http_client.py:1:19:1:25 | After ImportMember | test_http_client.py:15:25:15:35 | unsafe_path | The full URL of this request depends on a $@. | test_http_client.py:1:19:1:25 | After ImportMember | user-provided value | +| test_http_client.py:21:5:21:36 | After Attribute() | test_http_client.py:1:19:1:25 | After ImportMember | test_http_client.py:19:27:19:37 | unsafe_host | The full URL of this request depends on a $@. | test_http_client.py:1:19:1:25 | After ImportMember | user-provided value | +| test_http_client.py:21:5:21:36 | After Attribute() | test_http_client.py:1:19:1:25 | After ImportMember | test_http_client.py:21:25:21:35 | unsafe_path | The full URL of this request depends on a $@. | test_http_client.py:1:19:1:25 | After ImportMember | user-provided value | +| test_path_validation.py:21:9:21:63 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:21:32:21:39 | full_url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:37:9:37:60 | After KeyClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:37:29:37:36 | full_url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:53:9:53:47 | After Attribute() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:53:39:53:46 | full_url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:64:9:64:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:64:32:64:34 | url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:71:9:71:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:71:32:71:34 | url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:74:9:74:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:74:32:74:34 | url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:79:9:79:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:79:32:79:34 | url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:87:9:87:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:87:32:87:34 | url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:90:9:90:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:90:32:90:34 | url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:95:9:95:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:95:32:95:34 | url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:102:9:102:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:102:32:102:34 | url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:107:9:107:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:107:32:107:34 | url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:110:9:110:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:110:32:110:34 | url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:115:9:115:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:115:32:115:34 | url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:122:9:122:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:122:32:122:34 | url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:125:9:125:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:125:32:125:34 | url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:132:9:132:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:132:32:132:34 | url | The full URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_requests.py:9:5:9:28 | After Attribute() | test_requests.py:1:19:1:25 | After ImportMember | test_requests.py:9:18:9:27 | user_input | The full URL of this request depends on a $@. | test_requests.py:1:19:1:25 | After ImportMember | user-provided value | +| test_requests.py:17:5:17:27 | After Attribute() | test_requests.py:1:19:1:25 | After ImportMember | test_requests.py:17:17:17:26 | user_input | The full URL of this request depends on a $@. | test_requests.py:1:19:1:25 | After ImportMember | user-provided value | +| test_requests.py:22:5:22:44 | After Attribute() | test_requests.py:1:19:1:25 | After ImportMember | test_requests.py:22:34:22:43 | user_input | The full URL of this request depends on a $@. | test_requests.py:1:19:1:25 | After ImportMember | user-provided value | edges -| full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:1:19:1:25 | ControlFlowNode for request | provenance | | -| full_partial_test.py:1:19:1:25 | ControlFlowNode for request | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | provenance | | -| full_partial_test.py:1:19:1:25 | ControlFlowNode for request | full_partial_test.py:41:18:41:24 | ControlFlowNode for request | provenance | | -| full_partial_test.py:1:19:1:25 | ControlFlowNode for request | full_partial_test.py:66:18:66:24 | ControlFlowNode for request | provenance | | -| full_partial_test.py:1:19:1:25 | ControlFlowNode for request | full_partial_test.py:83:18:83:24 | ControlFlowNode for request | provenance | | -| full_partial_test.py:7:5:7:14 | ControlFlowNode for user_input | full_partial_test.py:11:18:11:27 | ControlFlowNode for user_input | provenance | | -| full_partial_test.py:7:5:7:14 | ControlFlowNode for user_input | full_partial_test.py:13:5:13:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:7:5:7:14 | ControlFlowNode for user_input | full_partial_test.py:20:5:20:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:7:5:7:14 | ControlFlowNode for user_input | full_partial_test.py:25:5:25:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:7:5:7:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| full_partial_test.py:13:5:13:7 | ControlFlowNode for url | full_partial_test.py:15:18:15:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:20:5:20:7 | ControlFlowNode for url | full_partial_test.py:22:18:22:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:25:5:25:7 | ControlFlowNode for url | full_partial_test.py:27:18:27:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:41:5:41:14 | ControlFlowNode for user_input | full_partial_test.py:45:5:45:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:41:5:41:14 | ControlFlowNode for user_input | full_partial_test.py:49:5:49:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:41:5:41:14 | ControlFlowNode for user_input | full_partial_test.py:53:5:53:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:41:5:41:14 | ControlFlowNode for user_input | full_partial_test.py:57:5:57:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:41:5:41:14 | ControlFlowNode for user_input | full_partial_test.py:61:5:61:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:41:18:41:24 | ControlFlowNode for request | full_partial_test.py:41:5:41:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| full_partial_test.py:45:5:45:7 | ControlFlowNode for url | full_partial_test.py:47:18:47:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:49:5:49:7 | ControlFlowNode for url | full_partial_test.py:51:18:51:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:53:5:53:7 | ControlFlowNode for url | full_partial_test.py:55:18:55:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:57:5:57:7 | ControlFlowNode for url | full_partial_test.py:59:18:59:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:61:5:61:7 | ControlFlowNode for url | full_partial_test.py:63:18:63:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:66:5:66:14 | ControlFlowNode for user_input | full_partial_test.py:70:5:70:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:66:5:66:14 | ControlFlowNode for user_input | full_partial_test.py:74:5:74:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:66:18:66:24 | ControlFlowNode for request | full_partial_test.py:66:5:66:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| full_partial_test.py:70:5:70:7 | ControlFlowNode for url | full_partial_test.py:72:18:72:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:74:5:74:7 | ControlFlowNode for url | full_partial_test.py:76:18:76:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:83:5:83:14 | ControlFlowNode for user_input | full_partial_test.py:87:5:87:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:83:5:83:14 | ControlFlowNode for user_input | full_partial_test.py:91:5:91:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:83:5:83:14 | ControlFlowNode for user_input | full_partial_test.py:95:5:95:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:83:18:83:24 | ControlFlowNode for request | full_partial_test.py:83:5:83:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| full_partial_test.py:87:5:87:7 | ControlFlowNode for url | full_partial_test.py:89:18:89:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:91:5:91:7 | ControlFlowNode for url | full_partial_test.py:93:18:93:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:95:5:95:7 | ControlFlowNode for url | full_partial_test.py:97:18:97:20 | ControlFlowNode for url | provenance | | -| test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | test_azure_client.py:6:19:6:25 | ControlFlowNode for request | provenance | | -| test_azure_client.py:6:19:6:25 | ControlFlowNode for request | test_azure_client.py:9:18:9:24 | ControlFlowNode for request | provenance | | -| test_azure_client.py:6:19:6:25 | ControlFlowNode for request | test_azure_client.py:10:19:10:25 | ControlFlowNode for request | provenance | | -| test_azure_client.py:9:18:9:24 | ControlFlowNode for request | test_azure_client.py:10:5:10:15 | ControlFlowNode for user_input2 | provenance | AdditionalTaintStep | -| test_azure_client.py:10:5:10:15 | ControlFlowNode for user_input2 | test_azure_client.py:13:5:13:12 | ControlFlowNode for full_url | provenance | | -| test_azure_client.py:10:19:10:25 | ControlFlowNode for request | test_azure_client.py:10:5:10:15 | ControlFlowNode for user_input2 | provenance | AdditionalTaintStep | -| test_azure_client.py:13:5:13:12 | ControlFlowNode for full_url | test_azure_client.py:16:28:16:35 | ControlFlowNode for full_url | provenance | Sink:MaD:2 | -| test_azure_client.py:13:5:13:12 | ControlFlowNode for full_url | test_azure_client.py:18:35:18:42 | ControlFlowNode for full_url | provenance | Sink:MaD:4 | -| test_azure_client.py:13:5:13:12 | ControlFlowNode for full_url | test_azure_client.py:20:15:20:22 | ControlFlowNode for full_url | provenance | Sink:MaD:1 | -| test_azure_client.py:13:5:13:12 | ControlFlowNode for full_url | test_azure_client.py:22:54:22:61 | ControlFlowNode for full_url | provenance | Sink:MaD:3 | -| test_azure_client.py:13:5:13:12 | ControlFlowNode for full_url | test_azure_client.py:25:37:25:44 | ControlFlowNode for full_url | provenance | Sink:MaD:5 | -| test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | test_http_client.py:1:19:1:25 | ControlFlowNode for request | provenance | | -| test_http_client.py:1:19:1:25 | ControlFlowNode for request | test_http_client.py:9:19:9:25 | ControlFlowNode for request | provenance | | -| test_http_client.py:1:19:1:25 | ControlFlowNode for request | test_http_client.py:10:19:10:25 | ControlFlowNode for request | provenance | | -| test_http_client.py:9:5:9:15 | ControlFlowNode for unsafe_host | test_http_client.py:13:27:13:37 | ControlFlowNode for unsafe_host | provenance | | -| test_http_client.py:9:5:9:15 | ControlFlowNode for unsafe_host | test_http_client.py:19:27:19:37 | ControlFlowNode for unsafe_host | provenance | | -| test_http_client.py:9:5:9:15 | ControlFlowNode for unsafe_host | test_http_client.py:28:27:28:37 | ControlFlowNode for unsafe_host | provenance | | -| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:9:5:9:15 | ControlFlowNode for unsafe_host | provenance | AdditionalTaintStep | -| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:10:5:10:15 | ControlFlowNode for unsafe_path | provenance | AdditionalTaintStep | -| test_http_client.py:10:5:10:15 | ControlFlowNode for unsafe_path | test_http_client.py:15:25:15:35 | ControlFlowNode for unsafe_path | provenance | | -| test_http_client.py:10:5:10:15 | ControlFlowNode for unsafe_path | test_http_client.py:21:25:21:35 | ControlFlowNode for unsafe_path | provenance | | -| test_http_client.py:10:5:10:15 | ControlFlowNode for unsafe_path | test_http_client.py:34:25:34:35 | ControlFlowNode for unsafe_path | provenance | | -| test_http_client.py:10:19:10:25 | ControlFlowNode for request | test_http_client.py:10:5:10:15 | ControlFlowNode for unsafe_path | provenance | AdditionalTaintStep | -| test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:5:19:5:25 | ControlFlowNode for request | provenance | | -| test_path_validation.py:5:19:5:25 | ControlFlowNode for request | test_path_validation.py:8:18:8:24 | ControlFlowNode for request | provenance | | -| test_path_validation.py:5:19:5:25 | ControlFlowNode for request | test_path_validation.py:9:19:9:25 | ControlFlowNode for request | provenance | | -| test_path_validation.py:5:19:5:25 | ControlFlowNode for request | test_path_validation.py:24:18:24:24 | ControlFlowNode for request | provenance | | -| test_path_validation.py:5:19:5:25 | ControlFlowNode for request | test_path_validation.py:25:19:25:25 | ControlFlowNode for request | provenance | | -| test_path_validation.py:5:19:5:25 | ControlFlowNode for request | test_path_validation.py:40:18:40:24 | ControlFlowNode for request | provenance | | -| test_path_validation.py:5:19:5:25 | ControlFlowNode for request | test_path_validation.py:41:19:41:25 | ControlFlowNode for request | provenance | | -| test_path_validation.py:5:19:5:25 | ControlFlowNode for request | test_path_validation.py:57:18:57:24 | ControlFlowNode for request | provenance | | -| test_path_validation.py:8:18:8:24 | ControlFlowNode for request | test_path_validation.py:9:5:9:15 | ControlFlowNode for user_input2 | provenance | AdditionalTaintStep | -| test_path_validation.py:9:5:9:15 | ControlFlowNode for user_input2 | test_path_validation.py:11:5:11:12 | ControlFlowNode for full_url | provenance | | -| test_path_validation.py:9:19:9:25 | ControlFlowNode for request | test_path_validation.py:9:5:9:15 | ControlFlowNode for user_input2 | provenance | AdditionalTaintStep | -| test_path_validation.py:11:5:11:12 | ControlFlowNode for full_url | test_path_validation.py:21:32:21:39 | ControlFlowNode for full_url | provenance | Sink:MaD:2 | -| test_path_validation.py:24:18:24:24 | ControlFlowNode for request | test_path_validation.py:25:5:25:15 | ControlFlowNode for user_input2 | provenance | AdditionalTaintStep | -| test_path_validation.py:25:5:25:15 | ControlFlowNode for user_input2 | test_path_validation.py:27:5:27:12 | ControlFlowNode for full_url | provenance | | -| test_path_validation.py:25:19:25:25 | ControlFlowNode for request | test_path_validation.py:25:5:25:15 | ControlFlowNode for user_input2 | provenance | AdditionalTaintStep | -| test_path_validation.py:27:5:27:12 | ControlFlowNode for full_url | test_path_validation.py:37:29:37:36 | ControlFlowNode for full_url | provenance | Sink:MaD:1 | -| test_path_validation.py:40:18:40:24 | ControlFlowNode for request | test_path_validation.py:41:5:41:15 | ControlFlowNode for user_input2 | provenance | AdditionalTaintStep | -| test_path_validation.py:41:5:41:15 | ControlFlowNode for user_input2 | test_path_validation.py:43:5:43:12 | ControlFlowNode for full_url | provenance | | -| test_path_validation.py:41:19:41:25 | ControlFlowNode for request | test_path_validation.py:41:5:41:15 | ControlFlowNode for user_input2 | provenance | AdditionalTaintStep | -| test_path_validation.py:43:5:43:12 | ControlFlowNode for full_url | test_path_validation.py:53:39:53:46 | ControlFlowNode for full_url | provenance | Sink:MaD:4 | -| test_path_validation.py:57:5:57:14 | ControlFlowNode for user_input | test_path_validation.py:61:5:61:7 | ControlFlowNode for url | provenance | | -| test_path_validation.py:57:18:57:24 | ControlFlowNode for request | test_path_validation.py:57:5:57:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:64:32:64:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:71:32:71:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:74:32:74:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:79:32:79:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:87:32:87:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:90:32:90:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:95:32:95:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:102:32:102:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:107:32:107:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:110:32:110:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:115:32:115:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:122:32:122:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:125:32:125:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:132:32:132:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_requests.py:1:19:1:25 | ControlFlowNode for ImportMember | test_requests.py:1:19:1:25 | ControlFlowNode for request | provenance | | -| test_requests.py:1:19:1:25 | ControlFlowNode for request | test_requests.py:7:18:7:24 | ControlFlowNode for request | provenance | | -| test_requests.py:1:19:1:25 | ControlFlowNode for request | test_requests.py:14:18:14:24 | ControlFlowNode for request | provenance | | -| test_requests.py:1:19:1:25 | ControlFlowNode for request | test_requests.py:20:18:20:24 | ControlFlowNode for request | provenance | | -| test_requests.py:7:5:7:14 | ControlFlowNode for user_input | test_requests.py:9:18:9:27 | ControlFlowNode for user_input | provenance | | -| test_requests.py:7:18:7:24 | ControlFlowNode for request | test_requests.py:7:5:7:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| test_requests.py:14:5:14:14 | ControlFlowNode for user_input | test_requests.py:17:17:17:26 | ControlFlowNode for user_input | provenance | | -| test_requests.py:14:18:14:24 | ControlFlowNode for request | test_requests.py:14:5:14:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| test_requests.py:20:5:20:14 | ControlFlowNode for user_input | test_requests.py:22:34:22:43 | ControlFlowNode for user_input | provenance | | -| test_requests.py:20:18:20:24 | ControlFlowNode for request | test_requests.py:20:5:20:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | +| full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:1:19:1:25 | request | provenance | | +| full_partial_test.py:1:19:1:25 | request | full_partial_test.py:7:18:7:24 | request | provenance | | +| full_partial_test.py:1:19:1:25 | request | full_partial_test.py:41:18:41:24 | request | provenance | | +| full_partial_test.py:1:19:1:25 | request | full_partial_test.py:66:18:66:24 | request | provenance | | +| full_partial_test.py:1:19:1:25 | request | full_partial_test.py:83:18:83:24 | request | provenance | | +| full_partial_test.py:7:5:7:14 | user_input | full_partial_test.py:11:18:11:27 | user_input | provenance | | +| full_partial_test.py:7:5:7:14 | user_input | full_partial_test.py:13:5:13:7 | url | provenance | | +| full_partial_test.py:7:5:7:14 | user_input | full_partial_test.py:20:5:20:7 | url | provenance | | +| full_partial_test.py:7:5:7:14 | user_input | full_partial_test.py:25:5:25:7 | url | provenance | | +| full_partial_test.py:7:18:7:24 | request | full_partial_test.py:7:5:7:14 | user_input | provenance | AdditionalTaintStep | +| full_partial_test.py:13:5:13:7 | url | full_partial_test.py:15:18:15:20 | url | provenance | | +| full_partial_test.py:20:5:20:7 | url | full_partial_test.py:22:18:22:20 | url | provenance | | +| full_partial_test.py:25:5:25:7 | url | full_partial_test.py:27:18:27:20 | url | provenance | | +| full_partial_test.py:41:5:41:14 | user_input | full_partial_test.py:45:5:45:7 | url | provenance | | +| full_partial_test.py:41:5:41:14 | user_input | full_partial_test.py:49:5:49:7 | url | provenance | | +| full_partial_test.py:41:5:41:14 | user_input | full_partial_test.py:53:5:53:7 | url | provenance | | +| full_partial_test.py:41:5:41:14 | user_input | full_partial_test.py:57:5:57:7 | url | provenance | | +| full_partial_test.py:41:5:41:14 | user_input | full_partial_test.py:61:5:61:7 | url | provenance | | +| full_partial_test.py:41:18:41:24 | request | full_partial_test.py:41:5:41:14 | user_input | provenance | AdditionalTaintStep | +| full_partial_test.py:45:5:45:7 | url | full_partial_test.py:47:18:47:20 | url | provenance | | +| full_partial_test.py:49:5:49:7 | url | full_partial_test.py:51:18:51:20 | url | provenance | | +| full_partial_test.py:53:5:53:7 | url | full_partial_test.py:55:18:55:20 | url | provenance | | +| full_partial_test.py:57:5:57:7 | url | full_partial_test.py:59:18:59:20 | url | provenance | | +| full_partial_test.py:61:5:61:7 | url | full_partial_test.py:63:18:63:20 | url | provenance | | +| full_partial_test.py:66:5:66:14 | user_input | full_partial_test.py:70:5:70:7 | url | provenance | | +| full_partial_test.py:66:5:66:14 | user_input | full_partial_test.py:74:5:74:7 | url | provenance | | +| full_partial_test.py:66:18:66:24 | request | full_partial_test.py:66:5:66:14 | user_input | provenance | AdditionalTaintStep | +| full_partial_test.py:70:5:70:7 | url | full_partial_test.py:72:18:72:20 | url | provenance | | +| full_partial_test.py:74:5:74:7 | url | full_partial_test.py:76:18:76:20 | url | provenance | | +| full_partial_test.py:83:5:83:14 | user_input | full_partial_test.py:87:5:87:7 | url | provenance | | +| full_partial_test.py:83:5:83:14 | user_input | full_partial_test.py:91:5:91:7 | url | provenance | | +| full_partial_test.py:83:5:83:14 | user_input | full_partial_test.py:95:5:95:7 | url | provenance | | +| full_partial_test.py:83:18:83:24 | request | full_partial_test.py:83:5:83:14 | user_input | provenance | AdditionalTaintStep | +| full_partial_test.py:87:5:87:7 | url | full_partial_test.py:89:18:89:20 | url | provenance | | +| full_partial_test.py:91:5:91:7 | url | full_partial_test.py:93:18:93:20 | url | provenance | | +| full_partial_test.py:95:5:95:7 | url | full_partial_test.py:97:18:97:20 | url | provenance | | +| test_azure_client.py:6:19:6:25 | After ImportMember | test_azure_client.py:6:19:6:25 | request | provenance | | +| test_azure_client.py:6:19:6:25 | request | test_azure_client.py:9:18:9:24 | request | provenance | | +| test_azure_client.py:6:19:6:25 | request | test_azure_client.py:10:19:10:25 | request | provenance | | +| test_azure_client.py:9:18:9:24 | request | test_azure_client.py:10:5:10:15 | user_input2 | provenance | AdditionalTaintStep | +| test_azure_client.py:10:5:10:15 | user_input2 | test_azure_client.py:13:5:13:12 | full_url | provenance | | +| test_azure_client.py:10:19:10:25 | request | test_azure_client.py:10:5:10:15 | user_input2 | provenance | AdditionalTaintStep | +| test_azure_client.py:13:5:13:12 | full_url | test_azure_client.py:16:28:16:35 | full_url | provenance | Sink:MaD:2 | +| test_azure_client.py:13:5:13:12 | full_url | test_azure_client.py:18:35:18:42 | full_url | provenance | Sink:MaD:4 | +| test_azure_client.py:13:5:13:12 | full_url | test_azure_client.py:20:15:20:22 | full_url | provenance | Sink:MaD:1 | +| test_azure_client.py:13:5:13:12 | full_url | test_azure_client.py:22:54:22:61 | full_url | provenance | Sink:MaD:3 | +| test_azure_client.py:13:5:13:12 | full_url | test_azure_client.py:25:37:25:44 | full_url | provenance | Sink:MaD:5 | +| test_http_client.py:1:19:1:25 | After ImportMember | test_http_client.py:1:19:1:25 | request | provenance | | +| test_http_client.py:1:19:1:25 | request | test_http_client.py:9:19:9:25 | request | provenance | | +| test_http_client.py:1:19:1:25 | request | test_http_client.py:10:19:10:25 | request | provenance | | +| test_http_client.py:9:5:9:15 | unsafe_host | test_http_client.py:13:27:13:37 | unsafe_host | provenance | | +| test_http_client.py:9:5:9:15 | unsafe_host | test_http_client.py:19:27:19:37 | unsafe_host | provenance | | +| test_http_client.py:9:5:9:15 | unsafe_host | test_http_client.py:28:27:28:37 | unsafe_host | provenance | | +| test_http_client.py:9:19:9:25 | request | test_http_client.py:9:5:9:15 | unsafe_host | provenance | AdditionalTaintStep | +| test_http_client.py:9:19:9:25 | request | test_http_client.py:10:5:10:15 | unsafe_path | provenance | AdditionalTaintStep | +| test_http_client.py:10:5:10:15 | unsafe_path | test_http_client.py:15:25:15:35 | unsafe_path | provenance | | +| test_http_client.py:10:5:10:15 | unsafe_path | test_http_client.py:21:25:21:35 | unsafe_path | provenance | | +| test_http_client.py:10:5:10:15 | unsafe_path | test_http_client.py:34:25:34:35 | unsafe_path | provenance | | +| test_http_client.py:10:19:10:25 | request | test_http_client.py:10:5:10:15 | unsafe_path | provenance | AdditionalTaintStep | +| test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:5:19:5:25 | request | provenance | | +| test_path_validation.py:5:19:5:25 | request | test_path_validation.py:8:18:8:24 | request | provenance | | +| test_path_validation.py:5:19:5:25 | request | test_path_validation.py:9:19:9:25 | request | provenance | | +| test_path_validation.py:5:19:5:25 | request | test_path_validation.py:24:18:24:24 | request | provenance | | +| test_path_validation.py:5:19:5:25 | request | test_path_validation.py:25:19:25:25 | request | provenance | | +| test_path_validation.py:5:19:5:25 | request | test_path_validation.py:40:18:40:24 | request | provenance | | +| test_path_validation.py:5:19:5:25 | request | test_path_validation.py:41:19:41:25 | request | provenance | | +| test_path_validation.py:5:19:5:25 | request | test_path_validation.py:57:18:57:24 | request | provenance | | +| test_path_validation.py:8:18:8:24 | request | test_path_validation.py:9:5:9:15 | user_input2 | provenance | AdditionalTaintStep | +| test_path_validation.py:9:5:9:15 | user_input2 | test_path_validation.py:11:5:11:12 | full_url | provenance | | +| test_path_validation.py:9:19:9:25 | request | test_path_validation.py:9:5:9:15 | user_input2 | provenance | AdditionalTaintStep | +| test_path_validation.py:11:5:11:12 | full_url | test_path_validation.py:21:32:21:39 | full_url | provenance | Sink:MaD:2 | +| test_path_validation.py:24:18:24:24 | request | test_path_validation.py:25:5:25:15 | user_input2 | provenance | AdditionalTaintStep | +| test_path_validation.py:25:5:25:15 | user_input2 | test_path_validation.py:27:5:27:12 | full_url | provenance | | +| test_path_validation.py:25:19:25:25 | request | test_path_validation.py:25:5:25:15 | user_input2 | provenance | AdditionalTaintStep | +| test_path_validation.py:27:5:27:12 | full_url | test_path_validation.py:37:29:37:36 | full_url | provenance | Sink:MaD:1 | +| test_path_validation.py:40:18:40:24 | request | test_path_validation.py:41:5:41:15 | user_input2 | provenance | AdditionalTaintStep | +| test_path_validation.py:41:5:41:15 | user_input2 | test_path_validation.py:43:5:43:12 | full_url | provenance | | +| test_path_validation.py:41:19:41:25 | request | test_path_validation.py:41:5:41:15 | user_input2 | provenance | AdditionalTaintStep | +| test_path_validation.py:43:5:43:12 | full_url | test_path_validation.py:53:39:53:46 | full_url | provenance | Sink:MaD:4 | +| test_path_validation.py:57:5:57:14 | user_input | test_path_validation.py:61:5:61:7 | url | provenance | | +| test_path_validation.py:57:18:57:24 | request | test_path_validation.py:57:5:57:14 | user_input | provenance | AdditionalTaintStep | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:64:32:64:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:71:32:71:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:74:32:74:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:79:32:79:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:87:32:87:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:90:32:90:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:95:32:95:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:102:32:102:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:107:32:107:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:110:32:110:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:115:32:115:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:122:32:122:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:125:32:125:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:132:32:132:34 | url | provenance | Sink:MaD:2 | +| test_requests.py:1:19:1:25 | After ImportMember | test_requests.py:1:19:1:25 | request | provenance | | +| test_requests.py:1:19:1:25 | request | test_requests.py:7:18:7:24 | request | provenance | | +| test_requests.py:1:19:1:25 | request | test_requests.py:14:18:14:24 | request | provenance | | +| test_requests.py:1:19:1:25 | request | test_requests.py:20:18:20:24 | request | provenance | | +| test_requests.py:7:5:7:14 | user_input | test_requests.py:9:18:9:27 | user_input | provenance | | +| test_requests.py:7:18:7:24 | request | test_requests.py:7:5:7:14 | user_input | provenance | AdditionalTaintStep | +| test_requests.py:14:5:14:14 | user_input | test_requests.py:17:17:17:26 | user_input | provenance | | +| test_requests.py:14:18:14:24 | request | test_requests.py:14:5:14:14 | user_input | provenance | AdditionalTaintStep | +| test_requests.py:20:5:20:14 | user_input | test_requests.py:22:34:22:43 | user_input | provenance | | +| test_requests.py:20:18:20:24 | request | test_requests.py:20:5:20:14 | user_input | provenance | AdditionalTaintStep | models | 1 | Sink: azure.keyvault.keys.KeyClient!; Call.Argument[0,vault_url:]; request-forgery | | 2 | Sink: azure.keyvault.secrets.SecretClient!; Call.Argument[0,vault_url:]; request-forgery | @@ -155,109 +155,109 @@ models | 4 | Sink: azure.storage.fileshare.ShareFileClient!; Member[from_file_url].Argument[0,file_url:]; request-forgery | | 5 | Sink: azure; Member[storage].Member[blob].Member[download_blob_from_url].Argument[0,blob_url:]; request-forgery | nodes -| full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | -| full_partial_test.py:1:19:1:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| full_partial_test.py:7:5:7:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| full_partial_test.py:7:18:7:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| full_partial_test.py:11:18:11:27 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| full_partial_test.py:13:5:13:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:15:18:15:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:20:5:20:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:22:18:22:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:25:5:25:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:27:18:27:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:41:5:41:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| full_partial_test.py:41:18:41:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| full_partial_test.py:45:5:45:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:47:18:47:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:49:5:49:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:51:18:51:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:53:5:53:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:55:18:55:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:57:5:57:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:59:18:59:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:61:5:61:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:63:18:63:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:66:5:66:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| full_partial_test.py:66:18:66:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| full_partial_test.py:70:5:70:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:72:18:72:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:74:5:74:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:76:18:76:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:83:5:83:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| full_partial_test.py:83:18:83:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| full_partial_test.py:87:5:87:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:89:18:89:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:91:5:91:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:93:18:93:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:95:5:95:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:97:18:97:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | -| test_azure_client.py:6:19:6:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_azure_client.py:9:18:9:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_azure_client.py:10:5:10:15 | ControlFlowNode for user_input2 | semmle.label | ControlFlowNode for user_input2 | -| test_azure_client.py:10:19:10:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_azure_client.py:13:5:13:12 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_azure_client.py:16:28:16:35 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_azure_client.py:18:35:18:42 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_azure_client.py:20:15:20:22 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_azure_client.py:22:54:22:61 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_azure_client.py:25:37:25:44 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | -| test_http_client.py:1:19:1:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_http_client.py:9:5:9:15 | ControlFlowNode for unsafe_host | semmle.label | ControlFlowNode for unsafe_host | -| test_http_client.py:9:19:9:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_http_client.py:10:5:10:15 | ControlFlowNode for unsafe_path | semmle.label | ControlFlowNode for unsafe_path | -| test_http_client.py:10:19:10:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_http_client.py:13:27:13:37 | ControlFlowNode for unsafe_host | semmle.label | ControlFlowNode for unsafe_host | -| test_http_client.py:15:25:15:35 | ControlFlowNode for unsafe_path | semmle.label | ControlFlowNode for unsafe_path | -| test_http_client.py:19:27:19:37 | ControlFlowNode for unsafe_host | semmle.label | ControlFlowNode for unsafe_host | -| test_http_client.py:21:25:21:35 | ControlFlowNode for unsafe_path | semmle.label | ControlFlowNode for unsafe_path | -| test_http_client.py:28:27:28:37 | ControlFlowNode for unsafe_host | semmle.label | ControlFlowNode for unsafe_host | -| test_http_client.py:34:25:34:35 | ControlFlowNode for unsafe_path | semmle.label | ControlFlowNode for unsafe_path | -| test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | -| test_path_validation.py:5:19:5:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_path_validation.py:8:18:8:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_path_validation.py:9:5:9:15 | ControlFlowNode for user_input2 | semmle.label | ControlFlowNode for user_input2 | -| test_path_validation.py:9:19:9:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_path_validation.py:11:5:11:12 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_path_validation.py:21:32:21:39 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_path_validation.py:24:18:24:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_path_validation.py:25:5:25:15 | ControlFlowNode for user_input2 | semmle.label | ControlFlowNode for user_input2 | -| test_path_validation.py:25:19:25:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_path_validation.py:27:5:27:12 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_path_validation.py:37:29:37:36 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_path_validation.py:40:18:40:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_path_validation.py:41:5:41:15 | ControlFlowNode for user_input2 | semmle.label | ControlFlowNode for user_input2 | -| test_path_validation.py:41:19:41:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_path_validation.py:43:5:43:12 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_path_validation.py:53:39:53:46 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_path_validation.py:57:5:57:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| test_path_validation.py:57:18:57:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:64:32:64:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:71:32:71:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:74:32:74:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:79:32:79:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:87:32:87:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:90:32:90:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:95:32:95:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:102:32:102:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:107:32:107:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:110:32:110:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:115:32:115:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:122:32:122:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:125:32:125:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:132:32:132:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_requests.py:1:19:1:25 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | -| test_requests.py:1:19:1:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_requests.py:7:5:7:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| test_requests.py:7:18:7:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_requests.py:9:18:9:27 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| test_requests.py:14:5:14:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| test_requests.py:14:18:14:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_requests.py:17:17:17:26 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| test_requests.py:20:5:20:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| test_requests.py:20:18:20:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_requests.py:22:34:22:43 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | +| full_partial_test.py:1:19:1:25 | After ImportMember | semmle.label | After ImportMember | +| full_partial_test.py:1:19:1:25 | request | semmle.label | request | +| full_partial_test.py:7:5:7:14 | user_input | semmle.label | user_input | +| full_partial_test.py:7:18:7:24 | request | semmle.label | request | +| full_partial_test.py:11:18:11:27 | user_input | semmle.label | user_input | +| full_partial_test.py:13:5:13:7 | url | semmle.label | url | +| full_partial_test.py:15:18:15:20 | url | semmle.label | url | +| full_partial_test.py:20:5:20:7 | url | semmle.label | url | +| full_partial_test.py:22:18:22:20 | url | semmle.label | url | +| full_partial_test.py:25:5:25:7 | url | semmle.label | url | +| full_partial_test.py:27:18:27:20 | url | semmle.label | url | +| full_partial_test.py:41:5:41:14 | user_input | semmle.label | user_input | +| full_partial_test.py:41:18:41:24 | request | semmle.label | request | +| full_partial_test.py:45:5:45:7 | url | semmle.label | url | +| full_partial_test.py:47:18:47:20 | url | semmle.label | url | +| full_partial_test.py:49:5:49:7 | url | semmle.label | url | +| full_partial_test.py:51:18:51:20 | url | semmle.label | url | +| full_partial_test.py:53:5:53:7 | url | semmle.label | url | +| full_partial_test.py:55:18:55:20 | url | semmle.label | url | +| full_partial_test.py:57:5:57:7 | url | semmle.label | url | +| full_partial_test.py:59:18:59:20 | url | semmle.label | url | +| full_partial_test.py:61:5:61:7 | url | semmle.label | url | +| full_partial_test.py:63:18:63:20 | url | semmle.label | url | +| full_partial_test.py:66:5:66:14 | user_input | semmle.label | user_input | +| full_partial_test.py:66:18:66:24 | request | semmle.label | request | +| full_partial_test.py:70:5:70:7 | url | semmle.label | url | +| full_partial_test.py:72:18:72:20 | url | semmle.label | url | +| full_partial_test.py:74:5:74:7 | url | semmle.label | url | +| full_partial_test.py:76:18:76:20 | url | semmle.label | url | +| full_partial_test.py:83:5:83:14 | user_input | semmle.label | user_input | +| full_partial_test.py:83:18:83:24 | request | semmle.label | request | +| full_partial_test.py:87:5:87:7 | url | semmle.label | url | +| full_partial_test.py:89:18:89:20 | url | semmle.label | url | +| full_partial_test.py:91:5:91:7 | url | semmle.label | url | +| full_partial_test.py:93:18:93:20 | url | semmle.label | url | +| full_partial_test.py:95:5:95:7 | url | semmle.label | url | +| full_partial_test.py:97:18:97:20 | url | semmle.label | url | +| test_azure_client.py:6:19:6:25 | After ImportMember | semmle.label | After ImportMember | +| test_azure_client.py:6:19:6:25 | request | semmle.label | request | +| test_azure_client.py:9:18:9:24 | request | semmle.label | request | +| test_azure_client.py:10:5:10:15 | user_input2 | semmle.label | user_input2 | +| test_azure_client.py:10:19:10:25 | request | semmle.label | request | +| test_azure_client.py:13:5:13:12 | full_url | semmle.label | full_url | +| test_azure_client.py:16:28:16:35 | full_url | semmle.label | full_url | +| test_azure_client.py:18:35:18:42 | full_url | semmle.label | full_url | +| test_azure_client.py:20:15:20:22 | full_url | semmle.label | full_url | +| test_azure_client.py:22:54:22:61 | full_url | semmle.label | full_url | +| test_azure_client.py:25:37:25:44 | full_url | semmle.label | full_url | +| test_http_client.py:1:19:1:25 | After ImportMember | semmle.label | After ImportMember | +| test_http_client.py:1:19:1:25 | request | semmle.label | request | +| test_http_client.py:9:5:9:15 | unsafe_host | semmle.label | unsafe_host | +| test_http_client.py:9:19:9:25 | request | semmle.label | request | +| test_http_client.py:10:5:10:15 | unsafe_path | semmle.label | unsafe_path | +| test_http_client.py:10:19:10:25 | request | semmle.label | request | +| test_http_client.py:13:27:13:37 | unsafe_host | semmle.label | unsafe_host | +| test_http_client.py:15:25:15:35 | unsafe_path | semmle.label | unsafe_path | +| test_http_client.py:19:27:19:37 | unsafe_host | semmle.label | unsafe_host | +| test_http_client.py:21:25:21:35 | unsafe_path | semmle.label | unsafe_path | +| test_http_client.py:28:27:28:37 | unsafe_host | semmle.label | unsafe_host | +| test_http_client.py:34:25:34:35 | unsafe_path | semmle.label | unsafe_path | +| test_path_validation.py:5:19:5:25 | After ImportMember | semmle.label | After ImportMember | +| test_path_validation.py:5:19:5:25 | request | semmle.label | request | +| test_path_validation.py:8:18:8:24 | request | semmle.label | request | +| test_path_validation.py:9:5:9:15 | user_input2 | semmle.label | user_input2 | +| test_path_validation.py:9:19:9:25 | request | semmle.label | request | +| test_path_validation.py:11:5:11:12 | full_url | semmle.label | full_url | +| test_path_validation.py:21:32:21:39 | full_url | semmle.label | full_url | +| test_path_validation.py:24:18:24:24 | request | semmle.label | request | +| test_path_validation.py:25:5:25:15 | user_input2 | semmle.label | user_input2 | +| test_path_validation.py:25:19:25:25 | request | semmle.label | request | +| test_path_validation.py:27:5:27:12 | full_url | semmle.label | full_url | +| test_path_validation.py:37:29:37:36 | full_url | semmle.label | full_url | +| test_path_validation.py:40:18:40:24 | request | semmle.label | request | +| test_path_validation.py:41:5:41:15 | user_input2 | semmle.label | user_input2 | +| test_path_validation.py:41:19:41:25 | request | semmle.label | request | +| test_path_validation.py:43:5:43:12 | full_url | semmle.label | full_url | +| test_path_validation.py:53:39:53:46 | full_url | semmle.label | full_url | +| test_path_validation.py:57:5:57:14 | user_input | semmle.label | user_input | +| test_path_validation.py:57:18:57:24 | request | semmle.label | request | +| test_path_validation.py:61:5:61:7 | url | semmle.label | url | +| test_path_validation.py:64:32:64:34 | url | semmle.label | url | +| test_path_validation.py:71:32:71:34 | url | semmle.label | url | +| test_path_validation.py:74:32:74:34 | url | semmle.label | url | +| test_path_validation.py:79:32:79:34 | url | semmle.label | url | +| test_path_validation.py:87:32:87:34 | url | semmle.label | url | +| test_path_validation.py:90:32:90:34 | url | semmle.label | url | +| test_path_validation.py:95:32:95:34 | url | semmle.label | url | +| test_path_validation.py:102:32:102:34 | url | semmle.label | url | +| test_path_validation.py:107:32:107:34 | url | semmle.label | url | +| test_path_validation.py:110:32:110:34 | url | semmle.label | url | +| test_path_validation.py:115:32:115:34 | url | semmle.label | url | +| test_path_validation.py:122:32:122:34 | url | semmle.label | url | +| test_path_validation.py:125:32:125:34 | url | semmle.label | url | +| test_path_validation.py:132:32:132:34 | url | semmle.label | url | +| test_requests.py:1:19:1:25 | After ImportMember | semmle.label | After ImportMember | +| test_requests.py:1:19:1:25 | request | semmle.label | request | +| test_requests.py:7:5:7:14 | user_input | semmle.label | user_input | +| test_requests.py:7:18:7:24 | request | semmle.label | request | +| test_requests.py:9:18:9:27 | user_input | semmle.label | user_input | +| test_requests.py:14:5:14:14 | user_input | semmle.label | user_input | +| test_requests.py:14:18:14:24 | request | semmle.label | request | +| test_requests.py:17:17:17:26 | user_input | semmle.label | user_input | +| test_requests.py:20:5:20:14 | user_input | semmle.label | user_input | +| test_requests.py:20:18:20:24 | request | semmle.label | request | +| test_requests.py:22:34:22:43 | user_input | semmle.label | user_input | subpaths diff --git a/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/PartialServerSideRequestForgery.expected b/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/PartialServerSideRequestForgery.expected index 0b8756071573..c6f2fe014113 100644 --- a/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/PartialServerSideRequestForgery.expected +++ b/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/PartialServerSideRequestForgery.expected @@ -1,236 +1,236 @@ #select -| full_partial_test.py:80:5:80:21 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:80:18:80:20 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| full_partial_test.py:105:5:105:21 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:105:18:105:20 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| full_partial_test.py:112:5:112:21 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:112:18:112:20 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| full_partial_test.py:119:5:119:21 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:119:18:119:20 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| full_partial_test.py:126:5:126:21 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:126:18:126:20 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| full_partial_test.py:136:5:136:21 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:136:18:136:20 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| full_partial_test.py:143:5:143:21 | ControlFlowNode for Attribute() | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:143:18:143:20 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| test_azure_client.py:15:5:15:54 | ControlFlowNode for SecretClient() | test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | test_azure_client.py:15:28:15:30 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | user-provided value | -| test_azure_client.py:17:5:17:38 | ControlFlowNode for Attribute() | test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | test_azure_client.py:17:35:17:37 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | user-provided value | -| test_azure_client.py:19:5:19:30 | ControlFlowNode for KeyClient() | test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | test_azure_client.py:19:15:19:17 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | user-provided value | -| test_azure_client.py:21:5:21:80 | ControlFlowNode for Attribute() | test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | test_azure_client.py:21:54:21:56 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | user-provided value | -| test_azure_client.py:24:5:24:100 | ControlFlowNode for download_blob_from_url() | test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | test_azure_client.py:24:37:24:39 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | user-provided value | -| test_http_client.py:25:5:25:31 | ControlFlowNode for Attribute() | test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | test_http_client.py:19:27:19:37 | ControlFlowNode for unsafe_host | Part of the URL of this request depends on a $@. | test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| test_http_client.py:30:5:30:31 | ControlFlowNode for Attribute() | test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | test_http_client.py:28:27:28:37 | ControlFlowNode for unsafe_host | Part of the URL of this request depends on a $@. | test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| test_http_client.py:34:5:34:36 | ControlFlowNode for Attribute() | test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | test_http_client.py:34:25:34:35 | ControlFlowNode for unsafe_path | Part of the URL of this request depends on a $@. | test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| test_http_client.py:39:5:39:29 | ControlFlowNode for Attribute() | test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | test_http_client.py:39:25:39:28 | ControlFlowNode for path | Part of the URL of this request depends on a $@. | test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| test_http_client.py:44:5:44:29 | ControlFlowNode for Attribute() | test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | test_http_client.py:44:25:44:28 | ControlFlowNode for path | Part of the URL of this request depends on a $@. | test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:14:9:14:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:14:32:14:34 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:16:9:16:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:16:32:16:34 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:19:9:19:63 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:19:32:19:39 | ControlFlowNode for full_url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:30:9:30:55 | ControlFlowNode for KeyClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:30:29:30:31 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:32:9:32:55 | ControlFlowNode for KeyClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:32:29:32:31 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:35:9:35:60 | ControlFlowNode for KeyClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:35:29:35:36 | ControlFlowNode for full_url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:46:9:46:42 | ControlFlowNode for Attribute() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:46:39:46:41 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:48:9:48:42 | ControlFlowNode for Attribute() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:48:39:48:41 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:51:9:51:47 | ControlFlowNode for Attribute() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:51:39:51:46 | ControlFlowNode for full_url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:66:9:66:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:66:32:66:34 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:69:9:69:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:69:32:69:34 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:76:9:76:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:76:32:76:34 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:81:9:81:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:81:32:81:34 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:85:9:85:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:85:32:85:34 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:92:9:92:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:92:32:92:34 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:97:9:97:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:97:32:97:34 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:100:9:100:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:100:32:100:34 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:105:9:105:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:105:32:105:34 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:112:9:112:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:112:32:112:34 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:117:9:117:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:117:32:117:34 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:120:9:120:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:120:32:120:34 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:127:9:127:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:127:32:127:34 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | -| test_path_validation.py:130:9:130:58 | ControlFlowNode for SecretClient() | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:130:32:130:34 | ControlFlowNode for url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | user-provided value | +| full_partial_test.py:80:5:80:21 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:80:18:80:20 | url | Part of the URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| full_partial_test.py:105:5:105:21 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:105:18:105:20 | url | Part of the URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| full_partial_test.py:112:5:112:21 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:112:18:112:20 | url | Part of the URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| full_partial_test.py:119:5:119:21 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:119:18:119:20 | url | Part of the URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| full_partial_test.py:126:5:126:21 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:126:18:126:20 | url | Part of the URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| full_partial_test.py:136:5:136:21 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:136:18:136:20 | url | Part of the URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| full_partial_test.py:143:5:143:21 | After Attribute() | full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:143:18:143:20 | url | Part of the URL of this request depends on a $@. | full_partial_test.py:1:19:1:25 | After ImportMember | user-provided value | +| test_azure_client.py:15:5:15:54 | After SecretClient() | test_azure_client.py:6:19:6:25 | After ImportMember | test_azure_client.py:15:28:15:30 | url | Part of the URL of this request depends on a $@. | test_azure_client.py:6:19:6:25 | After ImportMember | user-provided value | +| test_azure_client.py:17:5:17:38 | After Attribute() | test_azure_client.py:6:19:6:25 | After ImportMember | test_azure_client.py:17:35:17:37 | url | Part of the URL of this request depends on a $@. | test_azure_client.py:6:19:6:25 | After ImportMember | user-provided value | +| test_azure_client.py:19:5:19:30 | After KeyClient() | test_azure_client.py:6:19:6:25 | After ImportMember | test_azure_client.py:19:15:19:17 | url | Part of the URL of this request depends on a $@. | test_azure_client.py:6:19:6:25 | After ImportMember | user-provided value | +| test_azure_client.py:21:5:21:80 | After Attribute() | test_azure_client.py:6:19:6:25 | After ImportMember | test_azure_client.py:21:54:21:56 | url | Part of the URL of this request depends on a $@. | test_azure_client.py:6:19:6:25 | After ImportMember | user-provided value | +| test_azure_client.py:24:5:24:100 | After download_blob_from_url() | test_azure_client.py:6:19:6:25 | After ImportMember | test_azure_client.py:24:37:24:39 | url | Part of the URL of this request depends on a $@. | test_azure_client.py:6:19:6:25 | After ImportMember | user-provided value | +| test_http_client.py:25:5:25:31 | After Attribute() | test_http_client.py:1:19:1:25 | After ImportMember | test_http_client.py:19:27:19:37 | unsafe_host | Part of the URL of this request depends on a $@. | test_http_client.py:1:19:1:25 | After ImportMember | user-provided value | +| test_http_client.py:30:5:30:31 | After Attribute() | test_http_client.py:1:19:1:25 | After ImportMember | test_http_client.py:28:27:28:37 | unsafe_host | Part of the URL of this request depends on a $@. | test_http_client.py:1:19:1:25 | After ImportMember | user-provided value | +| test_http_client.py:34:5:34:36 | After Attribute() | test_http_client.py:1:19:1:25 | After ImportMember | test_http_client.py:34:25:34:35 | unsafe_path | Part of the URL of this request depends on a $@. | test_http_client.py:1:19:1:25 | After ImportMember | user-provided value | +| test_http_client.py:39:5:39:29 | After Attribute() | test_http_client.py:1:19:1:25 | After ImportMember | test_http_client.py:39:25:39:28 | path | Part of the URL of this request depends on a $@. | test_http_client.py:1:19:1:25 | After ImportMember | user-provided value | +| test_http_client.py:44:5:44:29 | After Attribute() | test_http_client.py:1:19:1:25 | After ImportMember | test_http_client.py:44:25:44:28 | path | Part of the URL of this request depends on a $@. | test_http_client.py:1:19:1:25 | After ImportMember | user-provided value | +| test_path_validation.py:14:9:14:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:14:32:14:34 | url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:16:9:16:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:16:32:16:34 | url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:19:9:19:63 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:19:32:19:39 | full_url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:30:9:30:55 | After KeyClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:30:29:30:31 | url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:32:9:32:55 | After KeyClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:32:29:32:31 | url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:35:9:35:60 | After KeyClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:35:29:35:36 | full_url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:46:9:46:42 | After Attribute() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:46:39:46:41 | url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:48:9:48:42 | After Attribute() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:48:39:48:41 | url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:51:9:51:47 | After Attribute() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:51:39:51:46 | full_url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:66:9:66:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:66:32:66:34 | url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:69:9:69:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:69:32:69:34 | url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:76:9:76:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:76:32:76:34 | url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:81:9:81:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:81:32:81:34 | url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:85:9:85:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:85:32:85:34 | url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:92:9:92:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:92:32:92:34 | url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:97:9:97:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:97:32:97:34 | url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:100:9:100:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:100:32:100:34 | url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:105:9:105:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:105:32:105:34 | url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:112:9:112:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:112:32:112:34 | url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:117:9:117:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:117:32:117:34 | url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:120:9:120:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:120:32:120:34 | url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:127:9:127:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:127:32:127:34 | url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | +| test_path_validation.py:130:9:130:58 | After SecretClient() | test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:130:32:130:34 | url | Part of the URL of this request depends on a $@. | test_path_validation.py:5:19:5:25 | After ImportMember | user-provided value | edges -| full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | full_partial_test.py:1:19:1:25 | ControlFlowNode for request | provenance | | -| full_partial_test.py:1:19:1:25 | ControlFlowNode for request | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | provenance | | -| full_partial_test.py:1:19:1:25 | ControlFlowNode for request | full_partial_test.py:8:17:8:23 | ControlFlowNode for request | provenance | | -| full_partial_test.py:1:19:1:25 | ControlFlowNode for request | full_partial_test.py:41:18:41:24 | ControlFlowNode for request | provenance | | -| full_partial_test.py:1:19:1:25 | ControlFlowNode for request | full_partial_test.py:42:17:42:23 | ControlFlowNode for request | provenance | | -| full_partial_test.py:1:19:1:25 | ControlFlowNode for request | full_partial_test.py:66:18:66:24 | ControlFlowNode for request | provenance | | -| full_partial_test.py:1:19:1:25 | ControlFlowNode for request | full_partial_test.py:67:17:67:23 | ControlFlowNode for request | provenance | | -| full_partial_test.py:1:19:1:25 | ControlFlowNode for request | full_partial_test.py:83:18:83:24 | ControlFlowNode for request | provenance | | -| full_partial_test.py:1:19:1:25 | ControlFlowNode for request | full_partial_test.py:84:17:84:23 | ControlFlowNode for request | provenance | | -| full_partial_test.py:1:19:1:25 | ControlFlowNode for request | full_partial_test.py:101:18:101:24 | ControlFlowNode for request | provenance | | -| full_partial_test.py:1:19:1:25 | ControlFlowNode for request | full_partial_test.py:108:18:108:24 | ControlFlowNode for request | provenance | | -| full_partial_test.py:1:19:1:25 | ControlFlowNode for request | full_partial_test.py:115:18:115:24 | ControlFlowNode for request | provenance | | -| full_partial_test.py:1:19:1:25 | ControlFlowNode for request | full_partial_test.py:122:18:122:24 | ControlFlowNode for request | provenance | | -| full_partial_test.py:1:19:1:25 | ControlFlowNode for request | full_partial_test.py:129:18:129:24 | ControlFlowNode for request | provenance | | -| full_partial_test.py:1:19:1:25 | ControlFlowNode for request | full_partial_test.py:139:18:139:24 | ControlFlowNode for request | provenance | | -| full_partial_test.py:7:5:7:14 | ControlFlowNode for user_input | full_partial_test.py:11:18:11:27 | ControlFlowNode for user_input | provenance | | -| full_partial_test.py:7:5:7:14 | ControlFlowNode for user_input | full_partial_test.py:13:5:13:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:7:5:7:14 | ControlFlowNode for user_input | full_partial_test.py:20:5:20:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:7:5:7:14 | ControlFlowNode for user_input | full_partial_test.py:25:5:25:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:7:5:7:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:8:5:8:13 | ControlFlowNode for query_val | provenance | AdditionalTaintStep | -| full_partial_test.py:8:5:8:13 | ControlFlowNode for query_val | full_partial_test.py:25:5:25:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:8:17:8:23 | ControlFlowNode for request | full_partial_test.py:8:5:8:13 | ControlFlowNode for query_val | provenance | AdditionalTaintStep | -| full_partial_test.py:13:5:13:7 | ControlFlowNode for url | full_partial_test.py:15:18:15:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:20:5:20:7 | ControlFlowNode for url | full_partial_test.py:22:18:22:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:25:5:25:7 | ControlFlowNode for url | full_partial_test.py:27:18:27:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:41:5:41:14 | ControlFlowNode for user_input | full_partial_test.py:45:5:45:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:41:5:41:14 | ControlFlowNode for user_input | full_partial_test.py:49:5:49:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:41:5:41:14 | ControlFlowNode for user_input | full_partial_test.py:53:5:53:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:41:5:41:14 | ControlFlowNode for user_input | full_partial_test.py:57:5:57:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:41:5:41:14 | ControlFlowNode for user_input | full_partial_test.py:61:5:61:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:41:18:41:24 | ControlFlowNode for request | full_partial_test.py:41:5:41:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| full_partial_test.py:41:18:41:24 | ControlFlowNode for request | full_partial_test.py:42:5:42:13 | ControlFlowNode for query_val | provenance | AdditionalTaintStep | -| full_partial_test.py:42:5:42:13 | ControlFlowNode for query_val | full_partial_test.py:53:5:53:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:42:17:42:23 | ControlFlowNode for request | full_partial_test.py:42:5:42:13 | ControlFlowNode for query_val | provenance | AdditionalTaintStep | -| full_partial_test.py:45:5:45:7 | ControlFlowNode for url | full_partial_test.py:47:18:47:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:49:5:49:7 | ControlFlowNode for url | full_partial_test.py:51:18:51:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:53:5:53:7 | ControlFlowNode for url | full_partial_test.py:55:18:55:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:57:5:57:7 | ControlFlowNode for url | full_partial_test.py:59:18:59:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:61:5:61:7 | ControlFlowNode for url | full_partial_test.py:63:18:63:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:66:5:66:14 | ControlFlowNode for user_input | full_partial_test.py:70:5:70:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:66:5:66:14 | ControlFlowNode for user_input | full_partial_test.py:74:5:74:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:66:5:66:14 | ControlFlowNode for user_input | full_partial_test.py:78:5:78:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:66:18:66:24 | ControlFlowNode for request | full_partial_test.py:66:5:66:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| full_partial_test.py:66:18:66:24 | ControlFlowNode for request | full_partial_test.py:67:5:67:13 | ControlFlowNode for query_val | provenance | AdditionalTaintStep | -| full_partial_test.py:67:5:67:13 | ControlFlowNode for query_val | full_partial_test.py:78:5:78:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:67:17:67:23 | ControlFlowNode for request | full_partial_test.py:67:5:67:13 | ControlFlowNode for query_val | provenance | AdditionalTaintStep | -| full_partial_test.py:70:5:70:7 | ControlFlowNode for url | full_partial_test.py:72:18:72:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:74:5:74:7 | ControlFlowNode for url | full_partial_test.py:76:18:76:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:78:5:78:7 | ControlFlowNode for url | full_partial_test.py:80:18:80:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:83:5:83:14 | ControlFlowNode for user_input | full_partial_test.py:87:5:87:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:83:5:83:14 | ControlFlowNode for user_input | full_partial_test.py:91:5:91:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:83:5:83:14 | ControlFlowNode for user_input | full_partial_test.py:95:5:95:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:83:18:83:24 | ControlFlowNode for request | full_partial_test.py:83:5:83:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| full_partial_test.py:83:18:83:24 | ControlFlowNode for request | full_partial_test.py:84:5:84:13 | ControlFlowNode for query_val | provenance | AdditionalTaintStep | -| full_partial_test.py:84:5:84:13 | ControlFlowNode for query_val | full_partial_test.py:95:5:95:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:84:17:84:23 | ControlFlowNode for request | full_partial_test.py:84:5:84:13 | ControlFlowNode for query_val | provenance | AdditionalTaintStep | -| full_partial_test.py:87:5:87:7 | ControlFlowNode for url | full_partial_test.py:89:18:89:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:91:5:91:7 | ControlFlowNode for url | full_partial_test.py:93:18:93:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:95:5:95:7 | ControlFlowNode for url | full_partial_test.py:97:18:97:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:101:5:101:14 | ControlFlowNode for user_input | full_partial_test.py:103:5:103:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:101:18:101:24 | ControlFlowNode for request | full_partial_test.py:101:5:101:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| full_partial_test.py:103:5:103:7 | ControlFlowNode for url | full_partial_test.py:105:18:105:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:108:5:108:14 | ControlFlowNode for user_input | full_partial_test.py:110:5:110:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:108:18:108:24 | ControlFlowNode for request | full_partial_test.py:108:5:108:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| full_partial_test.py:110:5:110:7 | ControlFlowNode for url | full_partial_test.py:112:18:112:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:115:5:115:14 | ControlFlowNode for user_input | full_partial_test.py:117:5:117:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:115:18:115:24 | ControlFlowNode for request | full_partial_test.py:115:5:115:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| full_partial_test.py:117:5:117:7 | ControlFlowNode for url | full_partial_test.py:119:18:119:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:122:5:122:14 | ControlFlowNode for user_input | full_partial_test.py:124:5:124:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:122:18:122:24 | ControlFlowNode for request | full_partial_test.py:122:5:122:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| full_partial_test.py:124:5:124:7 | ControlFlowNode for url | full_partial_test.py:126:18:126:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:129:5:129:14 | ControlFlowNode for user_input | full_partial_test.py:134:5:134:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:129:18:129:24 | ControlFlowNode for request | full_partial_test.py:129:5:129:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| full_partial_test.py:134:5:134:7 | ControlFlowNode for url | full_partial_test.py:136:18:136:20 | ControlFlowNode for url | provenance | | -| full_partial_test.py:139:5:139:14 | ControlFlowNode for user_input | full_partial_test.py:141:5:141:7 | ControlFlowNode for url | provenance | | -| full_partial_test.py:139:18:139:24 | ControlFlowNode for request | full_partial_test.py:139:5:139:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| full_partial_test.py:141:5:141:7 | ControlFlowNode for url | full_partial_test.py:143:18:143:20 | ControlFlowNode for url | provenance | | -| test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | test_azure_client.py:6:19:6:25 | ControlFlowNode for request | provenance | | -| test_azure_client.py:6:19:6:25 | ControlFlowNode for request | test_azure_client.py:9:18:9:24 | ControlFlowNode for request | provenance | | -| test_azure_client.py:6:19:6:25 | ControlFlowNode for request | test_azure_client.py:10:19:10:25 | ControlFlowNode for request | provenance | | -| test_azure_client.py:9:5:9:14 | ControlFlowNode for user_input | test_azure_client.py:12:5:12:7 | ControlFlowNode for url | provenance | | -| test_azure_client.py:9:18:9:24 | ControlFlowNode for request | test_azure_client.py:9:5:9:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| test_azure_client.py:9:18:9:24 | ControlFlowNode for request | test_azure_client.py:10:5:10:15 | ControlFlowNode for user_input2 | provenance | AdditionalTaintStep | -| test_azure_client.py:10:5:10:15 | ControlFlowNode for user_input2 | test_azure_client.py:13:5:13:12 | ControlFlowNode for full_url | provenance | | -| test_azure_client.py:10:19:10:25 | ControlFlowNode for request | test_azure_client.py:10:5:10:15 | ControlFlowNode for user_input2 | provenance | AdditionalTaintStep | -| test_azure_client.py:12:5:12:7 | ControlFlowNode for url | test_azure_client.py:15:28:15:30 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_azure_client.py:12:5:12:7 | ControlFlowNode for url | test_azure_client.py:17:35:17:37 | ControlFlowNode for url | provenance | Sink:MaD:4 | -| test_azure_client.py:12:5:12:7 | ControlFlowNode for url | test_azure_client.py:19:15:19:17 | ControlFlowNode for url | provenance | Sink:MaD:1 | -| test_azure_client.py:12:5:12:7 | ControlFlowNode for url | test_azure_client.py:21:54:21:56 | ControlFlowNode for url | provenance | Sink:MaD:3 | -| test_azure_client.py:12:5:12:7 | ControlFlowNode for url | test_azure_client.py:24:37:24:39 | ControlFlowNode for url | provenance | Sink:MaD:5 | -| test_azure_client.py:13:5:13:12 | ControlFlowNode for full_url | test_azure_client.py:16:28:16:35 | ControlFlowNode for full_url | provenance | Sink:MaD:2 | -| test_azure_client.py:13:5:13:12 | ControlFlowNode for full_url | test_azure_client.py:18:35:18:42 | ControlFlowNode for full_url | provenance | Sink:MaD:4 | -| test_azure_client.py:13:5:13:12 | ControlFlowNode for full_url | test_azure_client.py:20:15:20:22 | ControlFlowNode for full_url | provenance | Sink:MaD:1 | -| test_azure_client.py:13:5:13:12 | ControlFlowNode for full_url | test_azure_client.py:22:54:22:61 | ControlFlowNode for full_url | provenance | Sink:MaD:3 | -| test_azure_client.py:13:5:13:12 | ControlFlowNode for full_url | test_azure_client.py:25:37:25:44 | ControlFlowNode for full_url | provenance | Sink:MaD:5 | -| test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | test_http_client.py:1:19:1:25 | ControlFlowNode for request | provenance | | -| test_http_client.py:1:19:1:25 | ControlFlowNode for request | test_http_client.py:9:19:9:25 | ControlFlowNode for request | provenance | | -| test_http_client.py:1:19:1:25 | ControlFlowNode for request | test_http_client.py:10:19:10:25 | ControlFlowNode for request | provenance | | -| test_http_client.py:1:19:1:25 | ControlFlowNode for request | test_http_client.py:11:18:11:24 | ControlFlowNode for request | provenance | | -| test_http_client.py:9:5:9:15 | ControlFlowNode for unsafe_host | test_http_client.py:13:27:13:37 | ControlFlowNode for unsafe_host | provenance | | -| test_http_client.py:9:5:9:15 | ControlFlowNode for unsafe_host | test_http_client.py:19:27:19:37 | ControlFlowNode for unsafe_host | provenance | | -| test_http_client.py:9:5:9:15 | ControlFlowNode for unsafe_host | test_http_client.py:28:27:28:37 | ControlFlowNode for unsafe_host | provenance | | -| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:9:5:9:15 | ControlFlowNode for unsafe_host | provenance | AdditionalTaintStep | -| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:10:5:10:15 | ControlFlowNode for unsafe_path | provenance | AdditionalTaintStep | -| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:11:5:11:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| test_http_client.py:10:5:10:15 | ControlFlowNode for unsafe_path | test_http_client.py:15:25:15:35 | ControlFlowNode for unsafe_path | provenance | | -| test_http_client.py:10:5:10:15 | ControlFlowNode for unsafe_path | test_http_client.py:21:25:21:35 | ControlFlowNode for unsafe_path | provenance | | -| test_http_client.py:10:5:10:15 | ControlFlowNode for unsafe_path | test_http_client.py:34:25:34:35 | ControlFlowNode for unsafe_path | provenance | | -| test_http_client.py:10:19:10:25 | ControlFlowNode for request | test_http_client.py:10:5:10:15 | ControlFlowNode for unsafe_path | provenance | AdditionalTaintStep | -| test_http_client.py:10:19:10:25 | ControlFlowNode for request | test_http_client.py:11:5:11:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| test_http_client.py:11:5:11:14 | ControlFlowNode for user_input | test_http_client.py:36:5:36:8 | ControlFlowNode for path | provenance | | -| test_http_client.py:11:5:11:14 | ControlFlowNode for user_input | test_http_client.py:41:5:41:8 | ControlFlowNode for path | provenance | | -| test_http_client.py:11:18:11:24 | ControlFlowNode for request | test_http_client.py:11:5:11:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| test_http_client.py:36:5:36:8 | ControlFlowNode for path | test_http_client.py:39:25:39:28 | ControlFlowNode for path | provenance | | -| test_http_client.py:41:5:41:8 | ControlFlowNode for path | test_http_client.py:44:25:44:28 | ControlFlowNode for path | provenance | | -| test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | test_path_validation.py:5:19:5:25 | ControlFlowNode for request | provenance | | -| test_path_validation.py:5:19:5:25 | ControlFlowNode for request | test_path_validation.py:8:18:8:24 | ControlFlowNode for request | provenance | | -| test_path_validation.py:5:19:5:25 | ControlFlowNode for request | test_path_validation.py:9:19:9:25 | ControlFlowNode for request | provenance | | -| test_path_validation.py:5:19:5:25 | ControlFlowNode for request | test_path_validation.py:24:18:24:24 | ControlFlowNode for request | provenance | | -| test_path_validation.py:5:19:5:25 | ControlFlowNode for request | test_path_validation.py:25:19:25:25 | ControlFlowNode for request | provenance | | -| test_path_validation.py:5:19:5:25 | ControlFlowNode for request | test_path_validation.py:40:18:40:24 | ControlFlowNode for request | provenance | | -| test_path_validation.py:5:19:5:25 | ControlFlowNode for request | test_path_validation.py:41:19:41:25 | ControlFlowNode for request | provenance | | -| test_path_validation.py:5:19:5:25 | ControlFlowNode for request | test_path_validation.py:57:18:57:24 | ControlFlowNode for request | provenance | | -| test_path_validation.py:8:5:8:14 | ControlFlowNode for user_input | test_path_validation.py:10:5:10:7 | ControlFlowNode for url | provenance | | -| test_path_validation.py:8:18:8:24 | ControlFlowNode for request | test_path_validation.py:8:5:8:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| test_path_validation.py:8:18:8:24 | ControlFlowNode for request | test_path_validation.py:9:5:9:15 | ControlFlowNode for user_input2 | provenance | AdditionalTaintStep | -| test_path_validation.py:9:5:9:15 | ControlFlowNode for user_input2 | test_path_validation.py:11:5:11:12 | ControlFlowNode for full_url | provenance | | -| test_path_validation.py:9:19:9:25 | ControlFlowNode for request | test_path_validation.py:9:5:9:15 | ControlFlowNode for user_input2 | provenance | AdditionalTaintStep | -| test_path_validation.py:10:5:10:7 | ControlFlowNode for url | test_path_validation.py:14:32:14:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:10:5:10:7 | ControlFlowNode for url | test_path_validation.py:16:32:16:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:11:5:11:12 | ControlFlowNode for full_url | test_path_validation.py:19:32:19:39 | ControlFlowNode for full_url | provenance | Sink:MaD:2 | -| test_path_validation.py:11:5:11:12 | ControlFlowNode for full_url | test_path_validation.py:21:32:21:39 | ControlFlowNode for full_url | provenance | Sink:MaD:2 | -| test_path_validation.py:24:5:24:14 | ControlFlowNode for user_input | test_path_validation.py:26:5:26:7 | ControlFlowNode for url | provenance | | -| test_path_validation.py:24:18:24:24 | ControlFlowNode for request | test_path_validation.py:24:5:24:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| test_path_validation.py:24:18:24:24 | ControlFlowNode for request | test_path_validation.py:25:5:25:15 | ControlFlowNode for user_input2 | provenance | AdditionalTaintStep | -| test_path_validation.py:25:5:25:15 | ControlFlowNode for user_input2 | test_path_validation.py:27:5:27:12 | ControlFlowNode for full_url | provenance | | -| test_path_validation.py:25:19:25:25 | ControlFlowNode for request | test_path_validation.py:25:5:25:15 | ControlFlowNode for user_input2 | provenance | AdditionalTaintStep | -| test_path_validation.py:26:5:26:7 | ControlFlowNode for url | test_path_validation.py:30:29:30:31 | ControlFlowNode for url | provenance | Sink:MaD:1 | -| test_path_validation.py:26:5:26:7 | ControlFlowNode for url | test_path_validation.py:32:29:32:31 | ControlFlowNode for url | provenance | Sink:MaD:1 | -| test_path_validation.py:27:5:27:12 | ControlFlowNode for full_url | test_path_validation.py:35:29:35:36 | ControlFlowNode for full_url | provenance | Sink:MaD:1 | -| test_path_validation.py:27:5:27:12 | ControlFlowNode for full_url | test_path_validation.py:37:29:37:36 | ControlFlowNode for full_url | provenance | Sink:MaD:1 | -| test_path_validation.py:40:5:40:14 | ControlFlowNode for user_input | test_path_validation.py:42:5:42:7 | ControlFlowNode for url | provenance | | -| test_path_validation.py:40:18:40:24 | ControlFlowNode for request | test_path_validation.py:40:5:40:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| test_path_validation.py:40:18:40:24 | ControlFlowNode for request | test_path_validation.py:41:5:41:15 | ControlFlowNode for user_input2 | provenance | AdditionalTaintStep | -| test_path_validation.py:41:5:41:15 | ControlFlowNode for user_input2 | test_path_validation.py:43:5:43:12 | ControlFlowNode for full_url | provenance | | -| test_path_validation.py:41:19:41:25 | ControlFlowNode for request | test_path_validation.py:41:5:41:15 | ControlFlowNode for user_input2 | provenance | AdditionalTaintStep | -| test_path_validation.py:42:5:42:7 | ControlFlowNode for url | test_path_validation.py:46:39:46:41 | ControlFlowNode for url | provenance | Sink:MaD:4 | -| test_path_validation.py:42:5:42:7 | ControlFlowNode for url | test_path_validation.py:48:39:48:41 | ControlFlowNode for url | provenance | Sink:MaD:4 | -| test_path_validation.py:43:5:43:12 | ControlFlowNode for full_url | test_path_validation.py:51:39:51:46 | ControlFlowNode for full_url | provenance | Sink:MaD:4 | -| test_path_validation.py:43:5:43:12 | ControlFlowNode for full_url | test_path_validation.py:53:39:53:46 | ControlFlowNode for full_url | provenance | Sink:MaD:4 | -| test_path_validation.py:57:5:57:14 | ControlFlowNode for user_input | test_path_validation.py:61:5:61:7 | ControlFlowNode for url | provenance | | -| test_path_validation.py:57:18:57:24 | ControlFlowNode for request | test_path_validation.py:57:5:57:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:64:32:64:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:66:32:66:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:69:32:69:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:71:32:71:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:74:32:74:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:76:32:76:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:79:32:79:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:81:32:81:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:85:32:85:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:87:32:87:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:90:32:90:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:92:32:92:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:95:32:95:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:97:32:97:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:100:32:100:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:102:32:102:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:105:32:105:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:107:32:107:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:110:32:110:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:112:32:112:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:115:32:115:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:117:32:117:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:120:32:120:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:122:32:122:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:125:32:125:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:127:32:127:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:130:32:130:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | test_path_validation.py:132:32:132:34 | ControlFlowNode for url | provenance | Sink:MaD:2 | -| test_requests.py:1:19:1:25 | ControlFlowNode for ImportMember | test_requests.py:1:19:1:25 | ControlFlowNode for request | provenance | | -| test_requests.py:1:19:1:25 | ControlFlowNode for request | test_requests.py:7:18:7:24 | ControlFlowNode for request | provenance | | -| test_requests.py:1:19:1:25 | ControlFlowNode for request | test_requests.py:14:18:14:24 | ControlFlowNode for request | provenance | | -| test_requests.py:1:19:1:25 | ControlFlowNode for request | test_requests.py:20:18:20:24 | ControlFlowNode for request | provenance | | -| test_requests.py:7:5:7:14 | ControlFlowNode for user_input | test_requests.py:9:18:9:27 | ControlFlowNode for user_input | provenance | | -| test_requests.py:7:18:7:24 | ControlFlowNode for request | test_requests.py:7:5:7:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| test_requests.py:14:5:14:14 | ControlFlowNode for user_input | test_requests.py:17:17:17:26 | ControlFlowNode for user_input | provenance | | -| test_requests.py:14:18:14:24 | ControlFlowNode for request | test_requests.py:14:5:14:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | -| test_requests.py:20:5:20:14 | ControlFlowNode for user_input | test_requests.py:22:34:22:43 | ControlFlowNode for user_input | provenance | | -| test_requests.py:20:18:20:24 | ControlFlowNode for request | test_requests.py:20:5:20:14 | ControlFlowNode for user_input | provenance | AdditionalTaintStep | +| full_partial_test.py:1:19:1:25 | After ImportMember | full_partial_test.py:1:19:1:25 | request | provenance | | +| full_partial_test.py:1:19:1:25 | request | full_partial_test.py:7:18:7:24 | request | provenance | | +| full_partial_test.py:1:19:1:25 | request | full_partial_test.py:8:17:8:23 | request | provenance | | +| full_partial_test.py:1:19:1:25 | request | full_partial_test.py:41:18:41:24 | request | provenance | | +| full_partial_test.py:1:19:1:25 | request | full_partial_test.py:42:17:42:23 | request | provenance | | +| full_partial_test.py:1:19:1:25 | request | full_partial_test.py:66:18:66:24 | request | provenance | | +| full_partial_test.py:1:19:1:25 | request | full_partial_test.py:67:17:67:23 | request | provenance | | +| full_partial_test.py:1:19:1:25 | request | full_partial_test.py:83:18:83:24 | request | provenance | | +| full_partial_test.py:1:19:1:25 | request | full_partial_test.py:84:17:84:23 | request | provenance | | +| full_partial_test.py:1:19:1:25 | request | full_partial_test.py:101:18:101:24 | request | provenance | | +| full_partial_test.py:1:19:1:25 | request | full_partial_test.py:108:18:108:24 | request | provenance | | +| full_partial_test.py:1:19:1:25 | request | full_partial_test.py:115:18:115:24 | request | provenance | | +| full_partial_test.py:1:19:1:25 | request | full_partial_test.py:122:18:122:24 | request | provenance | | +| full_partial_test.py:1:19:1:25 | request | full_partial_test.py:129:18:129:24 | request | provenance | | +| full_partial_test.py:1:19:1:25 | request | full_partial_test.py:139:18:139:24 | request | provenance | | +| full_partial_test.py:7:5:7:14 | user_input | full_partial_test.py:11:18:11:27 | user_input | provenance | | +| full_partial_test.py:7:5:7:14 | user_input | full_partial_test.py:13:5:13:7 | url | provenance | | +| full_partial_test.py:7:5:7:14 | user_input | full_partial_test.py:20:5:20:7 | url | provenance | | +| full_partial_test.py:7:5:7:14 | user_input | full_partial_test.py:25:5:25:7 | url | provenance | | +| full_partial_test.py:7:18:7:24 | request | full_partial_test.py:7:5:7:14 | user_input | provenance | AdditionalTaintStep | +| full_partial_test.py:7:18:7:24 | request | full_partial_test.py:8:5:8:13 | query_val | provenance | AdditionalTaintStep | +| full_partial_test.py:8:5:8:13 | query_val | full_partial_test.py:25:5:25:7 | url | provenance | | +| full_partial_test.py:8:17:8:23 | request | full_partial_test.py:8:5:8:13 | query_val | provenance | AdditionalTaintStep | +| full_partial_test.py:13:5:13:7 | url | full_partial_test.py:15:18:15:20 | url | provenance | | +| full_partial_test.py:20:5:20:7 | url | full_partial_test.py:22:18:22:20 | url | provenance | | +| full_partial_test.py:25:5:25:7 | url | full_partial_test.py:27:18:27:20 | url | provenance | | +| full_partial_test.py:41:5:41:14 | user_input | full_partial_test.py:45:5:45:7 | url | provenance | | +| full_partial_test.py:41:5:41:14 | user_input | full_partial_test.py:49:5:49:7 | url | provenance | | +| full_partial_test.py:41:5:41:14 | user_input | full_partial_test.py:53:5:53:7 | url | provenance | | +| full_partial_test.py:41:5:41:14 | user_input | full_partial_test.py:57:5:57:7 | url | provenance | | +| full_partial_test.py:41:5:41:14 | user_input | full_partial_test.py:61:5:61:7 | url | provenance | | +| full_partial_test.py:41:18:41:24 | request | full_partial_test.py:41:5:41:14 | user_input | provenance | AdditionalTaintStep | +| full_partial_test.py:41:18:41:24 | request | full_partial_test.py:42:5:42:13 | query_val | provenance | AdditionalTaintStep | +| full_partial_test.py:42:5:42:13 | query_val | full_partial_test.py:53:5:53:7 | url | provenance | | +| full_partial_test.py:42:17:42:23 | request | full_partial_test.py:42:5:42:13 | query_val | provenance | AdditionalTaintStep | +| full_partial_test.py:45:5:45:7 | url | full_partial_test.py:47:18:47:20 | url | provenance | | +| full_partial_test.py:49:5:49:7 | url | full_partial_test.py:51:18:51:20 | url | provenance | | +| full_partial_test.py:53:5:53:7 | url | full_partial_test.py:55:18:55:20 | url | provenance | | +| full_partial_test.py:57:5:57:7 | url | full_partial_test.py:59:18:59:20 | url | provenance | | +| full_partial_test.py:61:5:61:7 | url | full_partial_test.py:63:18:63:20 | url | provenance | | +| full_partial_test.py:66:5:66:14 | user_input | full_partial_test.py:70:5:70:7 | url | provenance | | +| full_partial_test.py:66:5:66:14 | user_input | full_partial_test.py:74:5:74:7 | url | provenance | | +| full_partial_test.py:66:5:66:14 | user_input | full_partial_test.py:78:5:78:7 | url | provenance | | +| full_partial_test.py:66:18:66:24 | request | full_partial_test.py:66:5:66:14 | user_input | provenance | AdditionalTaintStep | +| full_partial_test.py:66:18:66:24 | request | full_partial_test.py:67:5:67:13 | query_val | provenance | AdditionalTaintStep | +| full_partial_test.py:67:5:67:13 | query_val | full_partial_test.py:78:5:78:7 | url | provenance | | +| full_partial_test.py:67:17:67:23 | request | full_partial_test.py:67:5:67:13 | query_val | provenance | AdditionalTaintStep | +| full_partial_test.py:70:5:70:7 | url | full_partial_test.py:72:18:72:20 | url | provenance | | +| full_partial_test.py:74:5:74:7 | url | full_partial_test.py:76:18:76:20 | url | provenance | | +| full_partial_test.py:78:5:78:7 | url | full_partial_test.py:80:18:80:20 | url | provenance | | +| full_partial_test.py:83:5:83:14 | user_input | full_partial_test.py:87:5:87:7 | url | provenance | | +| full_partial_test.py:83:5:83:14 | user_input | full_partial_test.py:91:5:91:7 | url | provenance | | +| full_partial_test.py:83:5:83:14 | user_input | full_partial_test.py:95:5:95:7 | url | provenance | | +| full_partial_test.py:83:18:83:24 | request | full_partial_test.py:83:5:83:14 | user_input | provenance | AdditionalTaintStep | +| full_partial_test.py:83:18:83:24 | request | full_partial_test.py:84:5:84:13 | query_val | provenance | AdditionalTaintStep | +| full_partial_test.py:84:5:84:13 | query_val | full_partial_test.py:95:5:95:7 | url | provenance | | +| full_partial_test.py:84:17:84:23 | request | full_partial_test.py:84:5:84:13 | query_val | provenance | AdditionalTaintStep | +| full_partial_test.py:87:5:87:7 | url | full_partial_test.py:89:18:89:20 | url | provenance | | +| full_partial_test.py:91:5:91:7 | url | full_partial_test.py:93:18:93:20 | url | provenance | | +| full_partial_test.py:95:5:95:7 | url | full_partial_test.py:97:18:97:20 | url | provenance | | +| full_partial_test.py:101:5:101:14 | user_input | full_partial_test.py:103:5:103:7 | url | provenance | | +| full_partial_test.py:101:18:101:24 | request | full_partial_test.py:101:5:101:14 | user_input | provenance | AdditionalTaintStep | +| full_partial_test.py:103:5:103:7 | url | full_partial_test.py:105:18:105:20 | url | provenance | | +| full_partial_test.py:108:5:108:14 | user_input | full_partial_test.py:110:5:110:7 | url | provenance | | +| full_partial_test.py:108:18:108:24 | request | full_partial_test.py:108:5:108:14 | user_input | provenance | AdditionalTaintStep | +| full_partial_test.py:110:5:110:7 | url | full_partial_test.py:112:18:112:20 | url | provenance | | +| full_partial_test.py:115:5:115:14 | user_input | full_partial_test.py:117:5:117:7 | url | provenance | | +| full_partial_test.py:115:18:115:24 | request | full_partial_test.py:115:5:115:14 | user_input | provenance | AdditionalTaintStep | +| full_partial_test.py:117:5:117:7 | url | full_partial_test.py:119:18:119:20 | url | provenance | | +| full_partial_test.py:122:5:122:14 | user_input | full_partial_test.py:124:5:124:7 | url | provenance | | +| full_partial_test.py:122:18:122:24 | request | full_partial_test.py:122:5:122:14 | user_input | provenance | AdditionalTaintStep | +| full_partial_test.py:124:5:124:7 | url | full_partial_test.py:126:18:126:20 | url | provenance | | +| full_partial_test.py:129:5:129:14 | user_input | full_partial_test.py:134:5:134:7 | url | provenance | | +| full_partial_test.py:129:18:129:24 | request | full_partial_test.py:129:5:129:14 | user_input | provenance | AdditionalTaintStep | +| full_partial_test.py:134:5:134:7 | url | full_partial_test.py:136:18:136:20 | url | provenance | | +| full_partial_test.py:139:5:139:14 | user_input | full_partial_test.py:141:5:141:7 | url | provenance | | +| full_partial_test.py:139:18:139:24 | request | full_partial_test.py:139:5:139:14 | user_input | provenance | AdditionalTaintStep | +| full_partial_test.py:141:5:141:7 | url | full_partial_test.py:143:18:143:20 | url | provenance | | +| test_azure_client.py:6:19:6:25 | After ImportMember | test_azure_client.py:6:19:6:25 | request | provenance | | +| test_azure_client.py:6:19:6:25 | request | test_azure_client.py:9:18:9:24 | request | provenance | | +| test_azure_client.py:6:19:6:25 | request | test_azure_client.py:10:19:10:25 | request | provenance | | +| test_azure_client.py:9:5:9:14 | user_input | test_azure_client.py:12:5:12:7 | url | provenance | | +| test_azure_client.py:9:18:9:24 | request | test_azure_client.py:9:5:9:14 | user_input | provenance | AdditionalTaintStep | +| test_azure_client.py:9:18:9:24 | request | test_azure_client.py:10:5:10:15 | user_input2 | provenance | AdditionalTaintStep | +| test_azure_client.py:10:5:10:15 | user_input2 | test_azure_client.py:13:5:13:12 | full_url | provenance | | +| test_azure_client.py:10:19:10:25 | request | test_azure_client.py:10:5:10:15 | user_input2 | provenance | AdditionalTaintStep | +| test_azure_client.py:12:5:12:7 | url | test_azure_client.py:15:28:15:30 | url | provenance | Sink:MaD:2 | +| test_azure_client.py:12:5:12:7 | url | test_azure_client.py:17:35:17:37 | url | provenance | Sink:MaD:4 | +| test_azure_client.py:12:5:12:7 | url | test_azure_client.py:19:15:19:17 | url | provenance | Sink:MaD:1 | +| test_azure_client.py:12:5:12:7 | url | test_azure_client.py:21:54:21:56 | url | provenance | Sink:MaD:3 | +| test_azure_client.py:12:5:12:7 | url | test_azure_client.py:24:37:24:39 | url | provenance | Sink:MaD:5 | +| test_azure_client.py:13:5:13:12 | full_url | test_azure_client.py:16:28:16:35 | full_url | provenance | Sink:MaD:2 | +| test_azure_client.py:13:5:13:12 | full_url | test_azure_client.py:18:35:18:42 | full_url | provenance | Sink:MaD:4 | +| test_azure_client.py:13:5:13:12 | full_url | test_azure_client.py:20:15:20:22 | full_url | provenance | Sink:MaD:1 | +| test_azure_client.py:13:5:13:12 | full_url | test_azure_client.py:22:54:22:61 | full_url | provenance | Sink:MaD:3 | +| test_azure_client.py:13:5:13:12 | full_url | test_azure_client.py:25:37:25:44 | full_url | provenance | Sink:MaD:5 | +| test_http_client.py:1:19:1:25 | After ImportMember | test_http_client.py:1:19:1:25 | request | provenance | | +| test_http_client.py:1:19:1:25 | request | test_http_client.py:9:19:9:25 | request | provenance | | +| test_http_client.py:1:19:1:25 | request | test_http_client.py:10:19:10:25 | request | provenance | | +| test_http_client.py:1:19:1:25 | request | test_http_client.py:11:18:11:24 | request | provenance | | +| test_http_client.py:9:5:9:15 | unsafe_host | test_http_client.py:13:27:13:37 | unsafe_host | provenance | | +| test_http_client.py:9:5:9:15 | unsafe_host | test_http_client.py:19:27:19:37 | unsafe_host | provenance | | +| test_http_client.py:9:5:9:15 | unsafe_host | test_http_client.py:28:27:28:37 | unsafe_host | provenance | | +| test_http_client.py:9:19:9:25 | request | test_http_client.py:9:5:9:15 | unsafe_host | provenance | AdditionalTaintStep | +| test_http_client.py:9:19:9:25 | request | test_http_client.py:10:5:10:15 | unsafe_path | provenance | AdditionalTaintStep | +| test_http_client.py:9:19:9:25 | request | test_http_client.py:11:5:11:14 | user_input | provenance | AdditionalTaintStep | +| test_http_client.py:10:5:10:15 | unsafe_path | test_http_client.py:15:25:15:35 | unsafe_path | provenance | | +| test_http_client.py:10:5:10:15 | unsafe_path | test_http_client.py:21:25:21:35 | unsafe_path | provenance | | +| test_http_client.py:10:5:10:15 | unsafe_path | test_http_client.py:34:25:34:35 | unsafe_path | provenance | | +| test_http_client.py:10:19:10:25 | request | test_http_client.py:10:5:10:15 | unsafe_path | provenance | AdditionalTaintStep | +| test_http_client.py:10:19:10:25 | request | test_http_client.py:11:5:11:14 | user_input | provenance | AdditionalTaintStep | +| test_http_client.py:11:5:11:14 | user_input | test_http_client.py:36:5:36:8 | path | provenance | | +| test_http_client.py:11:5:11:14 | user_input | test_http_client.py:41:5:41:8 | path | provenance | | +| test_http_client.py:11:18:11:24 | request | test_http_client.py:11:5:11:14 | user_input | provenance | AdditionalTaintStep | +| test_http_client.py:36:5:36:8 | path | test_http_client.py:39:25:39:28 | path | provenance | | +| test_http_client.py:41:5:41:8 | path | test_http_client.py:44:25:44:28 | path | provenance | | +| test_path_validation.py:5:19:5:25 | After ImportMember | test_path_validation.py:5:19:5:25 | request | provenance | | +| test_path_validation.py:5:19:5:25 | request | test_path_validation.py:8:18:8:24 | request | provenance | | +| test_path_validation.py:5:19:5:25 | request | test_path_validation.py:9:19:9:25 | request | provenance | | +| test_path_validation.py:5:19:5:25 | request | test_path_validation.py:24:18:24:24 | request | provenance | | +| test_path_validation.py:5:19:5:25 | request | test_path_validation.py:25:19:25:25 | request | provenance | | +| test_path_validation.py:5:19:5:25 | request | test_path_validation.py:40:18:40:24 | request | provenance | | +| test_path_validation.py:5:19:5:25 | request | test_path_validation.py:41:19:41:25 | request | provenance | | +| test_path_validation.py:5:19:5:25 | request | test_path_validation.py:57:18:57:24 | request | provenance | | +| test_path_validation.py:8:5:8:14 | user_input | test_path_validation.py:10:5:10:7 | url | provenance | | +| test_path_validation.py:8:18:8:24 | request | test_path_validation.py:8:5:8:14 | user_input | provenance | AdditionalTaintStep | +| test_path_validation.py:8:18:8:24 | request | test_path_validation.py:9:5:9:15 | user_input2 | provenance | AdditionalTaintStep | +| test_path_validation.py:9:5:9:15 | user_input2 | test_path_validation.py:11:5:11:12 | full_url | provenance | | +| test_path_validation.py:9:19:9:25 | request | test_path_validation.py:9:5:9:15 | user_input2 | provenance | AdditionalTaintStep | +| test_path_validation.py:10:5:10:7 | url | test_path_validation.py:14:32:14:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:10:5:10:7 | url | test_path_validation.py:16:32:16:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:11:5:11:12 | full_url | test_path_validation.py:19:32:19:39 | full_url | provenance | Sink:MaD:2 | +| test_path_validation.py:11:5:11:12 | full_url | test_path_validation.py:21:32:21:39 | full_url | provenance | Sink:MaD:2 | +| test_path_validation.py:24:5:24:14 | user_input | test_path_validation.py:26:5:26:7 | url | provenance | | +| test_path_validation.py:24:18:24:24 | request | test_path_validation.py:24:5:24:14 | user_input | provenance | AdditionalTaintStep | +| test_path_validation.py:24:18:24:24 | request | test_path_validation.py:25:5:25:15 | user_input2 | provenance | AdditionalTaintStep | +| test_path_validation.py:25:5:25:15 | user_input2 | test_path_validation.py:27:5:27:12 | full_url | provenance | | +| test_path_validation.py:25:19:25:25 | request | test_path_validation.py:25:5:25:15 | user_input2 | provenance | AdditionalTaintStep | +| test_path_validation.py:26:5:26:7 | url | test_path_validation.py:30:29:30:31 | url | provenance | Sink:MaD:1 | +| test_path_validation.py:26:5:26:7 | url | test_path_validation.py:32:29:32:31 | url | provenance | Sink:MaD:1 | +| test_path_validation.py:27:5:27:12 | full_url | test_path_validation.py:35:29:35:36 | full_url | provenance | Sink:MaD:1 | +| test_path_validation.py:27:5:27:12 | full_url | test_path_validation.py:37:29:37:36 | full_url | provenance | Sink:MaD:1 | +| test_path_validation.py:40:5:40:14 | user_input | test_path_validation.py:42:5:42:7 | url | provenance | | +| test_path_validation.py:40:18:40:24 | request | test_path_validation.py:40:5:40:14 | user_input | provenance | AdditionalTaintStep | +| test_path_validation.py:40:18:40:24 | request | test_path_validation.py:41:5:41:15 | user_input2 | provenance | AdditionalTaintStep | +| test_path_validation.py:41:5:41:15 | user_input2 | test_path_validation.py:43:5:43:12 | full_url | provenance | | +| test_path_validation.py:41:19:41:25 | request | test_path_validation.py:41:5:41:15 | user_input2 | provenance | AdditionalTaintStep | +| test_path_validation.py:42:5:42:7 | url | test_path_validation.py:46:39:46:41 | url | provenance | Sink:MaD:4 | +| test_path_validation.py:42:5:42:7 | url | test_path_validation.py:48:39:48:41 | url | provenance | Sink:MaD:4 | +| test_path_validation.py:43:5:43:12 | full_url | test_path_validation.py:51:39:51:46 | full_url | provenance | Sink:MaD:4 | +| test_path_validation.py:43:5:43:12 | full_url | test_path_validation.py:53:39:53:46 | full_url | provenance | Sink:MaD:4 | +| test_path_validation.py:57:5:57:14 | user_input | test_path_validation.py:61:5:61:7 | url | provenance | | +| test_path_validation.py:57:18:57:24 | request | test_path_validation.py:57:5:57:14 | user_input | provenance | AdditionalTaintStep | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:64:32:64:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:66:32:66:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:69:32:69:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:71:32:71:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:74:32:74:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:76:32:76:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:79:32:79:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:81:32:81:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:85:32:85:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:87:32:87:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:90:32:90:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:92:32:92:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:95:32:95:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:97:32:97:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:100:32:100:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:102:32:102:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:105:32:105:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:107:32:107:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:110:32:110:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:112:32:112:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:115:32:115:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:117:32:117:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:120:32:120:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:122:32:122:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:125:32:125:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:127:32:127:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:130:32:130:34 | url | provenance | Sink:MaD:2 | +| test_path_validation.py:61:5:61:7 | url | test_path_validation.py:132:32:132:34 | url | provenance | Sink:MaD:2 | +| test_requests.py:1:19:1:25 | After ImportMember | test_requests.py:1:19:1:25 | request | provenance | | +| test_requests.py:1:19:1:25 | request | test_requests.py:7:18:7:24 | request | provenance | | +| test_requests.py:1:19:1:25 | request | test_requests.py:14:18:14:24 | request | provenance | | +| test_requests.py:1:19:1:25 | request | test_requests.py:20:18:20:24 | request | provenance | | +| test_requests.py:7:5:7:14 | user_input | test_requests.py:9:18:9:27 | user_input | provenance | | +| test_requests.py:7:18:7:24 | request | test_requests.py:7:5:7:14 | user_input | provenance | AdditionalTaintStep | +| test_requests.py:14:5:14:14 | user_input | test_requests.py:17:17:17:26 | user_input | provenance | | +| test_requests.py:14:18:14:24 | request | test_requests.py:14:5:14:14 | user_input | provenance | AdditionalTaintStep | +| test_requests.py:20:5:20:14 | user_input | test_requests.py:22:34:22:43 | user_input | provenance | | +| test_requests.py:20:18:20:24 | request | test_requests.py:20:5:20:14 | user_input | provenance | AdditionalTaintStep | models | 1 | Sink: azure.keyvault.keys.KeyClient!; Call.Argument[0,vault_url:]; request-forgery | | 2 | Sink: azure.keyvault.secrets.SecretClient!; Call.Argument[0,vault_url:]; request-forgery | @@ -238,185 +238,185 @@ models | 4 | Sink: azure.storage.fileshare.ShareFileClient!; Member[from_file_url].Argument[0,file_url:]; request-forgery | | 5 | Sink: azure; Member[storage].Member[blob].Member[download_blob_from_url].Argument[0,blob_url:]; request-forgery | nodes -| full_partial_test.py:1:19:1:25 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | -| full_partial_test.py:1:19:1:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| full_partial_test.py:7:5:7:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| full_partial_test.py:7:18:7:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| full_partial_test.py:8:5:8:13 | ControlFlowNode for query_val | semmle.label | ControlFlowNode for query_val | -| full_partial_test.py:8:17:8:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| full_partial_test.py:11:18:11:27 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| full_partial_test.py:13:5:13:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:15:18:15:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:20:5:20:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:22:18:22:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:25:5:25:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:27:18:27:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:41:5:41:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| full_partial_test.py:41:18:41:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| full_partial_test.py:42:5:42:13 | ControlFlowNode for query_val | semmle.label | ControlFlowNode for query_val | -| full_partial_test.py:42:17:42:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| full_partial_test.py:45:5:45:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:47:18:47:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:49:5:49:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:51:18:51:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:53:5:53:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:55:18:55:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:57:5:57:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:59:18:59:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:61:5:61:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:63:18:63:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:66:5:66:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| full_partial_test.py:66:18:66:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| full_partial_test.py:67:5:67:13 | ControlFlowNode for query_val | semmle.label | ControlFlowNode for query_val | -| full_partial_test.py:67:17:67:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| full_partial_test.py:70:5:70:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:72:18:72:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:74:5:74:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:76:18:76:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:78:5:78:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:80:18:80:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:83:5:83:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| full_partial_test.py:83:18:83:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| full_partial_test.py:84:5:84:13 | ControlFlowNode for query_val | semmle.label | ControlFlowNode for query_val | -| full_partial_test.py:84:17:84:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| full_partial_test.py:87:5:87:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:89:18:89:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:91:5:91:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:93:18:93:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:95:5:95:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:97:18:97:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:101:5:101:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| full_partial_test.py:101:18:101:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| full_partial_test.py:103:5:103:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:105:18:105:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:108:5:108:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| full_partial_test.py:108:18:108:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| full_partial_test.py:110:5:110:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:112:18:112:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:115:5:115:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| full_partial_test.py:115:18:115:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| full_partial_test.py:117:5:117:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:119:18:119:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:122:5:122:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| full_partial_test.py:122:18:122:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| full_partial_test.py:124:5:124:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:126:18:126:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:129:5:129:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| full_partial_test.py:129:18:129:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| full_partial_test.py:134:5:134:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:136:18:136:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:139:5:139:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| full_partial_test.py:139:18:139:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| full_partial_test.py:141:5:141:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| full_partial_test.py:143:18:143:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_azure_client.py:6:19:6:25 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | -| test_azure_client.py:6:19:6:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_azure_client.py:9:5:9:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| test_azure_client.py:9:18:9:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_azure_client.py:10:5:10:15 | ControlFlowNode for user_input2 | semmle.label | ControlFlowNode for user_input2 | -| test_azure_client.py:10:19:10:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_azure_client.py:12:5:12:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_azure_client.py:13:5:13:12 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_azure_client.py:15:28:15:30 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_azure_client.py:16:28:16:35 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_azure_client.py:17:35:17:37 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_azure_client.py:18:35:18:42 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_azure_client.py:19:15:19:17 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_azure_client.py:20:15:20:22 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_azure_client.py:21:54:21:56 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_azure_client.py:22:54:22:61 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_azure_client.py:24:37:24:39 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_azure_client.py:25:37:25:44 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_http_client.py:1:19:1:25 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | -| test_http_client.py:1:19:1:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_http_client.py:9:5:9:15 | ControlFlowNode for unsafe_host | semmle.label | ControlFlowNode for unsafe_host | -| test_http_client.py:9:19:9:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_http_client.py:10:5:10:15 | ControlFlowNode for unsafe_path | semmle.label | ControlFlowNode for unsafe_path | -| test_http_client.py:10:19:10:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_http_client.py:11:5:11:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| test_http_client.py:11:18:11:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_http_client.py:13:27:13:37 | ControlFlowNode for unsafe_host | semmle.label | ControlFlowNode for unsafe_host | -| test_http_client.py:15:25:15:35 | ControlFlowNode for unsafe_path | semmle.label | ControlFlowNode for unsafe_path | -| test_http_client.py:19:27:19:37 | ControlFlowNode for unsafe_host | semmle.label | ControlFlowNode for unsafe_host | -| test_http_client.py:21:25:21:35 | ControlFlowNode for unsafe_path | semmle.label | ControlFlowNode for unsafe_path | -| test_http_client.py:28:27:28:37 | ControlFlowNode for unsafe_host | semmle.label | ControlFlowNode for unsafe_host | -| test_http_client.py:34:25:34:35 | ControlFlowNode for unsafe_path | semmle.label | ControlFlowNode for unsafe_path | -| test_http_client.py:36:5:36:8 | ControlFlowNode for path | semmle.label | ControlFlowNode for path | -| test_http_client.py:39:25:39:28 | ControlFlowNode for path | semmle.label | ControlFlowNode for path | -| test_http_client.py:41:5:41:8 | ControlFlowNode for path | semmle.label | ControlFlowNode for path | -| test_http_client.py:44:25:44:28 | ControlFlowNode for path | semmle.label | ControlFlowNode for path | -| test_path_validation.py:5:19:5:25 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | -| test_path_validation.py:5:19:5:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_path_validation.py:8:5:8:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| test_path_validation.py:8:18:8:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_path_validation.py:9:5:9:15 | ControlFlowNode for user_input2 | semmle.label | ControlFlowNode for user_input2 | -| test_path_validation.py:9:19:9:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_path_validation.py:10:5:10:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:11:5:11:12 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_path_validation.py:14:32:14:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:16:32:16:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:19:32:19:39 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_path_validation.py:21:32:21:39 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_path_validation.py:24:5:24:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| test_path_validation.py:24:18:24:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_path_validation.py:25:5:25:15 | ControlFlowNode for user_input2 | semmle.label | ControlFlowNode for user_input2 | -| test_path_validation.py:25:19:25:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_path_validation.py:26:5:26:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:27:5:27:12 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_path_validation.py:30:29:30:31 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:32:29:32:31 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:35:29:35:36 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_path_validation.py:37:29:37:36 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_path_validation.py:40:5:40:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| test_path_validation.py:40:18:40:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_path_validation.py:41:5:41:15 | ControlFlowNode for user_input2 | semmle.label | ControlFlowNode for user_input2 | -| test_path_validation.py:41:19:41:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_path_validation.py:42:5:42:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:43:5:43:12 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_path_validation.py:46:39:46:41 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:48:39:48:41 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:51:39:51:46 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_path_validation.py:53:39:53:46 | ControlFlowNode for full_url | semmle.label | ControlFlowNode for full_url | -| test_path_validation.py:57:5:57:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| test_path_validation.py:57:18:57:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_path_validation.py:61:5:61:7 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:64:32:64:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:66:32:66:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:69:32:69:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:71:32:71:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:74:32:74:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:76:32:76:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:79:32:79:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:81:32:81:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:85:32:85:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:87:32:87:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:90:32:90:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:92:32:92:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:95:32:95:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:97:32:97:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:100:32:100:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:102:32:102:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:105:32:105:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:107:32:107:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:110:32:110:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:112:32:112:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:115:32:115:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:117:32:117:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:120:32:120:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:122:32:122:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:125:32:125:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:127:32:127:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:130:32:130:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_path_validation.py:132:32:132:34 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | -| test_requests.py:1:19:1:25 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | -| test_requests.py:1:19:1:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_requests.py:7:5:7:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| test_requests.py:7:18:7:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_requests.py:9:18:9:27 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| test_requests.py:14:5:14:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| test_requests.py:14:18:14:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_requests.py:17:17:17:26 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| test_requests.py:20:5:20:14 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | -| test_requests.py:20:18:20:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test_requests.py:22:34:22:43 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | +| full_partial_test.py:1:19:1:25 | After ImportMember | semmle.label | After ImportMember | +| full_partial_test.py:1:19:1:25 | request | semmle.label | request | +| full_partial_test.py:7:5:7:14 | user_input | semmle.label | user_input | +| full_partial_test.py:7:18:7:24 | request | semmle.label | request | +| full_partial_test.py:8:5:8:13 | query_val | semmle.label | query_val | +| full_partial_test.py:8:17:8:23 | request | semmle.label | request | +| full_partial_test.py:11:18:11:27 | user_input | semmle.label | user_input | +| full_partial_test.py:13:5:13:7 | url | semmle.label | url | +| full_partial_test.py:15:18:15:20 | url | semmle.label | url | +| full_partial_test.py:20:5:20:7 | url | semmle.label | url | +| full_partial_test.py:22:18:22:20 | url | semmle.label | url | +| full_partial_test.py:25:5:25:7 | url | semmle.label | url | +| full_partial_test.py:27:18:27:20 | url | semmle.label | url | +| full_partial_test.py:41:5:41:14 | user_input | semmle.label | user_input | +| full_partial_test.py:41:18:41:24 | request | semmle.label | request | +| full_partial_test.py:42:5:42:13 | query_val | semmle.label | query_val | +| full_partial_test.py:42:17:42:23 | request | semmle.label | request | +| full_partial_test.py:45:5:45:7 | url | semmle.label | url | +| full_partial_test.py:47:18:47:20 | url | semmle.label | url | +| full_partial_test.py:49:5:49:7 | url | semmle.label | url | +| full_partial_test.py:51:18:51:20 | url | semmle.label | url | +| full_partial_test.py:53:5:53:7 | url | semmle.label | url | +| full_partial_test.py:55:18:55:20 | url | semmle.label | url | +| full_partial_test.py:57:5:57:7 | url | semmle.label | url | +| full_partial_test.py:59:18:59:20 | url | semmle.label | url | +| full_partial_test.py:61:5:61:7 | url | semmle.label | url | +| full_partial_test.py:63:18:63:20 | url | semmle.label | url | +| full_partial_test.py:66:5:66:14 | user_input | semmle.label | user_input | +| full_partial_test.py:66:18:66:24 | request | semmle.label | request | +| full_partial_test.py:67:5:67:13 | query_val | semmle.label | query_val | +| full_partial_test.py:67:17:67:23 | request | semmle.label | request | +| full_partial_test.py:70:5:70:7 | url | semmle.label | url | +| full_partial_test.py:72:18:72:20 | url | semmle.label | url | +| full_partial_test.py:74:5:74:7 | url | semmle.label | url | +| full_partial_test.py:76:18:76:20 | url | semmle.label | url | +| full_partial_test.py:78:5:78:7 | url | semmle.label | url | +| full_partial_test.py:80:18:80:20 | url | semmle.label | url | +| full_partial_test.py:83:5:83:14 | user_input | semmle.label | user_input | +| full_partial_test.py:83:18:83:24 | request | semmle.label | request | +| full_partial_test.py:84:5:84:13 | query_val | semmle.label | query_val | +| full_partial_test.py:84:17:84:23 | request | semmle.label | request | +| full_partial_test.py:87:5:87:7 | url | semmle.label | url | +| full_partial_test.py:89:18:89:20 | url | semmle.label | url | +| full_partial_test.py:91:5:91:7 | url | semmle.label | url | +| full_partial_test.py:93:18:93:20 | url | semmle.label | url | +| full_partial_test.py:95:5:95:7 | url | semmle.label | url | +| full_partial_test.py:97:18:97:20 | url | semmle.label | url | +| full_partial_test.py:101:5:101:14 | user_input | semmle.label | user_input | +| full_partial_test.py:101:18:101:24 | request | semmle.label | request | +| full_partial_test.py:103:5:103:7 | url | semmle.label | url | +| full_partial_test.py:105:18:105:20 | url | semmle.label | url | +| full_partial_test.py:108:5:108:14 | user_input | semmle.label | user_input | +| full_partial_test.py:108:18:108:24 | request | semmle.label | request | +| full_partial_test.py:110:5:110:7 | url | semmle.label | url | +| full_partial_test.py:112:18:112:20 | url | semmle.label | url | +| full_partial_test.py:115:5:115:14 | user_input | semmle.label | user_input | +| full_partial_test.py:115:18:115:24 | request | semmle.label | request | +| full_partial_test.py:117:5:117:7 | url | semmle.label | url | +| full_partial_test.py:119:18:119:20 | url | semmle.label | url | +| full_partial_test.py:122:5:122:14 | user_input | semmle.label | user_input | +| full_partial_test.py:122:18:122:24 | request | semmle.label | request | +| full_partial_test.py:124:5:124:7 | url | semmle.label | url | +| full_partial_test.py:126:18:126:20 | url | semmle.label | url | +| full_partial_test.py:129:5:129:14 | user_input | semmle.label | user_input | +| full_partial_test.py:129:18:129:24 | request | semmle.label | request | +| full_partial_test.py:134:5:134:7 | url | semmle.label | url | +| full_partial_test.py:136:18:136:20 | url | semmle.label | url | +| full_partial_test.py:139:5:139:14 | user_input | semmle.label | user_input | +| full_partial_test.py:139:18:139:24 | request | semmle.label | request | +| full_partial_test.py:141:5:141:7 | url | semmle.label | url | +| full_partial_test.py:143:18:143:20 | url | semmle.label | url | +| test_azure_client.py:6:19:6:25 | After ImportMember | semmle.label | After ImportMember | +| test_azure_client.py:6:19:6:25 | request | semmle.label | request | +| test_azure_client.py:9:5:9:14 | user_input | semmle.label | user_input | +| test_azure_client.py:9:18:9:24 | request | semmle.label | request | +| test_azure_client.py:10:5:10:15 | user_input2 | semmle.label | user_input2 | +| test_azure_client.py:10:19:10:25 | request | semmle.label | request | +| test_azure_client.py:12:5:12:7 | url | semmle.label | url | +| test_azure_client.py:13:5:13:12 | full_url | semmle.label | full_url | +| test_azure_client.py:15:28:15:30 | url | semmle.label | url | +| test_azure_client.py:16:28:16:35 | full_url | semmle.label | full_url | +| test_azure_client.py:17:35:17:37 | url | semmle.label | url | +| test_azure_client.py:18:35:18:42 | full_url | semmle.label | full_url | +| test_azure_client.py:19:15:19:17 | url | semmle.label | url | +| test_azure_client.py:20:15:20:22 | full_url | semmle.label | full_url | +| test_azure_client.py:21:54:21:56 | url | semmle.label | url | +| test_azure_client.py:22:54:22:61 | full_url | semmle.label | full_url | +| test_azure_client.py:24:37:24:39 | url | semmle.label | url | +| test_azure_client.py:25:37:25:44 | full_url | semmle.label | full_url | +| test_http_client.py:1:19:1:25 | After ImportMember | semmle.label | After ImportMember | +| test_http_client.py:1:19:1:25 | request | semmle.label | request | +| test_http_client.py:9:5:9:15 | unsafe_host | semmle.label | unsafe_host | +| test_http_client.py:9:19:9:25 | request | semmle.label | request | +| test_http_client.py:10:5:10:15 | unsafe_path | semmle.label | unsafe_path | +| test_http_client.py:10:19:10:25 | request | semmle.label | request | +| test_http_client.py:11:5:11:14 | user_input | semmle.label | user_input | +| test_http_client.py:11:18:11:24 | request | semmle.label | request | +| test_http_client.py:13:27:13:37 | unsafe_host | semmle.label | unsafe_host | +| test_http_client.py:15:25:15:35 | unsafe_path | semmle.label | unsafe_path | +| test_http_client.py:19:27:19:37 | unsafe_host | semmle.label | unsafe_host | +| test_http_client.py:21:25:21:35 | unsafe_path | semmle.label | unsafe_path | +| test_http_client.py:28:27:28:37 | unsafe_host | semmle.label | unsafe_host | +| test_http_client.py:34:25:34:35 | unsafe_path | semmle.label | unsafe_path | +| test_http_client.py:36:5:36:8 | path | semmle.label | path | +| test_http_client.py:39:25:39:28 | path | semmle.label | path | +| test_http_client.py:41:5:41:8 | path | semmle.label | path | +| test_http_client.py:44:25:44:28 | path | semmle.label | path | +| test_path_validation.py:5:19:5:25 | After ImportMember | semmle.label | After ImportMember | +| test_path_validation.py:5:19:5:25 | request | semmle.label | request | +| test_path_validation.py:8:5:8:14 | user_input | semmle.label | user_input | +| test_path_validation.py:8:18:8:24 | request | semmle.label | request | +| test_path_validation.py:9:5:9:15 | user_input2 | semmle.label | user_input2 | +| test_path_validation.py:9:19:9:25 | request | semmle.label | request | +| test_path_validation.py:10:5:10:7 | url | semmle.label | url | +| test_path_validation.py:11:5:11:12 | full_url | semmle.label | full_url | +| test_path_validation.py:14:32:14:34 | url | semmle.label | url | +| test_path_validation.py:16:32:16:34 | url | semmle.label | url | +| test_path_validation.py:19:32:19:39 | full_url | semmle.label | full_url | +| test_path_validation.py:21:32:21:39 | full_url | semmle.label | full_url | +| test_path_validation.py:24:5:24:14 | user_input | semmle.label | user_input | +| test_path_validation.py:24:18:24:24 | request | semmle.label | request | +| test_path_validation.py:25:5:25:15 | user_input2 | semmle.label | user_input2 | +| test_path_validation.py:25:19:25:25 | request | semmle.label | request | +| test_path_validation.py:26:5:26:7 | url | semmle.label | url | +| test_path_validation.py:27:5:27:12 | full_url | semmle.label | full_url | +| test_path_validation.py:30:29:30:31 | url | semmle.label | url | +| test_path_validation.py:32:29:32:31 | url | semmle.label | url | +| test_path_validation.py:35:29:35:36 | full_url | semmle.label | full_url | +| test_path_validation.py:37:29:37:36 | full_url | semmle.label | full_url | +| test_path_validation.py:40:5:40:14 | user_input | semmle.label | user_input | +| test_path_validation.py:40:18:40:24 | request | semmle.label | request | +| test_path_validation.py:41:5:41:15 | user_input2 | semmle.label | user_input2 | +| test_path_validation.py:41:19:41:25 | request | semmle.label | request | +| test_path_validation.py:42:5:42:7 | url | semmle.label | url | +| test_path_validation.py:43:5:43:12 | full_url | semmle.label | full_url | +| test_path_validation.py:46:39:46:41 | url | semmle.label | url | +| test_path_validation.py:48:39:48:41 | url | semmle.label | url | +| test_path_validation.py:51:39:51:46 | full_url | semmle.label | full_url | +| test_path_validation.py:53:39:53:46 | full_url | semmle.label | full_url | +| test_path_validation.py:57:5:57:14 | user_input | semmle.label | user_input | +| test_path_validation.py:57:18:57:24 | request | semmle.label | request | +| test_path_validation.py:61:5:61:7 | url | semmle.label | url | +| test_path_validation.py:64:32:64:34 | url | semmle.label | url | +| test_path_validation.py:66:32:66:34 | url | semmle.label | url | +| test_path_validation.py:69:32:69:34 | url | semmle.label | url | +| test_path_validation.py:71:32:71:34 | url | semmle.label | url | +| test_path_validation.py:74:32:74:34 | url | semmle.label | url | +| test_path_validation.py:76:32:76:34 | url | semmle.label | url | +| test_path_validation.py:79:32:79:34 | url | semmle.label | url | +| test_path_validation.py:81:32:81:34 | url | semmle.label | url | +| test_path_validation.py:85:32:85:34 | url | semmle.label | url | +| test_path_validation.py:87:32:87:34 | url | semmle.label | url | +| test_path_validation.py:90:32:90:34 | url | semmle.label | url | +| test_path_validation.py:92:32:92:34 | url | semmle.label | url | +| test_path_validation.py:95:32:95:34 | url | semmle.label | url | +| test_path_validation.py:97:32:97:34 | url | semmle.label | url | +| test_path_validation.py:100:32:100:34 | url | semmle.label | url | +| test_path_validation.py:102:32:102:34 | url | semmle.label | url | +| test_path_validation.py:105:32:105:34 | url | semmle.label | url | +| test_path_validation.py:107:32:107:34 | url | semmle.label | url | +| test_path_validation.py:110:32:110:34 | url | semmle.label | url | +| test_path_validation.py:112:32:112:34 | url | semmle.label | url | +| test_path_validation.py:115:32:115:34 | url | semmle.label | url | +| test_path_validation.py:117:32:117:34 | url | semmle.label | url | +| test_path_validation.py:120:32:120:34 | url | semmle.label | url | +| test_path_validation.py:122:32:122:34 | url | semmle.label | url | +| test_path_validation.py:125:32:125:34 | url | semmle.label | url | +| test_path_validation.py:127:32:127:34 | url | semmle.label | url | +| test_path_validation.py:130:32:130:34 | url | semmle.label | url | +| test_path_validation.py:132:32:132:34 | url | semmle.label | url | +| test_requests.py:1:19:1:25 | After ImportMember | semmle.label | After ImportMember | +| test_requests.py:1:19:1:25 | request | semmle.label | request | +| test_requests.py:7:5:7:14 | user_input | semmle.label | user_input | +| test_requests.py:7:18:7:24 | request | semmle.label | request | +| test_requests.py:9:18:9:27 | user_input | semmle.label | user_input | +| test_requests.py:14:5:14:14 | user_input | semmle.label | user_input | +| test_requests.py:14:18:14:24 | request | semmle.label | request | +| test_requests.py:17:17:17:26 | user_input | semmle.label | user_input | +| test_requests.py:20:5:20:14 | user_input | semmle.label | user_input | +| test_requests.py:20:18:20:24 | request | semmle.label | request | +| test_requests.py:22:34:22:43 | user_input | semmle.label | user_input | subpaths diff --git a/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll b/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll index fff877b9fcd9..dd71b5f98c79 100644 --- a/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll +++ b/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll @@ -211,6 +211,20 @@ signature module AstSig { */ default AstNode getTryElse(TryStmt try) { none() } + /** + * Gets the `else` block of this `while` loop statement, if any. + * + * Only some languages (e.g. Python) support `while-else` constructs. + */ + default AstNode getWhileElse(WhileStmt loop) { none() } + + /** + * Gets the `else` block of this `foreach` loop statement, if any. + * + * Only some languages (e.g. Python) support `for-else` constructs. + */ + default AstNode getForeachElse(ForeachStmt loop) { none() } + /** A catch clause in a try statement. */ class CatchClause extends AstNode { /** Gets the variable declared by this catch clause. */ @@ -1549,19 +1563,32 @@ module Make0 Ast> { n2.isBefore(loopstmt.getBody()) or n1.isAfterFalse(cond) and - n2.isAfter(loopstmt) + ( + n2.isBefore(getWhileElse(loopstmt)) + or + not exists(getWhileElse(loopstmt)) and n2.isAfter(loopstmt) + ) or n1.isAfter(loopstmt.getBody()) and n2.isAdditional(loopstmt, loopHeaderTag()) ) or + exists(WhileStmt whilestmt | + n1.isAfter(getWhileElse(whilestmt)) and + n2.isAfter(whilestmt) + ) + or exists(ForeachStmt foreachstmt | n1.isBefore(foreachstmt) and n2.isBefore(foreachstmt.getCollection()) or n1.isAfterValue(foreachstmt.getCollection(), any(EmptinessSuccessor t | t.getValue() = true)) and - n2.isAfter(foreachstmt) + ( + n2.isBefore(getForeachElse(foreachstmt)) + or + not exists(getForeachElse(foreachstmt)) and n2.isAfter(foreachstmt) + ) or n1.isAfterValue(foreachstmt.getCollection(), any(EmptinessSuccessor t | t.getValue() = false)) and @@ -1574,10 +1601,17 @@ module Make0 Ast> { n2.isAdditional(foreachstmt, loopHeaderTag()) or n1.isAdditional(foreachstmt, loopHeaderTag()) and - n2.isAfter(foreachstmt) + ( + n2.isBefore(getForeachElse(foreachstmt)) + or + not exists(getForeachElse(foreachstmt)) and n2.isAfter(foreachstmt) + ) or n1.isAdditional(foreachstmt, loopHeaderTag()) and n2.isBefore(foreachstmt.getVariable()) + or + n1.isAfter(getForeachElse(foreachstmt)) and + n2.isAfter(foreachstmt) ) or exists(ForStmt forstmt, PreControlFlowNode condentry |