diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll index 5de8fffca5be..d036052c14fe 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll @@ -5,6 +5,8 @@ private import DataFlowDispatch /** Gets the instance argument of a non-static call. */ private Node getInstanceArgument(Call call) { result.asExpr() = call.getQualifier() + or + result.(PreConstructorCallNode).getConstructorCall() = call // This does not include the implicit `this` argument on auto-generated // base class destructor calls as those do not have an AST element. } @@ -167,7 +169,15 @@ private class ArrayContent extends Content, TArrayContent { * value of `node1`. */ predicate storeStep(Node node1, Content f, PostUpdateNode node2) { - none() // stub implementation + exists(FieldAccess fa | + exists(Assignment a | + node1.asExpr() = a and + a.getLValue() = fa + ) and + not fa.getTarget().isStatic() and + node2.getPreUpdateNode().asExpr() = fa.getQualifier() and + f.(FieldContent).getField() = fa.getTarget() + ) } /** @@ -176,7 +186,12 @@ predicate storeStep(Node node1, Content f, PostUpdateNode node2) { * `node2`. */ predicate readStep(Node node1, Content f, Node node2) { - none() // stub implementation + exists(FieldAccess fr | + node1.asExpr() = fr.getQualifier() and + fr.getTarget() = f.(FieldContent).getField() and + fr = node2.asExpr() and + not fr = any(AssignExpr a).getLValue() + ) } /** diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll index 56f8727d213b..3d9338f475da 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll @@ -9,10 +9,10 @@ private import semmle.code.cpp.models.interfaces.DataFlow cached private newtype TNode = TExprNode(Expr e) or - TParameterNode(Parameter p) { exists(p.getFunction().getBlock()) } or - TDefinitionByReferenceNode(VariableAccess va, Expr argument) { - definitionByReference(va, argument) - } or + TPartialDefinitionNode(PartialDefinition pd) or + TPreConstructorCallNode(ConstructorCall call) or + TExplicitParameterNode(Parameter p) { exists(p.getFunction().getBlock()) } or + TInstanceParameterNode(MemberFunction f) { exists(f.getBlock()) and not f.isStatic() } or TUninitializedNode(LocalVariable v) { not v.hasInitializer() } /** @@ -38,11 +38,23 @@ class Node extends TNode { Expr asExpr() { result = this.(ExprNode).getExpr() } /** Gets the parameter corresponding to this node, if any. */ - Parameter asParameter() { result = this.(ParameterNode).getParameter() } + Parameter asParameter() { result = this.(ExplicitParameterNode).getParameter() } /** Gets the argument that defines this `DefinitionByReferenceNode`, if any. */ Expr asDefiningArgument() { result = this.(DefinitionByReferenceNode).getArgument() } + /** + * Gets the expression that is partially defined by this node, if any. + * + * Partial definitions are created for field stores (`x.y = taint();` is a partial + * definition of `x`), and for calls that may change the value of an object (so + * `x.set(taint())` is a partial definition of `x`, and `transfer(&x, taint())` is + * a partial definition of `&x`). + */ + Expr asPartialDefinition() { + result = this.(PartialDefinitionNode).getPartialDefinition().getDefinedExpr() + } + /** * Gets the uninitialized local variable corresponding to this node, if * any. @@ -81,14 +93,22 @@ class ExprNode extends Node, TExprNode { Expr getExpr() { result = expr } } +abstract class ParameterNode extends Node, TNode { + /** + * Holds if this node is the parameter of `c` at the specified (zero-based) + * position. The implicit `this` parameter is considered to have index `-1`. + */ + abstract predicate isParameterOf(Function f, int i); +} + /** * The value of a parameter at function entry, viewed as a node in a data * flow graph. */ -class ParameterNode extends Node, TParameterNode { +class ExplicitParameterNode extends ParameterNode, TExplicitParameterNode { Parameter param; - ParameterNode() { this = TParameterNode(param) } + ExplicitParameterNode() { this = TExplicitParameterNode(param) } override Function getFunction() { result = param.getFunction() } @@ -101,11 +121,23 @@ class ParameterNode extends Node, TParameterNode { /** Gets the parameter corresponding to this node. */ Parameter getParameter() { result = param } - /** - * Holds if this node is the parameter of `c` at the specified (zero-based) - * position. The implicit `this` parameter is considered to have index `-1`. - */ - predicate isParameterOf(Function f, int i) { f.getParameter(i) = param } + override predicate isParameterOf(Function f, int i) { f.getParameter(i) = param } +} + +class ImplicitParameterNode extends ParameterNode, TInstanceParameterNode { + MemberFunction f; + + ImplicitParameterNode() { this = TInstanceParameterNode(f) } + + override Function getFunction() { result = f } + + override Type getType() { result = f.getDeclaringType() } + + override string toString() { result = "`this` parameter in " + f.getName() } + + override Location getLocation() { result = f.getLocation() } + + override predicate isParameterOf(Function fun, int i) { f = fun and i = -1 } } /** @@ -117,12 +149,18 @@ class ParameterNode extends Node, TParameterNode { * `DefinitionByReferenceNode` to represent the value of `x` after the call has * returned. This node will have its `getArgument()` equal to `&x`. */ -class DefinitionByReferenceNode extends Node, TDefinitionByReferenceNode { +class DefinitionByReferenceNode extends PartialDefinitionNode { VariableAccess va; Expr argument; - DefinitionByReferenceNode() { this = TDefinitionByReferenceNode(va, argument) } + DefinitionByReferenceNode() { + exists(DefinitionByReference def | + def = this.getPartialDefinition() and + argument = def.getDefinedExpr() and + va = def.getVariableAccess() + ) + } override Function getFunction() { result = va.getEnclosingFunction() } @@ -177,13 +215,64 @@ class UninitializedNode extends Node, TUninitializedNode { * to the value before the update with the exception of `ClassInstanceExpr`, * which represents the value after the constructor has run. */ -class PostUpdateNode extends Node { - PostUpdateNode() { none() } // stub implementation - +abstract class PostUpdateNode extends Node { /** * Gets the node before the state update. */ - Node getPreUpdateNode() { none() } // stub implementation + abstract Node getPreUpdateNode(); + + override Function getFunction() { result = getPreUpdateNode().getFunction() } + + override Type getType() { result = getPreUpdateNode().getType() } + + override Location getLocation() { result = getPreUpdateNode().getLocation() } +} + +class PartialDefinitionNode extends PostUpdateNode, TPartialDefinitionNode { + PartialDefinition pd; + + PartialDefinitionNode() { this = TPartialDefinitionNode(pd) } + + override Node getPreUpdateNode() { result.asExpr() = pd.getDefinedExpr() } + + override Location getLocation() { result = pd.getLocation() } + + PartialDefinition getPartialDefinition() { result = pd } + + override string toString() { result = getPreUpdateNode().toString() + " [post update]" } +} + +/** + * A node representing the object that was just constructed and is identified + * with the "return value" of the constructor call. + */ +private class PostConstructorCallNode extends PostUpdateNode, TExprNode { + PostConstructorCallNode() { this = TExprNode(any(ConstructorCall c)) } + + override PreConstructorCallNode getPreUpdateNode() { + TExprNode(result.getConstructorCall()) = this + } + + // No override of `toString` since these nodes already have a `toString` from + // their overlap with `ExprNode`. +} + +/** + * INTERNAL: do not use. + * + * A synthetic data-flow node that plays the role of the qualifier (or + * `this`-argument) to a constructor call. + */ +class PreConstructorCallNode extends Node, TPreConstructorCallNode { + ConstructorCall getConstructorCall() { this = TPreConstructorCallNode(result) } + + override Function getFunction() { result = getConstructorCall().getEnclosingFunction() } + + override Type getType() { result = getConstructorCall().getType() } + + override Location getLocation() { result = getConstructorCall().getLocation() } + + override string toString() { result = getConstructorCall().toString() + " [pre constructor call]" } } /** @@ -194,7 +283,7 @@ ExprNode exprNode(Expr e) { result.getExpr() = e } /** * Gets the `Node` corresponding to the value of `p` at function entry. */ -ParameterNode parameterNode(Parameter p) { result.getParameter() = p } +ParameterNode parameterNode(Parameter p) { result.(ExplicitParameterNode).getParameter() = p } /** * Gets the `Node` corresponding to a definition by reference of the variable @@ -210,6 +299,43 @@ DefinitionByReferenceNode definitionByReferenceNodeFromArgument(Expr argument) { */ UninitializedNode uninitializedNode(LocalVariable v) { result.getLocalVariable() = v } +private module ThisFlow { + private Node thisAccessNode(ControlFlowNode cfn) { + result.(ImplicitParameterNode).getFunction().getBlock() = cfn or + result.asExpr().(ThisExpr) = cfn + } + + private int basicBlockThisIndex(BasicBlock b, Node thisNode) { + thisNode = thisAccessNode(b.getNode(result)) + } + + private int thisRank(BasicBlock b, Node thisNode) { + thisNode = rank[result](thisAccessNode(_) as node order by basicBlockThisIndex(b, node)) + } + + private int lastThisRank(BasicBlock b) { result = max(thisRank(b, _)) } + + private predicate thisAccessBlockReaches(BasicBlock b1, BasicBlock b2) { + exists(basicBlockThisIndex(b1, _)) and b2 = b1.getASuccessor() + or + exists(BasicBlock mid | + thisAccessBlockReaches(b1, mid) and + b2 = mid.getASuccessor() and + not exists(basicBlockThisIndex(mid, _)) + ) + } + + predicate adjacentThisRefs(Node n1, Node n2) { + exists(BasicBlock b | thisRank(b, n1) + 1 = thisRank(b, n2)) + or + exists(BasicBlock b1, BasicBlock b2 | + lastThisRank(b1) = thisRank(b1, n1) and + thisAccessBlockReaches(b1, b2) and + thisRank(b2, n2) = 1 + ) + } +} + /** * Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local * (intra-procedural) step. @@ -219,6 +345,8 @@ predicate localFlowStep(Node nodeFrom, Node nodeTo) { // Expr -> Expr exprToExprStep_nocfg(nodeFrom.asExpr(), nodeTo.asExpr()) or + exprToExprStep_nocfg(nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr(), nodeTo.asExpr()) + or // Node -> FlowVar -> VariableAccess exists(FlowVar var | ( @@ -228,13 +356,19 @@ predicate localFlowStep(Node nodeFrom, Node nodeTo) { or varSourceBaseCase(var, nodeFrom.asUninitialized()) or - var.definedByReference(nodeFrom.asDefiningArgument()) + var.definedPartiallyAt(nodeFrom.asPartialDefinition()) ) and varToExprStep(var, nodeTo.asExpr()) ) or // Expr -> DefinitionByReferenceNode exprToDefinitionByReferenceStep(nodeFrom.asExpr(), nodeTo.asDefiningArgument()) + or + // `this` -> adjacent-`this` + ThisFlow::adjacentThisRefs(nodeFrom, nodeTo) + or + // post-update-`this` -> following-`this`-ref + ThisFlow::adjacentThisRefs(nodeFrom.(PostUpdateNode).getPreUpdateNode(), nodeTo) } /** @@ -281,6 +415,18 @@ private predicate exprToExprStep_nocfg(Expr fromExpr, Expr toExpr) { or toExpr = any(StmtExpr stmtExpr | fromExpr = stmtExpr.getResultExpr()) or + // The following case is needed to track the qualifier object for flow + // through fields. It gives flow from `T(x)` to `new T(x)`. That's not + // strictly _data_ flow but _taint_ flow because the type of `fromExpr` is + // `T` while the type of `toExpr` is `T*`. + // + // This discrepancy is an artifact of how `new`-expressions are represented + // in the database in a way that slightly varies from what the standard + // specifies. In the C++ standard, there is no constructor call expression + // `T(x)` after `new`. Instead there is a type `T` and an optional + // initializer `(x)`. + toExpr.(NewExpr).getInitializer() = fromExpr + or toExpr = any(Call call | exists(DataFlowFunction f, FunctionInput inModel, FunctionOutput outModel, int iIn | call.getTarget() = f and diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll index c5b1fcdba52f..e2b9cc0d39f5 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll @@ -57,6 +57,8 @@ cached class FlowVar extends TFlowVar { */ cached abstract predicate definedByReference(Expr arg); + cached abstract predicate definedPartiallyAt(Expr e); + /** * Holds if this `FlowVar` corresponds to the initial value of `v`. The following * is an exhaustive list of cases where this may happen. @@ -77,6 +79,74 @@ cached class FlowVar extends TFlowVar { cached abstract Location getLocation(); } +/** + * Provides classes and predicates dealing with "partial definitions". + * + * In contrast to a normal "definition", which provides a new value for + * something, a partial definition is an expression that may affect a + * value, but does not necessarily replace it entirely. For example, + * `x.y = 1;` is a partial definition of the object `x`. + */ +private module PartialDefinitions { + private newtype TPartialDefinition = + TExplicitFieldStoreQualifier(Expr qualifier, ControlFlowNode node) { + exists(FieldAccess fa | + isInstanceFieldWrite(fa, node) and qualifier = fa.getQualifier() + ) + } or + TExplicitCallQualifier(Expr qualifier, Call call) { qualifier = call.getQualifier() } or + TReferenceArgument(Expr arg, VariableAccess va) { definitionByReference(va, arg) } + + private predicate isInstanceFieldWrite(FieldAccess fa, ControlFlowNode node) { + not fa.getTarget().isStatic() and + assignmentLikeOperation(node, fa.getTarget(), fa, _) + } + + class PartialDefinition extends TPartialDefinition { + Expr definedExpr; + ControlFlowNode node; + + PartialDefinition() { + this = TExplicitFieldStoreQualifier(definedExpr, node) or + this = TExplicitCallQualifier(definedExpr, _) and node = definedExpr or + this = TReferenceArgument(definedExpr, node) + } + + predicate partiallyDefines(Variable v) { definedExpr = v.getAnAccess() } + + predicate partiallyDefinesThis(ThisExpr e) { definedExpr = e } + + ControlFlowNode getSubBasicBlockStart() { result = node } + + Expr getDefinedExpr() { result = definedExpr } + + Location getLocation() { + not exists(definedExpr.getLocation()) and result = definedExpr.getParent().getLocation() + or + definedExpr.getLocation() instanceof UnknownLocation and + result = definedExpr.getParent().getLocation() + or + result = definedExpr.getLocation() and not result instanceof UnknownLocation + } + + string toString() { result = "partial def of " + definedExpr } + } + + /** + * A partial definition that's a definition by reference (in the sense of the + * `definitionByReference` predicate). + */ + class DefinitionByReference extends PartialDefinition, TReferenceArgument { + VariableAccess va; + + DefinitionByReference() { definitionByReference(va, definedExpr) } + + VariableAccess getVariableAccess() { result = va } + + override predicate partiallyDefines(Variable v) { va = v.getAnAccess() } + } +} +import PartialDefinitions private import FlowVar_internal /** @@ -99,11 +169,8 @@ module FlowVar_internal { */ predicate fullySupportedSsaVariable(Variable v) { v = any(SsaDefinition def).getAVariable() and - // After `foo(&x.field)` we need for there to be two definitions of `x`: - // the one that existed before the call to `foo` and the def-by-ref from - // the call. It's fundamental in SSA that each use is only associated with - // one def, so we can't easily get this effect with SSA. - not definitionByReference(v.getAnAccess(), _) and + // A partially-defined variable is handled using the partial definitions logic. + not any(PartialDefinition p).partiallyDefines(v) and // SSA variables do not exist before their first assignment, but one // feature of this data flow library is to track where uninitialized data // ends up. @@ -149,9 +216,9 @@ module FlowVar_internal { ( initializer(sbb.getANode(), v, _) or - assignmentLikeOperation(sbb, v, _) + assignmentLikeOperation(sbb, v, _, _) or - blockVarDefinedByReference(sbb, v, _) + sbb = any(PartialDefinition p | p.partiallyDefines(v)).getSubBasicBlockStart() or blockVarDefinedByVariable(sbb, v) ) @@ -162,6 +229,7 @@ module FlowVar_internal { */ class SsaVar extends TSsaVar, FlowVar { SsaDefinition def; + LocalScopeVariable v; SsaVar() { this = TSsaVar(def, v) } @@ -194,6 +262,8 @@ module FlowVar_internal { none() // Not supported for SSA. See `fullySupportedSsaVariable`. } + override predicate definedPartiallyAt(Expr e) { none() } + override predicate definedByInitialValue(LocalScopeVariable param) { def.definedByParameter(param) and param = v @@ -236,12 +306,14 @@ module FlowVar_internal { */ class BlockVar extends TBlockVar, FlowVar { SubBasicBlock sbb; + Variable v; BlockVar() { this = TBlockVar(sbb, v) } override VariableAccess getAnAccess() { - variableAccessInSBB(v, getAReachedBlockVarSBB(this), result) + variableAccessInSBB(v, getAReachedBlockVarSBB(this), result) and + result != sbb } override predicate definedByInitialValue(LocalScopeVariable lsv) { @@ -250,7 +322,7 @@ module FlowVar_internal { } override predicate definedByExpr(Expr e, ControlFlowNode node) { - assignmentLikeOperation(node, v, e) and + assignmentLikeOperation(node, v, _, e) and node = sbb or initializer(node, v, e) and @@ -258,7 +330,19 @@ module FlowVar_internal { } override predicate definedByReference(Expr arg) { - blockVarDefinedByReference(sbb, v, arg) + exists(DefinitionByReference def | + def.partiallyDefines(v) and + sbb = def.getSubBasicBlockStart() and + arg = def.getDefinedExpr() + ) + } + + override predicate definedPartiallyAt(Expr e) { + exists(PartialDefinition p | + p.partiallyDefines(v) and + sbb = p.getSubBasicBlockStart() and + e = p.getDefinedExpr() + ) } override string toString() { @@ -275,10 +359,16 @@ module FlowVar_internal { result = "definition by reference of "+ v ) or + exists(Expr partialDef | + this.definedPartiallyAt(partialDef) and + result = "partial definition at " + partialDef + ) + or // impossible case not this.definedByExpr(_, _) and not this.definedByInitialValue(_) and not this.definedByReference(_) and + not this.definedPartiallyAt(_) and result = "undefined "+ v } @@ -334,7 +424,7 @@ module FlowVar_internal { pragma[noinline] private Variable getAVariableAssignedInLoop() { exists(BasicBlock bbAssign | - assignmentLikeOperation(bbAssign.getANode(), result, _) and + assignmentLikeOperation(bbAssign.getANode(), result, _, _) and this.bbInLoop(bbAssign) ) } @@ -368,7 +458,7 @@ module FlowVar_internal { reachesWithoutAssignment(bb.getAPredecessor(), v) and this.bbInLoop(bb) ) and - not assignmentLikeOperation(bb.getANode(), v, _) + not assignmentLikeOperation(bb.getANode(), v, _, _) } } @@ -394,19 +484,59 @@ module FlowVar_internal { ) } - pragma[nomagic] + // The noopt is needed to avoid getting `variableLiveInSBB` joined in before + // `getASuccessor`. + pragma[noopt] private SubBasicBlock getAReachedBlockVarSBB(TBlockVar start) { - start = TBlockVar(result, _) + exists(Variable v | + start = TBlockVar(result, v) and + variableLiveInSBB(result, v) and + not largeVariable(v, _, _) + ) or exists(SubBasicBlock mid, SubBasicBlock sbbDef, Variable v | - start = TBlockVar(sbbDef, v) and mid = getAReachedBlockVarSBB(start) and + start = TBlockVar(sbbDef, v) and result = mid.getASuccessor() and + variableLiveInSBB(result, v) and not skipLoop(mid, result, sbbDef, v) and - not assignmentLikeOperation(result, v, _) + not assignmentLikeOperation(result, v, _, _) + ) + } + + /** + * Holds if `v` may have too many combinations of definitions and reached + * blocks for us the feasibly compute its def-use relation. + */ + private predicate largeVariable(Variable v, int liveBlocks, int defs) { + liveBlocks = strictcount(SubBasicBlock sbb | variableLiveInSBB(sbb, v)) and + defs = strictcount(SubBasicBlock sbb | exists(TBlockVar(sbb, v))) and + liveBlocks * defs > 1000000 + } + + /** + * Holds if a value held in `v` at the start of `sbb` (or after the first + * assignment, if that assignment is to `v`) might later be read. + */ + private predicate variableLiveInSBB(SubBasicBlock sbb, Variable v) { + variableAccessInSBB(v, sbb, _) + or + exists(SubBasicBlock succ | succ = sbb.getASuccessor() | + variableLiveInSBB(succ, v) and + not variableNotLiveBefore(succ, v) ) } + /** + * Holds if liveness of `v` should stop propagating backwards from `sbb`. + */ + private predicate variableNotLiveBefore(SubBasicBlock sbb, Variable v) { + assignmentLikeOperation(sbb, v, _, _) + or + // Liveness of `v` is killed when going backwards from a block that declares it + exists(DeclStmt ds | ds.getADeclaration().(LocalVariable) = v and sbb.contains(ds)) + } + /** Holds if `va` is a read access to `v` in `sbb`, where `v` is modeled by `BlockVar`. */ pragma[noinline] private predicate variableAccessInSBB(Variable v, SubBasicBlock sbb, VariableAccess va) { @@ -514,22 +644,28 @@ module FlowVar_internal { } /** - * Holds if `v` is modified as a side effect of evaluating `node`, receiving a - * value best described by `e`. This corresponds to `FlowVar::definedByExpr`, - * except that the case where `node instanceof Initializer` is covered by - * `initializer` instead of this predicate. + * Holds if `v` is modified through `va` as a side effect of evaluating + * `node`, receiving a value best described by `assignedExpr`. + * Assignment-like operations are those that desugar to a non-overloaded `=` + * assignment: `=`, `+=`, `++`, `--`, etc. + * + * This corresponds to `FlowVar::definedByExpr`, except that the case where + * `node instanceof Initializer` is covered by `initializer` instead of this + * predicate. */ predicate assignmentLikeOperation( - ControlFlowNode node, Variable v, Expr assignedExpr) - { + ControlFlowNode node, Variable v, VariableAccess va, Expr assignedExpr + ) { // Together, the two following cases cover `Assignment` node = any(AssignExpr ae | - v = ae.getLValue().(VariableAccess).getTarget() and + va = ae.getLValue() and + v = va.getTarget() and assignedExpr = ae.getRValue() ) or node = any(AssignOperation ao | - v = ao.getLValue().(VariableAccess).getTarget() and + va = ao.getLValue() and + v = va.getTarget() and // Here and in the `PrefixCrementOperation` case, we say that the assigned // expression is the operation itself. For example, we say that `x += 1` // assigns `x += 1` to `x`. The justification is that after this operation, @@ -540,16 +676,12 @@ module FlowVar_internal { // This case does not add further data flow paths, except if a // `PrefixCrementOperation` is itself a source node = any(CrementOperation op | - v = op.getOperand().(VariableAccess).getTarget() and + va = op.getOperand() and + v = va.getTarget() and assignedExpr = op ) } - predicate blockVarDefinedByReference(ControlFlowNode node, Variable v, Expr argument) { - node = v.getAnAccess() and - definitionByReference(node, argument) - } - /** * Holds if `v` is initialized by `init` to have value `assignedExpr`. */ @@ -571,9 +703,9 @@ module FlowVar_internal { exists(Variable v | not fullySupportedSsaVariable(v) | - assignmentLikeOperation(this, v, _) + assignmentLikeOperation(this, v, _, _) or - blockVarDefinedByReference(this, v, _) + this = any(PartialDefinition p | p.partiallyDefines(v)).getSubBasicBlockStart() // It is not necessary to cut the basic blocks at `Initializer` nodes // because the affected variable can have no _other_ value before its // initializer. It is not necessary to cut basic blocks at procedure diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/localFlow.expected b/cpp/ql/test/library-tests/dataflow/dataflow-tests/localFlow.expected index da5001b0b575..a14cc1d09292 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/localFlow.expected +++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/localFlow.expected @@ -5,12 +5,15 @@ | example.c:17:19:17:22 | {...} | example.c:24:13:24:18 | coords | | example.c:17:19:17:22 | {...} | example.c:26:2:26:7 | coords | | example.c:17:19:17:22 | {...} | example.c:26:19:26:24 | coords | +| example.c:24:2:24:7 | coords [post update] | example.c:26:2:26:7 | coords | +| example.c:24:2:24:7 | coords [post update] | example.c:26:19:26:24 | coords | +| example.c:24:13:24:18 | coords [post update] | example.c:24:2:24:7 | coords | +| example.c:24:13:24:18 | coords [post update] | example.c:26:2:26:7 | coords | +| example.c:24:13:24:18 | coords [post update] | example.c:26:19:26:24 | coords | | example.c:24:13:24:30 | ... = ... | example.c:24:2:24:30 | ... = ... | | example.c:24:24:24:30 | ... + ... | example.c:24:13:24:30 | ... = ... | | example.c:26:13:26:16 | call to getX | example.c:26:2:26:25 | ... = ... | | example.c:26:18:26:24 | ref arg & ... | example.c:26:2:26:7 | coords | -| example.c:26:18:26:24 | ref arg & ... | example.c:26:19:26:24 | coords | -| example.c:28:22:28:25 | ref arg & ... | example.c:28:23:28:25 | pos | | test.cpp:6:12:6:17 | call to source | test.cpp:7:8:7:9 | t1 | | test.cpp:6:12:6:17 | call to source | test.cpp:8:8:8:9 | t1 | | test.cpp:6:12:6:17 | call to source | test.cpp:9:8:9:9 | t1 | @@ -39,7 +42,7 @@ | test.cpp:431:12:431:13 | 0 | test.cpp:432:33:432:35 | tmp | | test.cpp:431:12:431:13 | 0 | test.cpp:433:8:433:10 | tmp | | test.cpp:432:10:432:13 | & ... | test.cpp:432:3:432:8 | call to memcpy | -| test.cpp:432:10:432:13 | ref arg & ... | test.cpp:432:11:432:13 | tmp | +| test.cpp:432:10:432:13 | ref arg & ... | test.cpp:432:3:432:8 | call to memcpy | | test.cpp:432:10:432:13 | ref arg & ... | test.cpp:432:33:432:35 | tmp | | test.cpp:432:10:432:13 | ref arg & ... | test.cpp:433:8:433:10 | tmp | | test.cpp:432:17:432:23 | source1 | test.cpp:432:10:432:13 | ref arg & ... | @@ -51,7 +54,7 @@ | test.cpp:437:12:437:13 | 0 | test.cpp:440:8:440:10 | tmp | | test.cpp:437:12:437:13 | 0 | test.cpp:442:10:442:12 | tmp | | test.cpp:439:10:439:13 | & ... | test.cpp:439:3:439:8 | call to memcpy | -| test.cpp:439:10:439:13 | ref arg & ... | test.cpp:439:11:439:13 | tmp | +| test.cpp:439:10:439:13 | ref arg & ... | test.cpp:439:3:439:8 | call to memcpy | | test.cpp:439:10:439:13 | ref arg & ... | test.cpp:439:33:439:35 | tmp | | test.cpp:439:10:439:13 | ref arg & ... | test.cpp:440:8:440:10 | tmp | | test.cpp:439:10:439:13 | ref arg & ... | test.cpp:442:10:442:12 | tmp | diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.cpp b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.cpp index 4e5bcdc015d8..d91c273f2dbf 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.cpp +++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.cpp @@ -135,7 +135,7 @@ void following_pointers( sourceStruct1_ptr->m1 = source(); sink(sourceStruct1_ptr->m1); // flow - sink(sourceStruct1_ptr->getFirst()); // no flow (due to limitations of the analysis) + sink(sourceStruct1_ptr->getFirst()); // flow [NOT DETECTED with IR] sink(sourceStruct1_ptr->m2); // no flow sink(sourceStruct1.m1); // flow (due to lack of no-alias tracking) @@ -410,11 +410,11 @@ class FlowThroughFields { int f() { sink(field); // tainted or clean? Not sure. taintField(); - sink(field); // tainted (FALSE NEGATIVE) + sink(field); // tainted [NOT DETECTED with IR] } int calledAfterTaint() { - sink(field); // tainted (FALSE NEGATIVE) + sink(field); // tainted [NOT DETECTED with IR] } int taintAndCall() { diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.expected b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.expected index 3a7167f2cd77..9968d63e3a88 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.expected +++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.expected @@ -15,6 +15,7 @@ | test.cpp:103:10:103:12 | ref | test.cpp:100:13:100:18 | call to source | | test.cpp:126:8:126:19 | sourceArray1 | test.cpp:120:9:120:20 | sourceArray1 | | test.cpp:137:27:137:28 | m1 | test.cpp:136:27:136:32 | call to source | +| test.cpp:138:27:138:34 | call to getFirst | test.cpp:136:27:136:32 | call to source | | test.cpp:140:22:140:23 | m1 | test.cpp:136:27:136:32 | call to source | | test.cpp:188:8:188:8 | y | test.cpp:186:27:186:32 | call to source | | test.cpp:192:8:192:8 | s | test.cpp:199:33:199:38 | call to source | @@ -27,6 +28,8 @@ | test.cpp:337:14:337:14 | x | test.cpp:353:17:353:22 | call to source | | test.cpp:366:7:366:7 | x | test.cpp:362:4:362:9 | call to source | | test.cpp:397:10:397:18 | globalVar | test.cpp:395:17:395:22 | call to source | +| test.cpp:413:10:413:14 | field | test.cpp:407:13:407:18 | call to source | +| test.cpp:417:10:417:14 | field | test.cpp:421:13:421:18 | call to source | | test.cpp:423:10:423:14 | field | test.cpp:421:13:421:18 | call to source | | test.cpp:433:8:433:10 | tmp | test.cpp:430:48:430:54 | source1 | | test.cpp:440:8:440:10 | tmp | test.cpp:436:53:436:59 | source1 | diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_diff.expected b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_diff.expected index b6d11257315d..6a2a933b6c7b 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_diff.expected +++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_diff.expected @@ -2,10 +2,13 @@ | test.cpp:100:13:100:18 | test.cpp:103:10:103:12 | AST only | | test.cpp:109:9:109:14 | test.cpp:110:10:110:12 | IR only | | test.cpp:136:27:136:32 | test.cpp:137:27:137:28 | AST only | +| test.cpp:136:27:136:32 | test.cpp:138:27:138:34 | AST only | | test.cpp:136:27:136:32 | test.cpp:140:22:140:23 | AST only | | test.cpp:142:32:142:37 | test.cpp:145:10:145:11 | IR only | | test.cpp:151:35:151:40 | test.cpp:153:17:153:18 | IR only | | test.cpp:395:17:395:22 | test.cpp:397:10:397:18 | AST only | +| test.cpp:407:13:407:18 | test.cpp:413:10:413:14 | AST only | +| test.cpp:421:13:421:18 | test.cpp:417:10:417:14 | AST only | | test.cpp:421:13:421:18 | test.cpp:423:10:423:14 | AST only | | test.cpp:430:48:430:54 | test.cpp:433:8:433:10 | AST only | | test.cpp:436:53:436:59 | test.cpp:440:8:440:10 | AST only | diff --git a/cpp/ql/test/library-tests/dataflow/fields/A.cpp b/cpp/ql/test/library-tests/dataflow/fields/A.cpp new file mode 100644 index 000000000000..703d1b9a73fa --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/fields/A.cpp @@ -0,0 +1,187 @@ +class A +{ + + class C + { + public: + virtual void insert(void *) {} + }; + class C1 : public C + { + public: + A *a; + }; + class C2 : public C + { + }; + + class B + { + public: + C *c; + B() {} + B(C *c) + { + this->c = c; + } + void set(C *c) { this->c = c; } + C *get() { return this->c; } + static B *make(C *c) + { + return new B(c); + } + }; + +public: + void f0() + { + C cc; + C ct; + cc.insert(nullptr); + ct.insert(new C()); + sink(&cc); // no flow + sink(&ct); // flow + } + void f1() + { + C *c = new C(); + B *b = B::make(c); + sink(b->c); // flow + } + + void f2() + { + B *b = new B(); + b->set(new C1()); + sink(b->get()); // flow + sink((new B(new C()))->get()); // flow + } + + void f3() + { + B *b1 = new B(); + B *b2; + b2 = setOnB(b1, new C2()); + sink(b1->c); // no flow + sink(b2->c); // flow + } + + void f4() + { + B *b1 = new B(); + B *b2; + b2 = setOnBWrap(b1, new C2()); + sink(b1->c); // no flow + sink(b2->c); // flow + } + + B *setOnBWrap(B *b1, C *c) + { + B *b2; + b2 = setOnB(b1, c); + return r() ? b1 : b2; + } + + A::B *setOnB(B *b1, C *c) + { + if (r()) + { + B *b2 = new B(); + b2->set(c); + return b2; + } + return b1; + } + + void f5() + { + A *a = new A(); + C1 *c1 = new C1(); + c1->a = a; + f6(c1); + } + void f6(C *c) + { + if (C1 *c1 = dynamic_cast(c)) + { + sink(c1->a); // flow + } + C *cc; + if (C2 *c2 = dynamic_cast(c)) + { + cc = c2; + } + else + { + cc = new C1(); + } + if (C1 *c1 = dynamic_cast(cc)) + { + sink(c1->a); // no flow, stopped by cast to C2 + } + } + + void f7(B *b) + { + b->set(new C()); + } + void f8() + { + B *b = new B(); + f7(b); + sink(b->c); // flow + } + + class D + { + public: + A::B *b; + + D(A::B *b, bool x) + { + b->c = new C(); + this->b = x ? b : new B(); + } + }; + + void + f9() + { + B *b = new B(); + D *d = new D(b, r()); + sink(d->b); // flow x2 + sink(d->b->c); // flow + sink(b->c); // flow + } + + void f10() + { + B *b = new B(); + MyList *l1 = new MyList(b, new MyList(nullptr, nullptr)); + MyList *l2 = new MyList(nullptr, l1); + MyList *l3 = new MyList(nullptr, l2); + sink(l3->head); // no flow, b is nested beneath at least one ->next + sink(l3->next->head); // flow, the precise nesting depth isn't tracked + sink(l3->next->next->head); // flow + sink(l3->next->next->next->head); // flow + for (MyList *l = l3; l != nullptr; l = l->next) + { + sink(l->head); // flow + } + } + + static void sink(void *o) {} + bool r() { return reinterpret_cast(this) % 10 > 5; } + + class MyList + { + public: + B *head; + MyList *next; + MyList(B *newHead, MyList *next) + { + head = newHead; + this->next = next; + } + }; +}; diff --git a/cpp/ql/test/library-tests/dataflow/fields/B.cpp b/cpp/ql/test/library-tests/dataflow/fields/B.cpp new file mode 100644 index 000000000000..13581ce3e60f --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/fields/B.cpp @@ -0,0 +1,49 @@ +class B +{ + + void f1() + { + Elem *e = new Elem(); + Box1 *b1 = new Box1(e, nullptr); + Box2 *b2 = new Box2(b1); + sink(b2->box1->elem1); // flow + sink(b2->box1->elem2); // FP due to flow in f2 below + } + + void f2() + { + Elem *e = new B::Elem(); + Box1 *b1 = new B::Box1(nullptr, e); + Box2 *b2 = new Box2(b1); + sink(b2->box1->elem1); // FP due to flow in f1 above + sink(b2->box1->elem2); // flow + } + + static void sink(void *o) {} + + class Elem + { + }; + + class Box1 + { + public: + Elem *elem1; + Elem *elem2; + Box1(Elem *e1, Elem *e2) + { + this->elem1 = e1; + this->elem2 = e2; + } + }; + + class Box2 + { + public: + Box1 *box1; + Box2(Box1 *b1) + { + this->box1 = b1; + } + }; +}; diff --git a/cpp/ql/test/library-tests/dataflow/fields/C.cpp b/cpp/ql/test/library-tests/dataflow/fields/C.cpp new file mode 100644 index 000000000000..ac87f48204bb --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/fields/C.cpp @@ -0,0 +1,37 @@ + +class C +{ + class Elem + { + }; + +private: + Elem *s1 = new Elem(); + const Elem *s2 = new Elem(); + Elem *s3; + +public: + const static Elem *s4; + + void main(void) + { + C *c = new C(); + c->func(); + } + + C() : s1(new Elem()) + { + this->s3 = new Elem(); + } + + void func() + { + sink(s1); + sink(s2); + sink(s3); + sink(s4); + } + + static void sink(const void *o) {} +}; +const C::Elem *C::s4 = new Elem(); \ No newline at end of file diff --git a/cpp/ql/test/library-tests/dataflow/fields/complex.cpp b/cpp/ql/test/library-tests/dataflow/fields/complex.cpp new file mode 100644 index 000000000000..5a6c67fa8df9 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/fields/complex.cpp @@ -0,0 +1,64 @@ +namespace Complex +{ +class Foo +{ + int a_; + int b_; + +public: + int a() { return a_; } + int b() { return b_; } + void setA(int a) { a_ = a; } + void setB(int b) { b_ = b; } + + Foo(int a, int b) : a_(a), b_(b){}; +}; + +class Bar +{ +public: + Foo f; + + Bar() : f(0, 0) {} +}; + +int user_input() +{ + return 42; +} + +void sink(int x) +{ +} + +void bar(Bar &b) +{ + sink(b.f.a()); // flow (through `b1.f.setA` and `b3.f.setA`) [NOT DETECTED] + sink(b.f.b()); // flow (through `b2.f.setB` and `b3.f.setB`) [NOT DETECTED] +} + +void foo() +{ + Bar b1; + Bar b2; + Bar b3; + Bar b4; + + b1.f.setA(user_input()); + b2.f.setB(user_input()); + b3.f.setA(user_input()); + b3.f.setB(user_input()); + + // Only a() should alert + bar(b1); + + // Only b() should alert + bar(b2); + + // Both a() and b() should alert + bar(b3); + + // Nothing should alert + bar(b4); +} +}; // namespace Complex diff --git a/cpp/ql/test/library-tests/dataflow/fields/constructors.cpp b/cpp/ql/test/library-tests/dataflow/fields/constructors.cpp new file mode 100644 index 000000000000..4aacab0c9e57 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/fields/constructors.cpp @@ -0,0 +1,51 @@ +namespace Constructors +{ +int user_input() +{ + return 42; +} + +void sink(int x) +{ +} + +class Foo +{ + int a_; + int b_; + +public: + int a() { return a_; } + int b() { return b_; } + void setA(int a) { a_ = a; } + void setB(int b) { b_ = b; } + + Foo(int a, int b) : a_(a), b_(b){}; +}; + +void bar(Foo &f) +{ + sink(f.a()); // flow (through `f` and `h`) [NOT DETECTED] + sink(f.b()); // flow (through `g` and `h`) [NOT DETECTED] +} + +void foo() +{ + Foo f(user_input(), 0); + Foo g(0, user_input()); + Foo h(user_input(), user_input()); + Foo i(0, 0); + + // Only a() should alert + bar(f); + + // Only b() should alert + bar(g); + + // Both a() and b() should alert + bar(h); + + // Nothing should alert + bar(i); +} +}; // namespace Constructors diff --git a/cpp/ql/test/library-tests/dataflow/fields/flow.expected b/cpp/ql/test/library-tests/dataflow/fields/flow.expected new file mode 100644 index 000000000000..5c627aa6c57b --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/fields/flow.expected @@ -0,0 +1,123 @@ +edges +| A.cpp:41:15:41:21 | new [void] | A.cpp:43:10:43:12 | & ... | +| A.cpp:47:12:47:18 | new [void] | A.cpp:48:20:48:20 | c [void] | +| A.cpp:48:12:48:18 | call to make [c, ... (1)] | A.cpp:49:10:49:10 | b [c, ... (1)] | +| A.cpp:48:20:48:20 | c [void] | A.cpp:48:12:48:18 | call to make [c, ... (1)] | +| A.cpp:49:10:49:10 | b [c, ... (1)] | A.cpp:49:13:49:13 | c | +| A.cpp:55:5:55:5 | b [post update] [c, ... (1)] | A.cpp:56:10:56:10 | b [c, ... (1)] | +| A.cpp:55:12:55:19 | new [void] | A.cpp:55:5:55:5 | b [post update] [c, ... (1)] | +| A.cpp:56:10:56:10 | b [c, ... (1)] | A.cpp:56:13:56:15 | call to get | +| A.cpp:57:11:57:24 | call to B [c, ... (1)] | A.cpp:57:11:57:24 | new [c, ... (1)] | +| A.cpp:57:11:57:24 | new [c, ... (1)] | A.cpp:57:28:57:30 | call to get | +| A.cpp:57:17:57:23 | new [void] | A.cpp:57:11:57:24 | call to B [c, ... (1)] | +| A.cpp:64:10:64:15 | call to setOnB [c, ... (1)] | A.cpp:66:10:66:11 | b2 [c, ... (1)] | +| A.cpp:64:21:64:28 | new [void] | A.cpp:64:10:64:15 | call to setOnB [c, ... (1)] | +| A.cpp:66:10:66:11 | b2 [c, ... (1)] | A.cpp:66:14:66:14 | c | +| A.cpp:73:10:73:19 | call to setOnBWrap [c, ... (1)] | A.cpp:75:10:75:11 | b2 [c, ... (1)] | +| A.cpp:73:25:73:32 | new [void] | A.cpp:73:10:73:19 | call to setOnBWrap [c, ... (1)] | +| A.cpp:75:10:75:11 | b2 [c, ... (1)] | A.cpp:75:14:75:14 | c | +| A.cpp:98:12:98:18 | new [void] | A.cpp:100:5:100:13 | ... = ... [void] | +| A.cpp:100:5:100:6 | c1 [post update] [a, ... (1)] | A.cpp:101:8:101:9 | c1 [a, ... (1)] | +| A.cpp:100:5:100:13 | ... = ... [void] | A.cpp:100:5:100:6 | c1 [post update] [a, ... (1)] | +| A.cpp:101:8:101:9 | c1 [a, ... (1)] | A.cpp:103:14:103:14 | c [a, ... (1)] | +| A.cpp:103:14:103:14 | c [a, ... (1)] | A.cpp:107:12:107:13 | c1 [a, ... (1)] | +| A.cpp:103:14:103:14 | c [a, ... (1)] | A.cpp:120:12:120:13 | c1 [a, ... (1)] | +| A.cpp:107:12:107:13 | c1 [a, ... (1)] | A.cpp:107:16:107:16 | a | +| A.cpp:120:12:120:13 | c1 [a, ... (1)] | A.cpp:120:16:120:16 | a | +| A.cpp:142:7:142:7 | b [post update] [c, ... (1)] | A.cpp:143:7:143:31 | ... = ... [c, ... (1)] | +| A.cpp:142:7:142:20 | ... = ... [void] | A.cpp:142:7:142:7 | b [post update] [c, ... (1)] | +| A.cpp:142:14:142:20 | new [void] | A.cpp:142:7:142:20 | ... = ... [void] | +| A.cpp:143:7:143:10 | this [post update] [b, ... (1)] | A.cpp:151:12:151:24 | call to D [b, ... (1)] | +| A.cpp:143:7:143:10 | this [post update] [b, ... (2)] | A.cpp:151:12:151:24 | call to D [b, ... (2)] | +| A.cpp:143:7:143:31 | ... = ... [c, ... (1)] | A.cpp:143:7:143:10 | this [post update] [b, ... (2)] | +| A.cpp:143:7:143:31 | ... = ... [void] | A.cpp:143:7:143:10 | this [post update] [b, ... (1)] | +| A.cpp:143:25:143:31 | new [void] | A.cpp:143:7:143:31 | ... = ... [void] | +| A.cpp:150:12:150:18 | new [void] | A.cpp:151:18:151:18 | b [void] | +| A.cpp:151:12:151:24 | call to D [b, ... (1)] | A.cpp:152:10:152:10 | d [b, ... (1)] | +| A.cpp:151:12:151:24 | call to D [b, ... (2)] | A.cpp:153:10:153:10 | d [b, ... (2)] | +| A.cpp:151:18:151:18 | b [void] | A.cpp:151:12:151:24 | call to D [b, ... (1)] | +| A.cpp:152:10:152:10 | d [b, ... (1)] | A.cpp:152:13:152:13 | b | +| A.cpp:153:10:153:10 | d [b, ... (2)] | A.cpp:153:13:153:13 | b [c, ... (1)] | +| A.cpp:153:13:153:13 | b [c, ... (1)] | A.cpp:153:16:153:16 | c | +| A.cpp:159:12:159:18 | new [void] | A.cpp:160:29:160:29 | b [void] | +| A.cpp:160:18:160:60 | call to MyList [head, ... (1)] | A.cpp:161:38:161:39 | l1 [head, ... (1)] | +| A.cpp:160:29:160:29 | b [void] | A.cpp:160:18:160:60 | call to MyList [head, ... (1)] | +| A.cpp:161:18:161:40 | call to MyList [next, ... (2)] | A.cpp:162:38:162:39 | l2 [next, ... (2)] | +| A.cpp:161:38:161:39 | l1 [head, ... (1)] | A.cpp:161:18:161:40 | call to MyList [next, ... (2)] | +| A.cpp:162:18:162:40 | call to MyList [next, ... (3)] | A.cpp:165:10:165:11 | l3 [next, ... (3)] | +| A.cpp:162:18:162:40 | call to MyList [next, ... (3)] | A.cpp:167:44:167:44 | l [next, ... (3)] | +| A.cpp:162:38:162:39 | l2 [next, ... (2)] | A.cpp:162:18:162:40 | call to MyList [next, ... (3)] | +| A.cpp:165:10:165:11 | l3 [next, ... (3)] | A.cpp:165:14:165:17 | next [next, ... (2)] | +| A.cpp:165:14:165:17 | next [next, ... (2)] | A.cpp:165:20:165:23 | next [head, ... (1)] | +| A.cpp:165:20:165:23 | next [head, ... (1)] | A.cpp:165:26:165:29 | head | +| A.cpp:167:44:167:44 | l [next, ... (2)] | A.cpp:167:47:167:50 | next [head, ... (1)] | +| A.cpp:167:44:167:44 | l [next, ... (3)] | A.cpp:167:47:167:50 | next [next, ... (2)] | +| A.cpp:167:47:167:50 | next [head, ... (1)] | A.cpp:169:12:169:12 | l [head, ... (1)] | +| A.cpp:167:47:167:50 | next [next, ... (2)] | A.cpp:167:44:167:44 | l [next, ... (2)] | +| A.cpp:169:12:169:12 | l [head, ... (1)] | A.cpp:169:15:169:18 | head | +| B.cpp:6:15:6:24 | new [void] | B.cpp:7:25:7:25 | e [void] | +| B.cpp:7:16:7:35 | call to Box1 [elem1, ... (1)] | B.cpp:8:25:8:26 | b1 [elem1, ... (1)] | +| B.cpp:7:25:7:25 | e [void] | B.cpp:7:16:7:35 | call to Box1 [elem1, ... (1)] | +| B.cpp:8:16:8:27 | call to Box2 [box1, ... (2)] | B.cpp:9:10:9:11 | b2 [box1, ... (2)] | +| B.cpp:8:16:8:27 | call to Box2 [box1, ... (2)] | B.cpp:10:10:10:11 | b2 [box1, ... (2)] | +| B.cpp:8:25:8:26 | b1 [elem1, ... (1)] | B.cpp:8:16:8:27 | call to Box2 [box1, ... (2)] | +| B.cpp:9:10:9:11 | b2 [box1, ... (2)] | B.cpp:9:14:9:17 | box1 [elem1, ... (1)] | +| B.cpp:9:14:9:17 | box1 [elem1, ... (1)] | B.cpp:9:20:9:24 | elem1 | +| B.cpp:10:10:10:11 | b2 [box1, ... (2)] | B.cpp:10:14:10:17 | box1 [elem2, ... (1)] | +| B.cpp:10:14:10:17 | box1 [elem2, ... (1)] | B.cpp:10:20:10:24 | elem2 | +| B.cpp:15:15:15:27 | new [void] | B.cpp:16:37:16:37 | e [void] | +| B.cpp:16:16:16:38 | call to Box1 [elem2, ... (1)] | B.cpp:17:25:17:26 | b1 [elem2, ... (1)] | +| B.cpp:16:37:16:37 | e [void] | B.cpp:16:16:16:38 | call to Box1 [elem2, ... (1)] | +| B.cpp:17:16:17:27 | call to Box2 [box1, ... (2)] | B.cpp:18:10:18:11 | b2 [box1, ... (2)] | +| B.cpp:17:16:17:27 | call to Box2 [box1, ... (2)] | B.cpp:19:10:19:11 | b2 [box1, ... (2)] | +| B.cpp:17:25:17:26 | b1 [elem2, ... (1)] | B.cpp:17:16:17:27 | call to Box2 [box1, ... (2)] | +| B.cpp:18:10:18:11 | b2 [box1, ... (2)] | B.cpp:18:14:18:17 | box1 [elem1, ... (1)] | +| B.cpp:18:14:18:17 | box1 [elem1, ... (1)] | B.cpp:18:20:18:24 | elem1 | +| B.cpp:19:10:19:11 | b2 [box1, ... (2)] | B.cpp:19:14:19:17 | box1 [elem2, ... (1)] | +| B.cpp:19:14:19:17 | box1 [elem2, ... (1)] | B.cpp:19:20:19:24 | elem2 | +| C.cpp:18:12:18:18 | call to C [s3, ... (1)] | C.cpp:19:5:19:5 | c [s3, ... (1)] | +| C.cpp:19:5:19:5 | c [s3, ... (1)] | C.cpp:27:8:27:11 | `this` parameter in func [s3, ... (1)] | +| C.cpp:24:5:24:8 | this [post update] [s3, ... (1)] | C.cpp:18:12:18:18 | call to C [s3, ... (1)] | +| C.cpp:24:5:24:25 | ... = ... [void] | C.cpp:24:5:24:8 | this [post update] [s3, ... (1)] | +| C.cpp:24:16:24:25 | new [void] | C.cpp:24:5:24:25 | ... = ... [void] | +| C.cpp:27:8:27:11 | `this` parameter in func [s3, ... (1)] | file://:0:0:0:0 | this [s3, ... (1)] | +| file://:0:0:0:0 | this [s3, ... (1)] | C.cpp:31:10:31:11 | s3 | +| simple.cpp:26:15:26:15 | f [a_, ... (1)] | simple.cpp:28:10:28:10 | f [a_, ... (1)] | +| simple.cpp:26:15:26:15 | f [b_, ... (1)] | simple.cpp:29:10:29:10 | f [b_, ... (1)] | +| simple.cpp:28:10:28:10 | f [a_, ... (1)] | simple.cpp:28:12:28:12 | call to a | +| simple.cpp:29:10:29:10 | f [b_, ... (1)] | simple.cpp:29:12:29:12 | call to b | +| simple.cpp:39:5:39:5 | f [post update] [a_, ... (1)] | simple.cpp:45:9:45:9 | f [a_, ... (1)] | +| simple.cpp:39:12:39:21 | call to user_input [void] | simple.cpp:39:5:39:5 | f [post update] [a_, ... (1)] | +| simple.cpp:40:5:40:5 | g [post update] [b_, ... (1)] | simple.cpp:48:9:48:9 | g [b_, ... (1)] | +| simple.cpp:40:12:40:21 | call to user_input [void] | simple.cpp:40:5:40:5 | g [post update] [b_, ... (1)] | +| simple.cpp:41:5:41:5 | h [post update] [a_, ... (1)] | simple.cpp:51:9:51:9 | h [a_, ... (1)] | +| simple.cpp:41:12:41:21 | call to user_input [void] | simple.cpp:41:5:41:5 | h [post update] [a_, ... (1)] | +| simple.cpp:42:5:42:5 | h [post update] [b_, ... (1)] | simple.cpp:51:9:51:9 | h [b_, ... (1)] | +| simple.cpp:42:12:42:21 | call to user_input [void] | simple.cpp:42:5:42:5 | h [post update] [b_, ... (1)] | +| simple.cpp:45:9:45:9 | f [a_, ... (1)] | simple.cpp:26:15:26:15 | f [a_, ... (1)] | +| simple.cpp:48:9:48:9 | g [b_, ... (1)] | simple.cpp:26:15:26:15 | f [b_, ... (1)] | +| simple.cpp:51:9:51:9 | h [a_, ... (1)] | simple.cpp:26:15:26:15 | f [a_, ... (1)] | +| simple.cpp:51:9:51:9 | h [b_, ... (1)] | simple.cpp:26:15:26:15 | f [b_, ... (1)] | +#select +| A.cpp:43:10:43:12 | & ... | A.cpp:41:15:41:21 | new [void] | A.cpp:43:10:43:12 | & ... | & ... flows from $@ | A.cpp:41:15:41:21 | new [void] | new [void] | +| A.cpp:49:13:49:13 | c | A.cpp:47:12:47:18 | new [void] | A.cpp:49:13:49:13 | c | c flows from $@ | A.cpp:47:12:47:18 | new [void] | new [void] | +| A.cpp:56:13:56:15 | call to get | A.cpp:55:12:55:19 | new [void] | A.cpp:56:13:56:15 | call to get | call to get flows from $@ | A.cpp:55:12:55:19 | new [void] | new [void] | +| A.cpp:57:28:57:30 | call to get | A.cpp:57:17:57:23 | new [void] | A.cpp:57:28:57:30 | call to get | call to get flows from $@ | A.cpp:57:17:57:23 | new [void] | new [void] | +| A.cpp:66:14:66:14 | c | A.cpp:64:21:64:28 | new [void] | A.cpp:66:14:66:14 | c | c flows from $@ | A.cpp:64:21:64:28 | new [void] | new [void] | +| A.cpp:75:14:75:14 | c | A.cpp:73:25:73:32 | new [void] | A.cpp:75:14:75:14 | c | c flows from $@ | A.cpp:73:25:73:32 | new [void] | new [void] | +| A.cpp:107:16:107:16 | a | A.cpp:98:12:98:18 | new [void] | A.cpp:107:16:107:16 | a | a flows from $@ | A.cpp:98:12:98:18 | new [void] | new [void] | +| A.cpp:120:16:120:16 | a | A.cpp:98:12:98:18 | new [void] | A.cpp:120:16:120:16 | a | a flows from $@ | A.cpp:98:12:98:18 | new [void] | new [void] | +| A.cpp:152:13:152:13 | b | A.cpp:143:25:143:31 | new [void] | A.cpp:152:13:152:13 | b | b flows from $@ | A.cpp:143:25:143:31 | new [void] | new [void] | +| A.cpp:152:13:152:13 | b | A.cpp:150:12:150:18 | new [void] | A.cpp:152:13:152:13 | b | b flows from $@ | A.cpp:150:12:150:18 | new [void] | new [void] | +| A.cpp:153:16:153:16 | c | A.cpp:142:14:142:20 | new [void] | A.cpp:153:16:153:16 | c | c flows from $@ | A.cpp:142:14:142:20 | new [void] | new [void] | +| A.cpp:165:26:165:29 | head | A.cpp:159:12:159:18 | new [void] | A.cpp:165:26:165:29 | head | head flows from $@ | A.cpp:159:12:159:18 | new [void] | new [void] | +| A.cpp:169:15:169:18 | head | A.cpp:159:12:159:18 | new [void] | A.cpp:169:15:169:18 | head | head flows from $@ | A.cpp:159:12:159:18 | new [void] | new [void] | +| B.cpp:9:20:9:24 | elem1 | B.cpp:6:15:6:24 | new [void] | B.cpp:9:20:9:24 | elem1 | elem1 flows from $@ | B.cpp:6:15:6:24 | new [void] | new [void] | +| B.cpp:10:20:10:24 | elem2 | B.cpp:6:15:6:24 | new [void] | B.cpp:10:20:10:24 | elem2 | elem2 flows from $@ | B.cpp:6:15:6:24 | new [void] | new [void] | +| B.cpp:18:20:18:24 | elem1 | B.cpp:15:15:15:27 | new [void] | B.cpp:18:20:18:24 | elem1 | elem1 flows from $@ | B.cpp:15:15:15:27 | new [void] | new [void] | +| B.cpp:19:20:19:24 | elem2 | B.cpp:15:15:15:27 | new [void] | B.cpp:19:20:19:24 | elem2 | elem2 flows from $@ | B.cpp:15:15:15:27 | new [void] | new [void] | +| C.cpp:31:10:31:11 | s3 | C.cpp:24:16:24:25 | new [void] | C.cpp:31:10:31:11 | s3 | s3 flows from $@ | C.cpp:24:16:24:25 | new [void] | new [void] | +| simple.cpp:28:12:28:12 | call to a | simple.cpp:39:12:39:21 | call to user_input [void] | simple.cpp:28:12:28:12 | call to a | call to a flows from $@ | simple.cpp:39:12:39:21 | call to user_input [void] | call to user_input [void] | +| simple.cpp:28:12:28:12 | call to a | simple.cpp:41:12:41:21 | call to user_input [void] | simple.cpp:28:12:28:12 | call to a | call to a flows from $@ | simple.cpp:41:12:41:21 | call to user_input [void] | call to user_input [void] | +| simple.cpp:29:12:29:12 | call to b | simple.cpp:40:12:40:21 | call to user_input [void] | simple.cpp:29:12:29:12 | call to b | call to b flows from $@ | simple.cpp:40:12:40:21 | call to user_input [void] | call to user_input [void] | +| simple.cpp:29:12:29:12 | call to b | simple.cpp:42:12:42:21 | call to user_input [void] | simple.cpp:29:12:29:12 | call to b | call to b flows from $@ | simple.cpp:42:12:42:21 | call to user_input [void] | call to user_input [void] | diff --git a/cpp/ql/test/library-tests/dataflow/fields/flow.ql b/cpp/ql/test/library-tests/dataflow/fields/flow.ql new file mode 100644 index 000000000000..131973534bf9 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/fields/flow.ql @@ -0,0 +1,37 @@ +/** + * @kind path-problem + */ + +import semmle.code.cpp.dataflow.DataFlow +import DataFlow::PathGraph +import DataFlow +import cpp + +class Conf extends Configuration { + Conf() { this = "FieldFlowConf" } + + override predicate isSource(Node src) { + src.asExpr() instanceof NewExpr + or + src.asExpr().(Call).getTarget().hasName("user_input") + } + + override predicate isSink(Node sink) { + exists(Call c | + c.getTarget().hasName("sink") and + c.getAnArgument() = sink.asExpr() + ) + } + + override predicate isAdditionalFlowStep(Node a, Node b) { + b.asPartialDefinition() = any(Call c | + c.getTarget().hasName("insert") and c.getAnArgument() = a.asExpr() + ).getQualifier() + or + b.asExpr().(AddressOfExpr).getOperand() = a.asExpr() + } +} + +from DataFlow::PathNode src, DataFlow::PathNode sink, Conf conf +where conf.hasFlowPath(src, sink) +select sink, src, sink, sink + " flows from $@", src, src.toString() diff --git a/cpp/ql/test/library-tests/dataflow/fields/simple.cpp b/cpp/ql/test/library-tests/dataflow/fields/simple.cpp new file mode 100644 index 000000000000..6f36fa6551df --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/fields/simple.cpp @@ -0,0 +1,56 @@ +namespace Simple +{ +int user_input() +{ + return 42; +} + +void sink(int x) +{ +} + +class Foo +{ + int a_; + int b_; + +public: + int a() { return a_; } + int b() { return b_; } + void setA(int a) { a_ = a; } + void setB(int b) { b_ = b; } + + Foo(int a, int b) : a_(a), b_(b){}; +}; + +void bar(Foo &f) +{ + sink(f.a()); // flow (through `f.setA` and `h.setA`) + sink(f.b()); // flow (through `g.setB` and `h.setB`) +} + +void foo() +{ + Foo f(0, 0); + Foo g(0, 0); + Foo h(0, 0); + Foo i(0, 0); + + f.setA(user_input()); + g.setB(user_input()); + h.setA(user_input()); + h.setB(user_input()); + + // Only a() should alert + bar(f); + + // Only b() should alert + bar(g); + + // Both a() and b() should alert + bar(h); + + // Nothing should alert + bar(i); +} +} // namespace Simple diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected b/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected index ac9d34d7cfd3..875e5619c840 100644 --- a/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected +++ b/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected @@ -1,3 +1,4 @@ +| file://:0:0:0:0 | this | file://:0:0:0:0 | this | | | file://:0:0:0:0 | this | taint.cpp:72:3:72:3 | c | TAINT | | file://:0:0:0:0 | this | taint.cpp:73:3:73:3 | d | TAINT | | file://:0:0:0:0 | this | taint.cpp:77:3:77:3 | d | TAINT | @@ -43,10 +44,13 @@ | taint.cpp:37:12:37:20 | call to increment | taint.cpp:43:7:43:13 | global9 | | | taint.cpp:38:13:38:16 | call to zero | taint.cpp:38:2:38:26 | ... = ... | | | taint.cpp:38:13:38:16 | call to zero | taint.cpp:44:7:44:14 | global10 | | +| taint.cpp:71:2:71:8 | `this` parameter in MyClass | file://:0:0:0:0 | this | | | taint.cpp:71:14:71:17 | 0 | taint.cpp:71:14:71:17 | constructor init of field a | TAINT | | taint.cpp:71:22:71:27 | call to source | taint.cpp:71:20:71:30 | constructor init of field b | TAINT | +| taint.cpp:72:3:72:3 | this [post update] | file://:0:0:0:0 | this | | | taint.cpp:72:7:72:12 | call to source | taint.cpp:72:3:72:14 | ... = ... | | | taint.cpp:73:7:73:7 | 0 | taint.cpp:73:3:73:7 | ... = ... | | +| taint.cpp:76:7:76:14 | `this` parameter in myMethod | file://:0:0:0:0 | this | | | taint.cpp:77:7:77:12 | call to source | taint.cpp:77:3:77:14 | ... = ... | | | taint.cpp:84:10:84:12 | call to MyClass | taint.cpp:86:2:86:4 | mc1 | | | taint.cpp:84:10:84:12 | call to MyClass | taint.cpp:88:7:88:9 | mc1 | | @@ -57,6 +61,10 @@ | taint.cpp:84:15:84:17 | call to MyClass | taint.cpp:93:7:93:9 | mc2 | | | taint.cpp:84:15:84:17 | call to MyClass | taint.cpp:94:7:94:9 | mc2 | | | taint.cpp:84:15:84:17 | call to MyClass | taint.cpp:95:7:95:9 | mc2 | | +| taint.cpp:86:2:86:4 | mc1 [post update] | taint.cpp:88:7:88:9 | mc1 | | +| taint.cpp:86:2:86:4 | mc1 [post update] | taint.cpp:89:7:89:9 | mc1 | | +| taint.cpp:86:2:86:4 | mc1 [post update] | taint.cpp:90:7:90:9 | mc1 | | +| taint.cpp:86:2:86:4 | mc1 [post update] | taint.cpp:91:7:91:9 | mc1 | | | taint.cpp:88:7:88:9 | mc1 | taint.cpp:88:11:88:11 | a | TAINT | | taint.cpp:89:7:89:9 | mc1 | taint.cpp:89:11:89:11 | b | TAINT | | taint.cpp:90:7:90:9 | mc1 | taint.cpp:90:11:90:11 | c | TAINT | @@ -133,30 +141,27 @@ | taint.cpp:165:22:165:25 | {...} | taint.cpp:173:8:173:13 | buffer | | | taint.cpp:165:24:165:24 | 0 | taint.cpp:165:22:165:25 | {...} | TAINT | | taint.cpp:170:10:170:15 | buffer | taint.cpp:170:3:170:8 | call to strcpy | | -| taint.cpp:170:10:170:15 | ref arg buffer | taint.cpp:170:10:170:15 | buffer | | +| taint.cpp:170:10:170:15 | ref arg buffer | taint.cpp:170:3:170:8 | call to strcpy | | | taint.cpp:170:10:170:15 | ref arg buffer | taint.cpp:171:8:171:13 | buffer | | | taint.cpp:170:10:170:15 | ref arg buffer | taint.cpp:172:10:172:15 | buffer | | | taint.cpp:170:10:170:15 | ref arg buffer | taint.cpp:173:8:173:13 | buffer | | | taint.cpp:170:18:170:26 | Hello, | taint.cpp:170:10:170:15 | ref arg buffer | TAINT | -| taint.cpp:171:8:171:13 | ref arg buffer | taint.cpp:171:8:171:13 | buffer | | | taint.cpp:171:8:171:13 | ref arg buffer | taint.cpp:172:10:172:15 | buffer | | | taint.cpp:171:8:171:13 | ref arg buffer | taint.cpp:173:8:173:13 | buffer | | | taint.cpp:172:10:172:15 | buffer | taint.cpp:172:3:172:8 | call to strcat | | | taint.cpp:172:10:172:15 | buffer | taint.cpp:172:10:172:15 | ref arg buffer | TAINT | -| taint.cpp:172:10:172:15 | ref arg buffer | taint.cpp:172:10:172:15 | buffer | | +| taint.cpp:172:10:172:15 | ref arg buffer | taint.cpp:172:3:172:8 | call to strcat | | | taint.cpp:172:10:172:15 | ref arg buffer | taint.cpp:173:8:173:13 | buffer | | | taint.cpp:172:18:172:24 | tainted | taint.cpp:172:10:172:15 | ref arg buffer | TAINT | -| taint.cpp:173:8:173:13 | ref arg buffer | taint.cpp:173:8:173:13 | buffer | | | taint.cpp:180:19:180:19 | p | taint.cpp:181:9:181:9 | p | | | taint.cpp:181:9:181:9 | p | taint.cpp:181:8:181:9 | * ... | TAINT | | taint.cpp:185:11:185:16 | call to source | taint.cpp:186:11:186:11 | x | | -| taint.cpp:186:10:186:11 | ref arg & ... | taint.cpp:186:11:186:11 | x | | | taint.cpp:186:11:186:11 | x | taint.cpp:186:10:186:11 | & ... | TAINT | | taint.cpp:192:23:192:28 | source | taint.cpp:194:13:194:18 | source | | | taint.cpp:193:6:193:6 | x | taint.cpp:194:10:194:10 | x | | | taint.cpp:193:6:193:6 | x | taint.cpp:195:7:195:7 | x | | | taint.cpp:194:9:194:10 | & ... | taint.cpp:194:2:194:7 | call to memcpy | | -| taint.cpp:194:9:194:10 | ref arg & ... | taint.cpp:194:10:194:10 | x | | +| taint.cpp:194:9:194:10 | ref arg & ... | taint.cpp:194:2:194:7 | call to memcpy | | | taint.cpp:194:9:194:10 | ref arg & ... | taint.cpp:195:7:195:7 | x | | | taint.cpp:194:10:194:10 | x | taint.cpp:194:9:194:10 | & ... | TAINT | | taint.cpp:194:13:194:18 | source | taint.cpp:194:9:194:10 | ref arg & ... | TAINT | @@ -169,9 +174,7 @@ | taint.cpp:208:6:208:6 | 0 | taint.cpp:211:7:211:7 | y | | | taint.cpp:208:6:208:6 | 0 | taint.cpp:213:15:213:15 | y | | | taint.cpp:208:6:208:6 | 0 | taint.cpp:216:7:216:7 | y | | -| taint.cpp:213:12:213:12 | ref arg x | taint.cpp:213:12:213:12 | x | | | taint.cpp:213:12:213:12 | ref arg x | taint.cpp:215:7:215:7 | x | | | taint.cpp:213:12:213:12 | x | taint.cpp:213:15:213:15 | ref arg y | | -| taint.cpp:213:15:213:15 | ref arg y | taint.cpp:213:15:213:15 | y | | | taint.cpp:213:15:213:15 | ref arg y | taint.cpp:216:7:216:7 | y | | | taint.cpp:213:15:213:15 | y | taint.cpp:213:12:213:12 | ref arg x | | diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/taint.cpp b/cpp/ql/test/library-tests/dataflow/taint-tests/taint.cpp index 90d043dd45b6..9cca2dffa918 100644 --- a/cpp/ql/test/library-tests/dataflow/taint-tests/taint.cpp +++ b/cpp/ql/test/library-tests/dataflow/taint-tests/taint.cpp @@ -87,11 +87,11 @@ void class_field_test() { sink(mc1.a); sink(mc1.b); // tainted [NOT DETECTED] - sink(mc1.c); // tainted [NOT DETECTED] - sink(mc1.d); // tainted [NOT DETECTED] + sink(mc1.c); // tainted [NOT DETECTED with IR] + sink(mc1.d); // tainted [NOT DETECTED with IR] sink(mc2.a); sink(mc2.b); // tainted [NOT DETECTED] - sink(mc2.c); // tainted [NOT DETECTED] + sink(mc2.c); // tainted [NOT DETECTED with IR] sink(mc2.d); } diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/taint.expected b/cpp/ql/test/library-tests/dataflow/taint-tests/taint.expected index dd03a3b49c52..c27bc148c760 100644 --- a/cpp/ql/test/library-tests/dataflow/taint-tests/taint.expected +++ b/cpp/ql/test/library-tests/dataflow/taint-tests/taint.expected @@ -4,6 +4,9 @@ | taint.cpp:41:7:41:13 | global7 | taint.cpp:35:12:35:17 | call to source | | taint.cpp:42:7:42:13 | global8 | taint.cpp:35:12:35:17 | call to source | | taint.cpp:43:7:43:13 | global9 | taint.cpp:37:22:37:27 | call to source | +| taint.cpp:90:11:90:11 | c | taint.cpp:72:7:72:12 | call to source | +| taint.cpp:91:11:91:11 | d | taint.cpp:77:7:77:12 | call to source | +| taint.cpp:94:11:94:11 | c | taint.cpp:72:7:72:12 | call to source | | taint.cpp:129:7:129:9 | * ... | taint.cpp:120:11:120:16 | call to source | | taint.cpp:134:7:134:9 | * ... | taint.cpp:120:11:120:16 | call to source | | taint.cpp:137:7:137:9 | * ... | taint.cpp:120:11:120:16 | call to source | diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/test_diff.expected b/cpp/ql/test/library-tests/dataflow/taint-tests/test_diff.expected index c43a3511ee89..d86b5b43ef05 100644 --- a/cpp/ql/test/library-tests/dataflow/taint-tests/test_diff.expected +++ b/cpp/ql/test/library-tests/dataflow/taint-tests/test_diff.expected @@ -1,6 +1,9 @@ | taint.cpp:41:7:41:13 | taint.cpp:35:12:35:17 | AST only | | taint.cpp:42:7:42:13 | taint.cpp:35:12:35:17 | AST only | | taint.cpp:43:7:43:13 | taint.cpp:37:22:37:27 | AST only | +| taint.cpp:90:11:90:11 | taint.cpp:72:7:72:12 | AST only | +| taint.cpp:91:11:91:11 | taint.cpp:77:7:77:12 | AST only | +| taint.cpp:94:11:94:11 | taint.cpp:72:7:72:12 | AST only | | taint.cpp:130:7:130:9 | taint.cpp:127:8:127:13 | IR only | | taint.cpp:137:7:137:9 | taint.cpp:120:11:120:16 | AST only | | taint.cpp:173:8:173:13 | taint.cpp:164:19:164:24 | AST only |