diff --git a/unified/ql/consistency-queries/DataFlowConsistency.ql b/unified/ql/consistency-queries/DataFlowConsistency.ql new file mode 100644 index 000000000000..267abd674cb5 --- /dev/null +++ b/unified/ql/consistency-queries/DataFlowConsistency.ql @@ -0,0 +1,10 @@ +private import unified +private import codeql.unified.internal.dataflow.AllDataFlow +private import codeql.dataflow.internal.DataFlowImplConsistency + +module ConsistencyInput implements InputSig { } + +module ConsistencyOutput = + MakeConsistency; + +import ConsistencyOutput diff --git a/unified/ql/consistency-queries/LocalSsaConsistency.ql b/unified/ql/consistency-queries/LocalSsaConsistency.ql new file mode 100644 index 000000000000..209adfca340a --- /dev/null +++ b/unified/ql/consistency-queries/LocalSsaConsistency.ql @@ -0,0 +1,3 @@ +private import unified +private import codeql.unified.internal.dataflow.LocalSsa +import LocalSsaOutput::Consistency diff --git a/unified/ql/lib/codeql/unified/internal/AstExtra.qll b/unified/ql/lib/codeql/unified/internal/AstExtra.qll index 5e8e54faa61a..64ab0262ea56 100644 --- a/unified/ql/lib/codeql/unified/internal/AstExtra.qll +++ b/unified/ql/lib/codeql/unified/internal/AstExtra.qll @@ -60,15 +60,20 @@ module Public { TopLevelStmt() { this = any(TopLevel t).getBody().getAStmt() } } + /** An identifier appearing in the context of a break/continue label, argument/parameter name, or name of a member lookup. */ + final class IdentifierLabel extends Identifier { + IdentifierLabel() { + this = any(MemberAccessExpr e).getMemberNameNode() or + this = any(Argument a).getNameNode() or + this = any(Parameter p).getExternalNameNode() or + this = any(LabeledStmt stmt).getLabelNameNode() or + this = any(BreakExpr expr).getLabelNameNode() or + this = any(ContinueExpr expr).getLabelNameNode() + } + } + /** An identifier appearing in the context of an expression, pattern, or type annotation. */ final class IdentifierExpr extends Identifier { - IdentifierExpr() { - not this = any(MemberAccessExpr e).getMemberNameNode() and - not this = any(Argument a).getNameNode() and - not this = any(Parameter p).getExternalNameNode() and - not this = any(LabeledStmt stmt).getLabelNameNode() and - not this = any(BreakExpr expr).getLabelNameNode() and - not this = any(ContinueExpr expr).getLabelNameNode() - } + IdentifierExpr() { not this instanceof IdentifierLabel } } } diff --git a/unified/ql/lib/codeql/unified/internal/ExprPositions.qll b/unified/ql/lib/codeql/unified/internal/ExprPositions.qll new file mode 100644 index 000000000000..2e64053642eb --- /dev/null +++ b/unified/ql/lib/codeql/unified/internal/ExprPositions.qll @@ -0,0 +1,70 @@ +private import unified +private import NameBinding as NameBinding + +/** + * Holds if `expr` appears in the context of a type annotation. + */ +predicate isInTypeContext(Expr expr) { + expr = any(TypeCastExpr n).getType() + or + expr = any(TypeTestExpr n).getType() + or + expr = any(VariableDeclaration n).getType() + or + expr = any(FunctionDeclaration n).getReturnType() + or + expr = any(FunctionExpr n).getReturnType() + or + expr = any(AccessorDeclaration n).getType() + or + expr = any(Parameter n).getType() + or + expr = any(TypeAliasDeclaration n).getType() + or + expr = any(BaseType n).getType() + or + expr = any(TypeParameter n).getBound() + or + expr = any(AssociatedTypeDeclaration n).getBound() + or + expr.getParent() instanceof TypeConstraint + or + isInTypeContext(expr.getEnclosingExpr()) +} + +/** Holds if `e` appears in a name-binding position inside `declaration` */ +predicate isInBindingContext(Expr e, AstNode declaration) { + NameBinding::bindingContext(e, _, declaration) +} + +/** Holds if `e` is part of the target of `assignment`. */ +predicate isInAssignmentContext(Expr e, AstNode assignment) { + e = assignment.(AssignExpr).getTarget() + or + e = assignment.(CompoundAssignExpr).getTarget() + or + exists(TupleExpr tuple | + isInAssignmentContext(tuple, assignment) and + e = tuple.getAnElement().getValue() + ) +} + +/** + * Holds if `e` receives an incoming value because it is part of a binding pattern + * or assignment target. + */ +predicate hasIncomingValue(Expr e, AstNode declarationOrAssignment) { + isInBindingContext(e, declarationOrAssignment) + or + isInAssignmentContext(e, declarationOrAssignment) +} + +/** + * Holds if `e` evaluates to a result. + */ +predicate hasResultValue(Expr e) { + not isInTypeContext(e) and + not isInBindingContext(e, _) and + not isInAssignmentContext(e, any(AssignExpr n)) and // non-compound assignment target + not e instanceof IdentifierLabel +} diff --git a/unified/ql/lib/codeql/unified/internal/FacadeAst.qll b/unified/ql/lib/codeql/unified/internal/FacadeAst.qll index 5435876b3646..54efc88a473d 100644 --- a/unified/ql/lib/codeql/unified/internal/FacadeAst.qll +++ b/unified/ql/lib/codeql/unified/internal/FacadeAst.qll @@ -197,5 +197,8 @@ module Unified { result = arg.getValue() ) } + + /** Gets the number of arguments passed to this call, not counting implicit arguments like receiver. */ + int getNumberOfArguments() { result = count(this.getAnArgument()) } } } diff --git a/unified/ql/lib/codeql/unified/internal/StaticNameBinding.qll b/unified/ql/lib/codeql/unified/internal/StaticNameBinding.qll index 38060266c258..cf8e216bd7e0 100644 --- a/unified/ql/lib/codeql/unified/internal/StaticNameBinding.qll +++ b/unified/ql/lib/codeql/unified/internal/StaticNameBinding.qll @@ -667,6 +667,9 @@ module Public { LocalVariable getImplicitQualifierVariable() { ResolveImplicitReceiverAccess::access(this, result) } + + /** Gets the simple name of this identifier, that is, the name of the member being accessed. */ + string getName() { result = this.getValue() } } } diff --git a/unified/ql/lib/codeql/unified/internal/dataflow/AllDataFlow.qll b/unified/ql/lib/codeql/unified/internal/dataflow/AllDataFlow.qll new file mode 100644 index 000000000000..893e62d3b140 --- /dev/null +++ b/unified/ql/lib/codeql/unified/internal/dataflow/AllDataFlow.qll @@ -0,0 +1,11 @@ +/** Re-exports all the files in the internal dataflow folder (except DataFlowPublic). */ + +import Content +import DataFlowGraph +import DataFlowInstantiation +import DataFlowNode +import DataFlowPlugin +import Step +import LocalSsa +import TaintTrackingInstantiation +import VariableRefKind diff --git a/unified/ql/lib/codeql/unified/internal/dataflow/Content.qll b/unified/ql/lib/codeql/unified/internal/dataflow/Content.qll new file mode 100644 index 000000000000..73a3d7da2c9d --- /dev/null +++ b/unified/ql/lib/codeql/unified/internal/dataflow/Content.qll @@ -0,0 +1,37 @@ +private import unified +private import AllDataFlow + +private newtype TContent = + TNamedMember(string name) { + name = any(Identifier id).getValue() + or + // Tuple elements can be accessed as named members, e.g. `tuple.0`, `tuple.1`, etc, + // so just model their elements as named members. + name = [0 .. 20].toString() + } + +class Content extends TContent { + string asNamedMember() { this = TNamedMember(result) } + + string toString() { result = this.asNamedMember() } + + Location getLocation() { none() } +} + +private newtype TContentSet = TSingleton(Content content) + +class ContentSet extends TContentSet { + Content asSingleton() { this = TSingleton(result) } + + string toString() { result = this.asSingleton().toString() } + + Location getLocation() { result = this.asSingleton().getLocation() } + + Content getAStoreContent() { result = this.asSingleton() } + + Content getAReadContent() { result = this.asSingleton() } +} + +module ContentSet { + ContentSet namedMember(string name) { result.asSingleton().asNamedMember() = name } +} diff --git a/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowGraph.qll b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowGraph.qll new file mode 100644 index 000000000000..1edff6eea04b --- /dev/null +++ b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowGraph.qll @@ -0,0 +1,118 @@ +private import unified +private import AllDataFlow + +predicate step(Node node1, Step step, Node node2) { + any(DataFlowPlugin p).step(node1, step, node2) + or + exists(VariableDeclaration decl | + node1.isResultValue(decl.getValue()) and + step.value() and + node2.isIncomingValue(decl.getPattern()) + ) + or + exists(AssignExpr assign | + node1.isResultValue(assign.getValue()) and + step.value() and + node2.isIncomingValue(assign.getTarget()) + ) + or + exists(LocalVariableAccess access | + node1.isLocalVariableRead(access, access.getLocalVariable()) and + step.value() and + node2.isResultValue(access) + or + node1.isIncomingValue(access) and + step.value() and + node2.isLocalVariableWrite(access, access.getLocalVariable()) + or + node1.isPostUpdate(access) and + step.value() and + node2.isLocalVariablePostUpdate(access, access.getLocalVariable()) + ) + or + exists(UnqualifiedMemberAccess access | access.isInstanceAccess() | + node1.isLocalVariableRead(access, access.getImplicitQualifierVariable()) and + step.readName(access.getName()) and + node2.isResultValue(access) + or + (node1.isIncomingValue(access) or node1.isPostUpdate(access)) and + step.storeName(access.getName()) and + node2.isLocalVariablePostUpdate(access, access.getImplicitQualifierVariable()) + ) + or + exists(StringInterpolationExpr expr | + node1.isResultValue(expr.getAnElement()) and + step.taint() and + node2.isResultValue(expr) + ) + or + exists(TupleExpr expr, int i | + node1.isResultValue(expr.getElement(i).getValue()) and + step.storeName(i.toString()) and + node2.isResultValue(expr) + or + node1.isIncomingValue(expr) and + step.readName(i.toString()) and + node2.isIncomingValue(expr.getElement(i).getValue()) + ) + or + exists(MemberAccessExpr expr | + node1.isResultValue(expr.getBase()) and + step.readName(expr.getMemberName()) and + node2.isResultValue(expr) + or + (node1.isIncomingValue(expr) or node1.isPostUpdate(expr)) and + step.storeName(expr.getMemberName()) and + node2.isPostUpdate(expr.getBase()) + ) + or + none() // Temporarily disable compilation errors from unsatisfiable types +} + +/** Holds if `node` should be included in the debug view. */ +private signature predicate relevantNodeSig(AstNode node); + +module DebugGraph { + private Node adjacent(Node n) { + step(n, _, result) + or + step(result, _, n) + or + localSsaStep(n, result, _) + or + localSsaStep(result, n, _) + } + + private predicate relevantDataFlowNode(Node node) { + relevantNode(node.getWrappedAstNode()) + or + not exists(node.getWrappedAstNode()) and + relevantDataFlowNode(adjacent(node)) + } + + query predicate nodes(Node node, string key, string value) { + relevantDataFlowNode(node) and + key = "semmle.label" and + value = node.toString() + } + + query predicate edges(Node node1, Node node2, string key, string value) { + key = "semmle.label" and + relevantDataFlowNode(node1) and + relevantDataFlowNode(node2) and + ( + exists(Step step | + step(node1, step, node2) and + value = step.toString() + ) + or + exists(boolean isUseStep | + localSsaStep(node1, node2, isUseStep) and + if isUseStep = true then value = "use-use" else value = "def-use" + ) + or + node2 = getPostUpdateNode(node1) and + value = "post-update" + ) + } +} diff --git a/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowInstantiation.qll b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowInstantiation.qll new file mode 100644 index 000000000000..da5602cddd29 --- /dev/null +++ b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowInstantiation.qll @@ -0,0 +1,199 @@ +private import unified +private import AllDataFlow +private import AllDataFlow as D +private import codeql.dataflow.DataFlow +private import codeql.util.Void +private import codeql.util.Unit + +module DataFlowInput implements InputSig { + class Node = D::Node; + + // + // Contents + // + class Content = D::Content; + + class ContentSet = D::ContentSet; + + class ContentApprox = Content; // TODO + + ContentApprox getContentApprox(Content c) { result = c } // TODO + + // + // Parameter, argument, return, and out nodes and their positions/kinds + // + class ParameterNode extends Node { + ParameterNode() { none() } // TODO + } + + class ArgumentNode extends Node { + ArgumentNode() { none() } // TODO + } + + class ReturnNode extends Node { + ReturnNode() { none() } // TODO + + ReturnKind getKind() { none() } // TODO + } + + class OutNode extends Node { + OutNode() { none() } // TODO + } + + class ReturnKind = Unit; + + class ParameterPosition extends Void { + ParameterPosition() { none() } // TODO + + bindingset[this] + string toString() { none() } // TODO + } + + class ArgumentPosition extends Void { + ArgumentPosition() { none() } // TODO + + bindingset[this] + string toString() { none() } // TODO + } + + predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) { none() } // TODO + + // + // Calls and callables + // + class DataFlowCall extends Void { + Location getLocation() { none() } // TODO + + DataFlowCallable getEnclosingCallable() { none() } // TODO + } + + class DataFlowCallable = Callable; // TODO: Use newtype + + DataFlowCallable viableCallable(DataFlowCall c) { none() } // TODO + + DataFlowCallable nodeGetEnclosingCallable(Node node) { result = node.getEnclosingCallable() } + + predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition pos) { + none() // TODO + } + + predicate isArgumentNode(ArgumentNode n, DataFlowCall call, ArgumentPosition pos) { + none() // TODO + } + + OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { none() } // TODO + + // + // Post-update nodes + // + class PostUpdateNode extends Node { + PostUpdateNode() { this = getPostUpdateNode(_) } + + Node getPreUpdateNode() { this = getPostUpdateNode(result) } + } + + // + // Types + // + class DataFlowType extends Unit { + // TODO: track proper types + string toString() { result = "" } // do not include "unit" type in path steps + } + + class CastNode extends Node { + CastNode() { none() } // TODO + } + + DataFlowType getNodeType(Node node) { any() } // TODO + + predicate compatibleTypes(DataFlowType t1, DataFlowType t2) { any() } // TODO + + predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) { any() } // TODO + + // + // Steps + // + predicate simpleLocalFlowStep(Node node1, Node node2, string model) { + step(node1, any(Step s | s.value()), node2) and model = "" + or + localSsaStep(node1, node2, _) and model = "" + } + + predicate jumpStep(Node node1, Node node2) { step(node1, any(Step s | s.jump()), node2) } + + predicate readStep(Node node1, ContentSet c, Node node2) { + step(node1, any(Step s | s.read(c)), node2) + } + + predicate storeStep(Node node1, ContentSet c, Node node2) { + step(node1, any(Step s | s.store(c)), node2) + } + + predicate clearsContent(Node n, ContentSet c) { none() } // TODO + + predicate expectsContent(Node n, ContentSet c) { none() } // TODO + + predicate localMustFlowStep(Node node1, Node node2) { localSsaMustFlowStep(node1, node2) } // TODO + + // + // Misc + // + additional predicate nodeIsVisible(Node node) { + node instanceof TValueNode + or + node instanceof TStrictlyIncomingValue + or + node instanceof TExprPostUpdateNode + } + + predicate nodeIsHidden(Node node) { not nodeIsVisible(node) } + + predicate neverSkipInPathGraph(Node n) { + n.isIncomingValue(_) or // Never skip assignment target + n.asExpr() instanceof LocalVariableAccess // Never skip a variable reference + } + + class DataFlowExpr = Expr; + + Node exprNode(DataFlowExpr e) { none() } // TODO + + predicate forceHighPrecision(Content c) { none() } // TODO + + class NodeRegion extends Void { + NodeRegion() { none() } // TODO + + predicate contains(Node n) { none() } // TODO + + string toString() { none() } // TODO + } + + predicate isUnreachableInCall(NodeRegion nr, DataFlowCall call) { none() } // TODO + + predicate allowParameterReturnInSelf(ParameterNode p) { none() } // TODO + + class LambdaCallKind extends Void { + LambdaCallKind() { none() } // TODO + + string toString() { none() } // TODO + } + + predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) { none() } // TODO + + predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { none() } // TODO + + predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { + none() // TODO + } + + predicate knownSourceModel(Node source, string model) { none() } // TODO + + predicate knownSinkModel(Node sink, string model) { none() } // TODO + + class DataFlowSecondLevelScope extends Void { + DataFlowSecondLevelScope() { none() } // TODO + + string toString() { none() } // TODO + } +} + +module DataFlowOutput = DataFlowMake; diff --git a/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowNode.qll b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowNode.qll new file mode 100644 index 000000000000..bc4bec52b948 --- /dev/null +++ b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowNode.qll @@ -0,0 +1,155 @@ +private import unified +private import AllDataFlow +private import codeql.unified.internal.ExprPositions + +private predicate hasPostUpdate(Expr expr) { + exists(MemberAccessExpr member | + (hasIncomingValue(member, _) or hasPostUpdate(member)) and + expr = member.getBase() + ) +} + +/** + * Holds if `expr` performs an access to `var` of the given `kind` at `cfgNode`. + */ +predicate performsVariableAccess( + Expr expr, LocalVariable var, VariableRefKind kind, ControlFlowNode cfgNode +) { + exists(LocalVariableAccess access | var = access.getLocalVariable() and expr = access | + hasResultValue(access) and kind.isRead() and cfgNode.isAfter(expr) + or + hasIncomingValue(access, _) and kind.isWrite() and cfgNode.asExpr() = expr // TODO: use more precise CFG node + or + hasPostUpdate(access) and kind.isPostUpdate() and cfgNode.asExpr() = expr // TODO: use more precise CFG node + ) + or + exists(UnqualifiedMemberAccess access | + access.isInstanceAccess() and var = access.getImplicitQualifierVariable() and expr = access + | + kind.isRead() and cfgNode.isBefore(access) + or + (hasIncomingValue(access, _) or hasPostUpdate(access)) and + kind.isPostUpdate() and + cfgNode.asExpr() = access // TODO: use more precise CFG node + ) +} + +newtype TDataFlowNode = + TValueNode(Expr expr) { hasResultValue(expr) or hasIncomingValue(expr, _) } or + TStrictlyIncomingValue(Expr expr) { hasResultValue(expr) and hasIncomingValue(expr, _) } or + TExprPostUpdateNode(Expr expr) { hasPostUpdate(expr) } or + TLocalVariableRefNode(Expr expr, LocalVariable var, VariableRefKind kind) { + performsVariableAccess(expr, var, kind, _) + } or + TLocalSsaNode(LocalSsaDataFlowOutput::SsaNode node) + +/** + * A node representing something that can have a value. + */ +class Node extends TDataFlowNode { + /** Holds if this is the result of evaluating `expr`. */ + pragma[nomagic] + predicate isResultValue(Expr expr) { hasResultValue(expr) and this = TValueNode(expr) } + + /** Holds if this represents the value about to be assigned to `expr` or pattern-matched against `expr`. */ + pragma[nomagic] + predicate isIncomingValue(Expr expr) { + // Use the TValueNode when it is not needed for representing the result value + hasIncomingValue(expr, _) and + not hasResultValue(expr) and + this = TValueNode(expr) + or + this = TStrictlyIncomingValue(expr) + } + + /** Holds if this represents the reference to `v` at `access`. */ + predicate isLocalVariableRef(Expr access, LocalVariable v, VariableRefKind kind) { + this = TLocalVariableRefNode(access, v, kind) + } + + /** Holds if this represents the value read from `v` at `access`. */ + predicate isLocalVariableRead(Expr access, LocalVariable v) { + this.isLocalVariableRef(access, v, TRead()) + } + + /** Holds if this represents the value written to `v` at `access`. */ + predicate isLocalVariableWrite(Expr access, LocalVariable v) { + this.isLocalVariableRef(access, v, TWrite()) + } + + /** Holds if this represents the updated state of the value held in `v` after it has been mutated by the surrounding assignment or call. */ + predicate isLocalVariablePostUpdate(Expr access, LocalVariable v) { + this.isLocalVariableRef(access, v, TPostUpdate()) + } + + /** Holds if this represents the updated state of the value returned by `expr` after it has been mutated by the surrounding assignment or call. */ + predicate isPostUpdate(Expr expr) { this = TExprPostUpdateNode(expr) } + + /** Gets the expression represented by this node. */ + Expr asExpr() { this = TValueNode(result) } + + /** + * Gets the AST node wrapped by this data flow, if any. + */ + AstNode getWrappedAstNode() { + result = this.asExpr() or + this = TStrictlyIncomingValue(result) or + this = TExprPostUpdateNode(result) or + this = TLocalVariableRefNode(result, _, _) + } + + /** Get a string representation of this element. */ + string toString() { + result = this.asExpr().toString() + or + exists(Expr expr | + this = TStrictlyIncomingValue(expr) and + result = "[incoming] " + expr.toString() + or + this = TExprPostUpdateNode(expr) and + result = "[post] " + expr.toString() + ) + or + exists(LocalVariable v, VariableRefKind kind | + this = TLocalVariableRefNode(_, v, kind) and + result = "[variable " + kind + "] " + v.toString() + ) + or + exists(LocalSsaDataFlowOutput::SsaNode node | + this = TLocalSsaNode(node) and + result = node.toString() + ) + } + + /** Gets the location of this data flow node. */ + Location getLocation() { + result = this.getWrappedAstNode().getLocation() + or + exists(LocalSsaDataFlowOutput::SsaNode node | + this = TLocalSsaNode(node) and + result = node.getLocation() + ) + } + + /** Gets the callable containing this data flow node. */ + Callable getEnclosingCallable() { + result = this.getWrappedAstNode().getEnclosingCallable() + or + exists(LocalSsaDataFlowOutput::SsaNode node | + this = TLocalSsaNode(node) and + result = node.getSourceVariable().getDeclaringCallable() + ) + } +} + +Node getPostUpdateNode(Node pre) { + exists(Expr expr | + pre.isResultValue(expr) and + result.isPostUpdate(expr) + ) + or + exists(Expr expr, LocalVariable var | + pre.isLocalVariableRead(expr, var) and + result.isLocalVariablePostUpdate(expr, var) + ) +} diff --git a/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowPlugin.qll b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowPlugin.qll new file mode 100644 index 000000000000..7bf02b5a7524 --- /dev/null +++ b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowPlugin.qll @@ -0,0 +1,13 @@ +/** + * Provides an interface for language-specific data flow rules. + */ + +private import unified +private import AllDataFlow +private import codeql.util.Unit +private import DataFlowPluginSwift // ensure overrides are seen + +class DataFlowPlugin extends Unit { + /** Holds if there is a language-specific step from `node1 -> step -> node2`. */ + predicate step(Node node1, Step step, Node node2) { none() } +} diff --git a/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowPluginSwift.qll b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowPluginSwift.qll new file mode 100644 index 000000000000..6cf9b5d487b8 --- /dev/null +++ b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowPluginSwift.qll @@ -0,0 +1,29 @@ +/** + * Provides Swift-specific data flow rules. + */ + +private import unified +private import AllDataFlow + +private class SwiftDataFlowPlugin extends DataFlowPlugin { + // Note: For now we assume all code is Swift, but in the future we must restrict these rules to Swift-files + override predicate step(Node node1, Step step, Node node2) { + exists(BinaryExpr expr | + expr.getOperator().getValue() = "+" and + node1.isResultValue([expr.getLeft(), expr.getRight()]) and + step.taint() and + node2.isResultValue(expr) + ) + or + exists(CallExpr call | + // String interpolations in Swift currently insert a call to a built-in called "interpolation". + // Add taint through plain 1-argument calls to this built-in. + call.getCallee().(BuiltinExpr).getValue() = "interpolation" and + call.getNumberOfArguments() = 1 and + not exists(call.getArgument(0).getName()) and + node1.isResultValue(call.getArgument(0).getValue()) and + step.value() and + node2.isResultValue(call) + ) + } +} diff --git a/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowPublic.qll b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowPublic.qll new file mode 100644 index 000000000000..b25ca7202505 --- /dev/null +++ b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowPublic.qll @@ -0,0 +1,13 @@ +private import unified +private import AllDataFlow +private import AllDataFlow as D + +module DataFlow { + class Node = D::Node; + + import DataFlowOutput +} + +module TaintTracking { + import TaintTrackingOutput +} diff --git a/unified/ql/lib/codeql/unified/internal/dataflow/LocalSsa.qll b/unified/ql/lib/codeql/unified/internal/dataflow/LocalSsa.qll new file mode 100644 index 000000000000..6b4fe6c376c1 --- /dev/null +++ b/unified/ql/lib/codeql/unified/internal/dataflow/LocalSsa.qll @@ -0,0 +1,95 @@ +/** + * SSA for non-captured variables. + */ + +private import unified +private import unified as U +private import AllDataFlow +private import codeql.ssa.Ssa +private import codeql.util.Void + +module LocalSsaInput implements InputSig { + class SourceVariable extends LocalVariable { + SourceVariable() { not this.isCaptured() } + } + + predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) { + certain = true and + ( + performsVariableAccess(_, v, TWrite(), bb.getNode(i)) + or + // Add implicit initialization of all variables at index -1 before the entry block + bb.(EntryBasicBlock).getEnclosingCallable() = v.getDeclaringCallable() and + i = -1 + ) + } + + predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain) { + certain = true and + performsVariableAccess(_, v, TRead(), bb.getNode(i)) + } +} + +module LocalSsaOutput = Make; + +private import LocalSsaOutput + +module LocalSsaDataFlowInput implements DataFlowIntegrationInputSig { + class Expr extends TLocalVariableRefNode { + predicate hasCfgNode(BasicBlock bb, int i) { + exists(U::Expr expr, LocalVariable var, VariableRefKind kind | + this = TLocalVariableRefNode(expr, var, kind) and + kind.isRead() and + performsVariableAccess(expr, var, kind, bb.getNode(i)) + ) + } + + string toString() { result = this.(Node).toString() } + } + + class GuardValue = Void; + + class Guard extends Void { + string toString() { none() } + + predicate hasValueBranchEdge(BasicBlock bb1, BasicBlock bb2, GuardValue val) { none() } + + predicate valueControlsBranchEdge(BasicBlock bb1, BasicBlock bb2, GuardValue val) { none() } + } + + predicate guardDirectlyControlsBlock(Guard guard, BasicBlock bb, GuardValue val) { none() } +} + +module LocalSsaDataFlowOutput = DataFlowIntegration; + +private module Ssa = LocalSsaDataFlowOutput; + +Node getNodeFromLocalSsaNode(Ssa::Node n) { + result = TLocalSsaNode(n) + or + result = n.(Ssa::ExprNode).getExpr() + or + result = getPostUpdateNode(n.(Ssa::ExprPostUpdateNode).getExpr()) + or + exists(LocalVariable v, BasicBlock bb, int i, Expr expr | + n.(Ssa::WriteDefSourceNode).getDefinition().definesAt(v, bb, i) and + performsVariableAccess(expr, v, TWrite(), bb.getNode(i)) and + result.isLocalVariableWrite(expr, v) + ) +} + +predicate localSsaStep(Node node1, Node node2, boolean isUseStep) { + exists(Ssa::Node ssa1, Ssa::Node ssa2 | + Ssa::localFlowStep(_, ssa1, ssa2, isUseStep) and + node1 = getNodeFromLocalSsaNode(ssa1) and + node2 = getNodeFromLocalSsaNode(ssa2) + ) +} + +predicate localSsaMustFlowStep(Node node1, Node node2) { + exists(Ssa::Node ssa1, Ssa::Node ssa2 | + Ssa::localMustFlowStep(_, ssa1, ssa2) and + node1 = getNodeFromLocalSsaNode(ssa1) and + node2 = getNodeFromLocalSsaNode(ssa2) + ) +} diff --git a/unified/ql/lib/codeql/unified/internal/dataflow/Step.qll b/unified/ql/lib/codeql/unified/internal/dataflow/Step.qll new file mode 100644 index 000000000000..65d2667a464e --- /dev/null +++ b/unified/ql/lib/codeql/unified/internal/dataflow/Step.qll @@ -0,0 +1,51 @@ +private import unified +private import AllDataFlow + +private newtype TStep = + TValueStep() or + TJumpStep() or + TTaintStep() or + TReadStep(ContentSet contents) or + TStoreStep(ContentSet contents) + +/** + * A type of data flow step, used during construction of the data flow graph. + */ +class Step extends TStep { + /** Holds if this represents a value-preserving step. */ + predicate value() { this = TValueStep() } + + /** Holds if this represents a value-preserving jump step (propagating across unrelated call stacks). */ + predicate jump() { this = TJumpStep() } + + /** Holds if this represents a taint-preserving step. */ + predicate taint() { this = TTaintStep() } + + /** Holds if this represents a step reading `contents`. */ + predicate read(ContentSet contents) { this = TReadStep(contents) } + + /** Holds if this represents a step reading the named member `name`. */ + pragma[nomagic] + predicate readName(string name) { this.read(ContentSet::namedMember(name)) } + + /** Holds if this represents a step storing into `contents`. */ + predicate store(ContentSet contents) { this = TStoreStep(contents) } + + /** Holds if this represents a step storing into the named member `name`. */ + pragma[nomagic] + predicate storeName(string name) { this.store(ContentSet::namedMember(name)) } + + string toString() { + this.value() and result = "value" + or + this.jump() and result = "jump" + or + this.taint() and result = "taint" + or + exists(ContentSet contents | + this.read(contents) and result = "read[" + contents + "]" + or + this.store(contents) and result = "store[" + contents + "]" + ) + } +} diff --git a/unified/ql/lib/codeql/unified/internal/dataflow/TaintTrackingInstantiation.qll b/unified/ql/lib/codeql/unified/internal/dataflow/TaintTrackingInstantiation.qll new file mode 100644 index 000000000000..ad911bc72224 --- /dev/null +++ b/unified/ql/lib/codeql/unified/internal/dataflow/TaintTrackingInstantiation.qll @@ -0,0 +1,18 @@ +private import unified +private import AllDataFlow +private import codeql.dataflow.TaintTracking + +module TaintTrackingInput implements InputSig { + predicate defaultTaintSanitizer(Node node) { none() } // TODO + + predicate defaultAdditionalTaintStep(Node src, Node sink, string model) { + step(src, any(Step s | s.taint()), sink) and model = "" + } + + bindingset[node] + predicate defaultImplicitTaintRead(Node node, ContentSet c) { none() } // TODO + + predicate speculativeTaintStep(Node src, Node sink) { none() } // TODO +} + +module TaintTrackingOutput = TaintFlowMake; diff --git a/unified/ql/lib/codeql/unified/internal/dataflow/VariableRefKind.qll b/unified/ql/lib/codeql/unified/internal/dataflow/VariableRefKind.qll new file mode 100644 index 000000000000..1ae34f665704 --- /dev/null +++ b/unified/ql/lib/codeql/unified/internal/dataflow/VariableRefKind.qll @@ -0,0 +1,22 @@ +private import unified + +newtype TVariableRefKind = + TRead() or + TWrite() or + TPostUpdate() + +class VariableRefKind extends TVariableRefKind { + predicate isRead() { this = TRead() } + + predicate isWrite() { this = TWrite() } + + predicate isPostUpdate() { this = TPostUpdate() } + + string toString() { + this.isRead() and result = "read" + or + this.isWrite() and result = "write" + or + this.isPostUpdate() and result = "post-update" + } +} diff --git a/unified/ql/lib/codeql/unified/internal/dev/debugDataFlowGraph.ql b/unified/ql/lib/codeql/unified/internal/dev/debugDataFlowGraph.ql new file mode 100644 index 000000000000..7f79b0d151e7 --- /dev/null +++ b/unified/ql/lib/codeql/unified/internal/dev/debugDataFlowGraph.ql @@ -0,0 +1,19 @@ +/** + * @name Debug data flow graph + * @description Renders the data flow graph + * @kind graph + * @id unified/debug-data-flow-graph + */ + +private import unified +private import codeql.unified.internal.dataflow.DataFlowGraph + +/** + * Holds if `node` should be shown in the graph. + */ +predicate relevantNode(AstNode node) { + // Match an ancestor node by location so its whole subtree is shown. + node.getParent*().getLocation().toString().matches("%test.swift@13:%") +} + +import DebugGraph diff --git a/unified/ql/lib/qlpack.yml b/unified/ql/lib/qlpack.yml index 0167b114ba96..84261b14adc7 100644 --- a/unified/ql/lib/qlpack.yml +++ b/unified/ql/lib/qlpack.yml @@ -6,7 +6,10 @@ extractor: unified library: true upgrades: upgrades dependencies: + codeql/concepts: ${workspace} codeql/controlflow: ${workspace} + codeql/ssa: ${workspace} + codeql/dataflow: ${workspace} codeql/namebinding: ${workspace} codeql/util: ${workspace} warnOnImplicitThis: true diff --git a/unified/ql/lib/unified.qll b/unified/ql/lib/unified.qll index 500de80b64f3..9729b6445812 100644 --- a/unified/ql/lib/unified.qll +++ b/unified/ql/lib/unified.qll @@ -8,3 +8,4 @@ import codeql.unified.internal.Ast::UnifiedFinal import codeql.unified.internal.AstExtra::Public import codeql.unified.internal.ControlFlowGraph import codeql.unified.internal.NameBinding::Public +import codeql.unified.internal.dataflow.DataFlowPublic diff --git a/unified/ql/lib/utils/test/InlineFlowTest.qll b/unified/ql/lib/utils/test/InlineFlowTest.qll new file mode 100644 index 000000000000..967a2446c32c --- /dev/null +++ b/unified/ql/lib/utils/test/InlineFlowTest.qll @@ -0,0 +1,38 @@ +/** + * Inline flow tests for the unified language. + * See `shared/util/codeql/dataflow/test/InlineFlowTest.qll` + */ + +private import unified +private import codeql.dataflow.test.InlineFlowTest +private import codeql.unified.internal.dataflow.AllDataFlow +private import internal.InlineExpectationsTestImpl as InlineExpectationsTestImpl + +private string getCalleeName(CallExpr call) { result = call.getCallee().(Identifier).getValue() } + +private module FlowTestImpl implements InputSig { + predicate defaultSource(DataFlow::Node source) { getCalleeName(source.asExpr()) = "source" } + + predicate defaultSink(DataFlow::Node sink) { + any(CallExpr call | getCalleeName(call) = "sink").getAnArgument().getValue() = sink.asExpr() + } + + private string getSourceArgString(DataFlow::Node src) { + defaultSource(src) and + result = src.asExpr().(CallExpr).getArgument(0).getValue().getStringValue() + } + + bindingset[src, sink] + string getArgString(DataFlow::Node src, DataFlow::Node sink) { + ( + result = getSourceArgString(src) + or + not exists(getSourceArgString(src)) and result = "" + ) and + exists(sink) + } + + predicate interpretModelForTest(QlBuiltins::ExtensionId madId, string model) { none() } +} + +import InlineFlowTestMake diff --git a/unified/ql/src/queries/security/CWE-312/CleartextLogging.qhelp b/unified/ql/src/queries/security/CWE-312/CleartextLogging.qhelp new file mode 100644 index 000000000000..8de3f21878f0 --- /dev/null +++ b/unified/ql/src/queries/security/CWE-312/CleartextLogging.qhelp @@ -0,0 +1,46 @@ + + + + +

+Attackers could gain access to sensitive information that is logged unencrypted. +

+
+ + +

+Always make sure to encrypt or obfuscate sensitive information before you log it. +

+ +

+Generally, you should decrypt sensitive information only at the point where it is necessary for it to be used in cleartext. +

+ +

+Be aware that external processes often store the standard output and +standard error streams of the application. This will include logged sensitive information. +

+
+ + +

+The following example code logs user credentials (in this case, their password) +in plaintext: +

+ +

+Instead, you should encrypt or obfuscate the credentials, or omit them entirely: +

+ +
+ + + +
  • M. Dowd, J. McDonald and J. Schuhm, The Art of Software Security Assessment, 1st Edition, Chapter 2 - 'Common Vulnerabilities of Encryption', p. 43. Addison Wesley, 2006.
  • +
  • M. Howard and D. LeBlanc, Writing Secure Code, 2nd Edition, Chapter 9 - 'Protecting Secret Data', p. 299. Microsoft, 2002.
  • +
  • OWASP: Logging Cheat Sheet.
  • + +
    +
    diff --git a/unified/ql/src/queries/security/CWE-312/CleartextLogging.ql b/unified/ql/src/queries/security/CWE-312/CleartextLogging.ql new file mode 100644 index 000000000000..4aa751a1e91a --- /dev/null +++ b/unified/ql/src/queries/security/CWE-312/CleartextLogging.ql @@ -0,0 +1,52 @@ +/** + * @name Cleartext logging of sensitive information + * @description Logging sensitive information in plaintext can + * expose it to an attacker. + * @kind path-problem + * @problem.severity error + * @security-severity 7.5 + * @precision high + * @id unified/swift/cleartext-logging + * @tags security + * external/cwe/cwe-312 + * external/cwe/cwe-359 + * external/cwe/cwe-532 + */ + +// +// FIXME: This is a deliberately dumb and noisy version of the query used to exercise data flow early on. +// +import unified +import codeql.concepts.internal.SensitiveDataHeuristics + +private string getNameFromExpr(Expr e) { + result = e.(IdentifierExpr).getValue() + or + result = e.(MemberAccessExpr).getMemberName() +} + +module DummyConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node node) { + exists(string name | + name = getNameFromExpr(node.asExpr()) and + HeuristicNames::nameIndicatesSensitiveData(name) + ) + } + + predicate isSink(DataFlow::Node node) { + exists(CallExpr call | + getNameFromExpr(call.getCallee()).regexpMatch("(?i)(ns)?(log|warn(ing)?|error|print).*") and + node.asExpr() = call.getAnArgument().getValue() + ) + } + + predicate isBarrierIn(DataFlow::Node node) { isSource(node) } +} + +module DummyFlow = TaintTracking::Global; + +import DummyFlow::PathGraph + +from DummyFlow::PathNode source, DummyFlow::PathNode sink +where DummyFlow::flowPath(source, sink) +select sink.getNode(), source, sink, "Logging of $@", source.getNode(), "sensitive data" diff --git a/unified/ql/src/queries/security/CWE-312/CleartextLoggingBad.swift b/unified/ql/src/queries/security/CWE-312/CleartextLoggingBad.swift new file mode 100644 index 000000000000..036001e87179 --- /dev/null +++ b/unified/ql/src/queries/security/CWE-312/CleartextLoggingBad.swift @@ -0,0 +1,2 @@ +let password = "P@ssw0rd" +NSLog("User password changed to \(password)") diff --git a/unified/ql/src/queries/security/CWE-312/CleartextLoggingGood.swift b/unified/ql/src/queries/security/CWE-312/CleartextLoggingGood.swift new file mode 100644 index 000000000000..1d90ac5e5656 --- /dev/null +++ b/unified/ql/src/queries/security/CWE-312/CleartextLoggingGood.swift @@ -0,0 +1,2 @@ +let password = "P@ssw0rd" +NSLog("User password changed") diff --git a/unified/ql/test/library-tests/dataflow/implicit-self.swift b/unified/ql/test/library-tests/dataflow/implicit-self.swift new file mode 100644 index 000000000000..8e13479d51fe --- /dev/null +++ b/unified/ql/test/library-tests/dataflow/implicit-self.swift @@ -0,0 +1,56 @@ +class Box { + var x: String = "" +} + +class C { + var x: String = "" + var box = Box() + + func t1() { + sink(self.x); // no flow + self.x = source("t1.1"); + sink(self.x); // $ hasValueFlow=t1.1 + } + + func t2() { + sink(x); // no flow + x = source("t2.1"); + sink(x); // $ hasValueFlow=t2.1 + } + + func t3() { + sink(self.x); // no flow + x = source("t3.1"); + sink(self.x); // $ hasValueFlow=t3.1 + } + + func t4() { + sink(x); // no flow + self.x = source("t4.1"); + sink(x); // $ hasValueFlow=t4.1 + } + + func t5() { + sink(self.box.x); // no flow + self.box.x = source("t5.1"); + sink(self.box.x); // $ hasValueFlow=t5.1 + } + + func t6() { + sink(box.x); // no flow + box.x = source("t6.1"); + sink(box.x); // $ hasValueFlow=t6.1 + } + + func t7() { + sink(self.box.x); // no flow + box.x = source("t7.1"); + sink(self.box.x); // $ hasValueFlow=t7.1 + } + + func t8() { + sink(box.x); // no flow + self.box.x = source("t8.1"); + sink(box.x); // $ hasValueFlow=t8.1 + } +} diff --git a/unified/ql/test/library-tests/dataflow/test.expected b/unified/ql/test/library-tests/dataflow/test.expected new file mode 100644 index 000000000000..f6df03922f1f --- /dev/null +++ b/unified/ql/test/library-tests/dataflow/test.expected @@ -0,0 +1,265 @@ +models +edges +| implicit-self.swift:11:9:11:12 | [post] self [x] | implicit-self.swift:12:14:12:17 | self [x] | provenance | | +| implicit-self.swift:11:9:11:14 | MemberAccessExpr | implicit-self.swift:11:9:11:12 | [post] self [x] | provenance | | +| implicit-self.swift:11:18:11:31 | CallExpr | implicit-self.swift:11:9:11:14 | MemberAccessExpr | provenance | | +| implicit-self.swift:12:14:12:17 | self [x] | implicit-self.swift:12:14:12:19 | MemberAccessExpr | provenance | | +| implicit-self.swift:17:9:17:9 | x | implicit-self.swift:18:14:18:14 | x | provenance | | +| implicit-self.swift:17:13:17:26 | CallExpr | implicit-self.swift:17:9:17:9 | x | provenance | | +| implicit-self.swift:23:9:23:9 | x | implicit-self.swift:24:14:24:17 | self [x] | provenance | | +| implicit-self.swift:23:13:23:26 | CallExpr | implicit-self.swift:23:9:23:9 | x | provenance | | +| implicit-self.swift:24:14:24:17 | self [x] | implicit-self.swift:24:14:24:19 | MemberAccessExpr | provenance | | +| implicit-self.swift:29:9:29:12 | [post] self [x] | implicit-self.swift:30:14:30:14 | x | provenance | | +| implicit-self.swift:29:9:29:14 | MemberAccessExpr | implicit-self.swift:29:9:29:12 | [post] self [x] | provenance | | +| implicit-self.swift:29:18:29:31 | CallExpr | implicit-self.swift:29:9:29:14 | MemberAccessExpr | provenance | | +| implicit-self.swift:35:9:35:12 | [post] self [box, x] | implicit-self.swift:36:14:36:17 | self [box, x] | provenance | | +| implicit-self.swift:35:9:35:16 | [post] MemberAccessExpr [x] | implicit-self.swift:35:9:35:12 | [post] self [box, x] | provenance | | +| implicit-self.swift:35:9:35:18 | MemberAccessExpr | implicit-self.swift:35:9:35:16 | [post] MemberAccessExpr [x] | provenance | | +| implicit-self.swift:35:22:35:35 | CallExpr | implicit-self.swift:35:9:35:18 | MemberAccessExpr | provenance | | +| implicit-self.swift:36:14:36:17 | self [box, x] | implicit-self.swift:36:14:36:21 | MemberAccessExpr [x] | provenance | | +| implicit-self.swift:36:14:36:21 | MemberAccessExpr [x] | implicit-self.swift:36:14:36:23 | MemberAccessExpr | provenance | | +| implicit-self.swift:41:9:41:11 | [post] box [x] | implicit-self.swift:42:14:42:16 | box [x] | provenance | | +| implicit-self.swift:41:9:41:13 | MemberAccessExpr | implicit-self.swift:41:9:41:11 | [post] box [x] | provenance | | +| implicit-self.swift:41:17:41:30 | CallExpr | implicit-self.swift:41:9:41:13 | MemberAccessExpr | provenance | | +| implicit-self.swift:42:14:42:16 | box [x] | implicit-self.swift:42:14:42:18 | MemberAccessExpr | provenance | | +| implicit-self.swift:47:9:47:11 | [post] box [x] | implicit-self.swift:48:14:48:17 | self [box, x] | provenance | | +| implicit-self.swift:47:9:47:13 | MemberAccessExpr | implicit-self.swift:47:9:47:11 | [post] box [x] | provenance | | +| implicit-self.swift:47:17:47:30 | CallExpr | implicit-self.swift:47:9:47:13 | MemberAccessExpr | provenance | | +| implicit-self.swift:48:14:48:17 | self [box, x] | implicit-self.swift:48:14:48:21 | MemberAccessExpr [x] | provenance | | +| implicit-self.swift:48:14:48:21 | MemberAccessExpr [x] | implicit-self.swift:48:14:48:23 | MemberAccessExpr | provenance | | +| implicit-self.swift:53:9:53:12 | [post] self [box, x] | implicit-self.swift:54:14:54:16 | box [x] | provenance | | +| implicit-self.swift:53:9:53:16 | [post] MemberAccessExpr [x] | implicit-self.swift:53:9:53:12 | [post] self [box, x] | provenance | | +| implicit-self.swift:53:9:53:18 | MemberAccessExpr | implicit-self.swift:53:9:53:16 | [post] MemberAccessExpr [x] | provenance | | +| implicit-self.swift:53:22:53:35 | CallExpr | implicit-self.swift:53:9:53:18 | MemberAccessExpr | provenance | | +| implicit-self.swift:54:14:54:16 | box [x] | implicit-self.swift:54:14:54:18 | MemberAccessExpr | provenance | | +| test.swift:6:10:6:23 | CallExpr | test.swift:6:10:6:32 | BinaryExpr | provenance | | +| test.swift:7:19:7:32 | CallExpr | test.swift:7:10:7:32 | BinaryExpr | provenance | | +| test.swift:9:13:9:26 | CallExpr | test.swift:9:10:9:33 | StringInterpolationExpr | provenance | | +| test.swift:10:18:10:31 | CallExpr | test.swift:10:10:10:33 | StringInterpolationExpr | provenance | | +| test.swift:11:18:11:31 | CallExpr | test.swift:11:10:11:38 | StringInterpolationExpr | provenance | | +| test.swift:16:10:16:33 | TupleExpr [0] | test.swift:16:10:16:35 | MemberAccessExpr | provenance | | +| test.swift:16:11:16:25 | CallExpr | test.swift:16:10:16:33 | TupleExpr [0] | provenance | | +| test.swift:19:10:19:33 | TupleExpr [1] | test.swift:19:10:19:35 | MemberAccessExpr | provenance | | +| test.swift:19:19:19:32 | CallExpr | test.swift:19:10:19:33 | TupleExpr [1] | provenance | | +| test.swift:23:9:23:9 | a | test.swift:24:10:24:10 | a | provenance | | +| test.swift:23:13:23:26 | CallExpr | test.swift:23:9:23:9 | a | provenance | | +| test.swift:28:9:28:14 | TupleExpr [0] | test.swift:28:10:28:10 | a | provenance | | +| test.swift:28:10:28:10 | a | test.swift:29:10:29:10 | a | provenance | | +| test.swift:28:18:28:41 | TupleExpr [0] | test.swift:28:9:28:14 | TupleExpr [0] | provenance | | +| test.swift:28:19:28:33 | CallExpr | test.swift:28:18:28:41 | TupleExpr [0] | provenance | | +| test.swift:32:9:32:14 | TupleExpr [1] | test.swift:32:13:32:13 | d | provenance | | +| test.swift:32:13:32:13 | d | test.swift:34:10:34:10 | d | provenance | | +| test.swift:32:18:32:41 | TupleExpr [1] | test.swift:32:9:32:14 | TupleExpr [1] | provenance | | +| test.swift:32:27:32:40 | CallExpr | test.swift:32:18:32:41 | TupleExpr [1] | provenance | | +| test.swift:38:9:38:9 | a | test.swift:39:10:39:10 | a | provenance | | +| test.swift:38:13:38:26 | CallExpr | test.swift:38:9:38:9 | a | provenance | | +| test.swift:46:5:46:9 | [post] tuple [0] | test.swift:47:10:47:14 | tuple [0] | provenance | | +| test.swift:46:5:46:11 | MemberAccessExpr | test.swift:46:5:46:9 | [post] tuple [0] | provenance | | +| test.swift:46:15:46:28 | CallExpr | test.swift:46:5:46:11 | MemberAccessExpr | provenance | | +| test.swift:47:10:47:14 | tuple [0] | test.swift:47:10:47:16 | MemberAccessExpr | provenance | | +| test.swift:53:5:53:14 | [post] deep_tuple [1, 0] | test.swift:58:10:58:19 | deep_tuple [1, 0] | provenance | | +| test.swift:53:5:53:16 | [post] MemberAccessExpr [0] | test.swift:53:5:53:14 | [post] deep_tuple [1, 0] | provenance | | +| test.swift:53:5:53:18 | MemberAccessExpr | test.swift:53:5:53:16 | [post] MemberAccessExpr [0] | provenance | | +| test.swift:53:22:53:35 | CallExpr | test.swift:53:5:53:18 | MemberAccessExpr | provenance | | +| test.swift:58:10:58:19 | deep_tuple [1, 0] | test.swift:58:10:58:21 | MemberAccessExpr [0] | provenance | | +| test.swift:58:10:58:21 | MemberAccessExpr [0] | test.swift:58:10:58:23 | MemberAccessExpr | provenance | | +| test.swift:64:5:64:16 | TupleExpr [0] | test.swift:64:6:64:12 | MemberAccessExpr | provenance | | +| test.swift:64:6:64:10 | [post] tuple [1] | test.swift:66:10:66:14 | tuple [1] | provenance | | +| test.swift:64:6:64:12 | MemberAccessExpr | test.swift:64:6:64:10 | [post] tuple [1] | provenance | | +| test.swift:64:20:64:51 | TupleExpr [0] | test.swift:64:5:64:16 | TupleExpr [0] | provenance | | +| test.swift:64:21:64:35 | CallExpr | test.swift:64:20:64:51 | TupleExpr [0] | provenance | | +| test.swift:66:10:66:14 | tuple [1] | test.swift:66:10:66:16 | MemberAccessExpr | provenance | | +| test.swift:74:5:74:9 | [post] tuple [0] | test.swift:75:10:75:14 | tuple [0] | provenance | | +| test.swift:74:5:74:11 | MemberAccessExpr | test.swift:74:5:74:9 | [post] tuple [0] | provenance | | +| test.swift:74:15:74:29 | CallExpr | test.swift:74:5:74:11 | MemberAccessExpr | provenance | | +| test.swift:75:10:75:14 | tuple [0] | test.swift:75:10:75:16 | MemberAccessExpr | provenance | | +| test.swift:86:9:86:9 | x | test.swift:90:10:90:10 | x | provenance | | +| test.swift:86:9:86:9 | x | test.swift:99:14:99:14 | x | provenance | | +| test.swift:86:13:86:27 | CallExpr | test.swift:86:9:86:9 | x | provenance | | +| test.swift:94:9:94:9 | y | test.swift:96:10:96:10 | y | provenance | | +| test.swift:94:9:94:9 | y | test.swift:100:14:100:14 | y | provenance | | +| test.swift:94:13:94:27 | CallExpr | test.swift:94:9:94:9 | y | provenance | | +| test.swift:107:9:107:13 | [post] tuple [0] | test.swift:112:10:112:14 | tuple [0] | provenance | | +| test.swift:107:9:107:15 | MemberAccessExpr | test.swift:107:9:107:13 | [post] tuple [0] | provenance | | +| test.swift:107:19:107:33 | CallExpr | test.swift:107:9:107:15 | MemberAccessExpr | provenance | | +| test.swift:112:10:112:14 | tuple [0] | test.swift:112:10:112:16 | MemberAccessExpr | provenance | | +| test.swift:117:9:117:13 | tuple [0] | test.swift:120:17:120:21 | tuple [0] | provenance | | +| test.swift:117:9:117:13 | tuple [1] | test.swift:120:17:120:21 | tuple [1] | provenance | | +| test.swift:117:17:117:50 | TupleExpr [0] | test.swift:117:9:117:13 | tuple [0] | provenance | | +| test.swift:117:17:117:50 | TupleExpr [1] | test.swift:117:9:117:13 | tuple [1] | provenance | | +| test.swift:117:18:117:33 | CallExpr | test.swift:117:17:117:50 | TupleExpr [0] | provenance | | +| test.swift:117:35:117:49 | CallExpr | test.swift:117:17:117:50 | TupleExpr [1] | provenance | | +| test.swift:120:9:120:13 | TupleExpr [0] | test.swift:120:10:120:10 | a | provenance | | +| test.swift:120:9:120:13 | TupleExpr [1] | test.swift:120:12:120:12 | b | provenance | | +| test.swift:120:10:120:10 | a | test.swift:125:10:125:10 | a | provenance | | +| test.swift:120:12:120:12 | b | test.swift:126:10:126:10 | b | provenance | | +| test.swift:120:17:120:21 | tuple [0] | test.swift:120:9:120:13 | TupleExpr [0] | provenance | | +| test.swift:120:17:120:21 | tuple [1] | test.swift:120:9:120:13 | TupleExpr [1] | provenance | | +| test.swift:131:5:131:5 | a | test.swift:131:14:131:14 | a | provenance | | +| test.swift:131:5:131:5 | a | test.swift:132:10:132:10 | a | provenance | | +| test.swift:131:19:131:33 | CallExpr | test.swift:131:5:131:5 | a | provenance | | +nodes +| implicit-self.swift:11:9:11:12 | [post] self [x] | semmle.label | [post] self [x] | +| implicit-self.swift:11:9:11:14 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| implicit-self.swift:11:18:11:31 | CallExpr | semmle.label | CallExpr | +| implicit-self.swift:12:14:12:17 | self [x] | semmle.label | self [x] | +| implicit-self.swift:12:14:12:19 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| implicit-self.swift:17:9:17:9 | x | semmle.label | x | +| implicit-self.swift:17:13:17:26 | CallExpr | semmle.label | CallExpr | +| implicit-self.swift:18:14:18:14 | x | semmle.label | x | +| implicit-self.swift:23:9:23:9 | x | semmle.label | x | +| implicit-self.swift:23:13:23:26 | CallExpr | semmle.label | CallExpr | +| implicit-self.swift:24:14:24:17 | self [x] | semmle.label | self [x] | +| implicit-self.swift:24:14:24:19 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| implicit-self.swift:29:9:29:12 | [post] self [x] | semmle.label | [post] self [x] | +| implicit-self.swift:29:9:29:14 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| implicit-self.swift:29:18:29:31 | CallExpr | semmle.label | CallExpr | +| implicit-self.swift:30:14:30:14 | x | semmle.label | x | +| implicit-self.swift:35:9:35:12 | [post] self [box, x] | semmle.label | [post] self [box, x] | +| implicit-self.swift:35:9:35:16 | [post] MemberAccessExpr [x] | semmle.label | [post] MemberAccessExpr [x] | +| implicit-self.swift:35:9:35:18 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| implicit-self.swift:35:22:35:35 | CallExpr | semmle.label | CallExpr | +| implicit-self.swift:36:14:36:17 | self [box, x] | semmle.label | self [box, x] | +| implicit-self.swift:36:14:36:21 | MemberAccessExpr [x] | semmle.label | MemberAccessExpr [x] | +| implicit-self.swift:36:14:36:23 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| implicit-self.swift:41:9:41:11 | [post] box [x] | semmle.label | [post] box [x] | +| implicit-self.swift:41:9:41:13 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| implicit-self.swift:41:17:41:30 | CallExpr | semmle.label | CallExpr | +| implicit-self.swift:42:14:42:16 | box [x] | semmle.label | box [x] | +| implicit-self.swift:42:14:42:18 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| implicit-self.swift:47:9:47:11 | [post] box [x] | semmle.label | [post] box [x] | +| implicit-self.swift:47:9:47:13 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| implicit-self.swift:47:17:47:30 | CallExpr | semmle.label | CallExpr | +| implicit-self.swift:48:14:48:17 | self [box, x] | semmle.label | self [box, x] | +| implicit-self.swift:48:14:48:21 | MemberAccessExpr [x] | semmle.label | MemberAccessExpr [x] | +| implicit-self.swift:48:14:48:23 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| implicit-self.swift:53:9:53:12 | [post] self [box, x] | semmle.label | [post] self [box, x] | +| implicit-self.swift:53:9:53:16 | [post] MemberAccessExpr [x] | semmle.label | [post] MemberAccessExpr [x] | +| implicit-self.swift:53:9:53:18 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| implicit-self.swift:53:22:53:35 | CallExpr | semmle.label | CallExpr | +| implicit-self.swift:54:14:54:16 | box [x] | semmle.label | box [x] | +| implicit-self.swift:54:14:54:18 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| test.swift:2:10:2:21 | CallExpr | semmle.label | CallExpr | +| test.swift:6:10:6:23 | CallExpr | semmle.label | CallExpr | +| test.swift:6:10:6:32 | BinaryExpr | semmle.label | BinaryExpr | +| test.swift:7:10:7:32 | BinaryExpr | semmle.label | BinaryExpr | +| test.swift:7:19:7:32 | CallExpr | semmle.label | CallExpr | +| test.swift:9:10:9:33 | StringInterpolationExpr | semmle.label | StringInterpolationExpr | +| test.swift:9:13:9:26 | CallExpr | semmle.label | CallExpr | +| test.swift:10:10:10:33 | StringInterpolationExpr | semmle.label | StringInterpolationExpr | +| test.swift:10:18:10:31 | CallExpr | semmle.label | CallExpr | +| test.swift:11:10:11:38 | StringInterpolationExpr | semmle.label | StringInterpolationExpr | +| test.swift:11:18:11:31 | CallExpr | semmle.label | CallExpr | +| test.swift:16:10:16:33 | TupleExpr [0] | semmle.label | TupleExpr [0] | +| test.swift:16:10:16:35 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| test.swift:16:11:16:25 | CallExpr | semmle.label | CallExpr | +| test.swift:19:10:19:33 | TupleExpr [1] | semmle.label | TupleExpr [1] | +| test.swift:19:10:19:35 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| test.swift:19:19:19:32 | CallExpr | semmle.label | CallExpr | +| test.swift:23:9:23:9 | a | semmle.label | a | +| test.swift:23:13:23:26 | CallExpr | semmle.label | CallExpr | +| test.swift:24:10:24:10 | a | semmle.label | a | +| test.swift:28:9:28:14 | TupleExpr [0] | semmle.label | TupleExpr [0] | +| test.swift:28:10:28:10 | a | semmle.label | a | +| test.swift:28:18:28:41 | TupleExpr [0] | semmle.label | TupleExpr [0] | +| test.swift:28:19:28:33 | CallExpr | semmle.label | CallExpr | +| test.swift:29:10:29:10 | a | semmle.label | a | +| test.swift:32:9:32:14 | TupleExpr [1] | semmle.label | TupleExpr [1] | +| test.swift:32:13:32:13 | d | semmle.label | d | +| test.swift:32:18:32:41 | TupleExpr [1] | semmle.label | TupleExpr [1] | +| test.swift:32:27:32:40 | CallExpr | semmle.label | CallExpr | +| test.swift:34:10:34:10 | d | semmle.label | d | +| test.swift:38:9:38:9 | a | semmle.label | a | +| test.swift:38:13:38:26 | CallExpr | semmle.label | CallExpr | +| test.swift:39:10:39:10 | a | semmle.label | a | +| test.swift:46:5:46:9 | [post] tuple [0] | semmle.label | [post] tuple [0] | +| test.swift:46:5:46:11 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| test.swift:46:15:46:28 | CallExpr | semmle.label | CallExpr | +| test.swift:47:10:47:14 | tuple [0] | semmle.label | tuple [0] | +| test.swift:47:10:47:16 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| test.swift:53:5:53:14 | [post] deep_tuple [1, 0] | semmle.label | [post] deep_tuple [1, 0] | +| test.swift:53:5:53:16 | [post] MemberAccessExpr [0] | semmle.label | [post] MemberAccessExpr [0] | +| test.swift:53:5:53:18 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| test.swift:53:22:53:35 | CallExpr | semmle.label | CallExpr | +| test.swift:58:10:58:19 | deep_tuple [1, 0] | semmle.label | deep_tuple [1, 0] | +| test.swift:58:10:58:21 | MemberAccessExpr [0] | semmle.label | MemberAccessExpr [0] | +| test.swift:58:10:58:23 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| test.swift:64:5:64:16 | TupleExpr [0] | semmle.label | TupleExpr [0] | +| test.swift:64:6:64:10 | [post] tuple [1] | semmle.label | [post] tuple [1] | +| test.swift:64:6:64:12 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| test.swift:64:20:64:51 | TupleExpr [0] | semmle.label | TupleExpr [0] | +| test.swift:64:21:64:35 | CallExpr | semmle.label | CallExpr | +| test.swift:66:10:66:14 | tuple [1] | semmle.label | tuple [1] | +| test.swift:66:10:66:16 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| test.swift:74:5:74:9 | [post] tuple [0] | semmle.label | [post] tuple [0] | +| test.swift:74:5:74:11 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| test.swift:74:15:74:29 | CallExpr | semmle.label | CallExpr | +| test.swift:75:10:75:14 | tuple [0] | semmle.label | tuple [0] | +| test.swift:75:10:75:16 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| test.swift:86:9:86:9 | x | semmle.label | x | +| test.swift:86:13:86:27 | CallExpr | semmle.label | CallExpr | +| test.swift:90:10:90:10 | x | semmle.label | x | +| test.swift:94:9:94:9 | y | semmle.label | y | +| test.swift:94:13:94:27 | CallExpr | semmle.label | CallExpr | +| test.swift:96:10:96:10 | y | semmle.label | y | +| test.swift:99:14:99:14 | x | semmle.label | x | +| test.swift:100:14:100:14 | y | semmle.label | y | +| test.swift:107:9:107:13 | [post] tuple [0] | semmle.label | [post] tuple [0] | +| test.swift:107:9:107:15 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| test.swift:107:19:107:33 | CallExpr | semmle.label | CallExpr | +| test.swift:112:10:112:14 | tuple [0] | semmle.label | tuple [0] | +| test.swift:112:10:112:16 | MemberAccessExpr | semmle.label | MemberAccessExpr | +| test.swift:117:9:117:13 | tuple [0] | semmle.label | tuple [0] | +| test.swift:117:9:117:13 | tuple [1] | semmle.label | tuple [1] | +| test.swift:117:17:117:50 | TupleExpr [0] | semmle.label | TupleExpr [0] | +| test.swift:117:17:117:50 | TupleExpr [1] | semmle.label | TupleExpr [1] | +| test.swift:117:18:117:33 | CallExpr | semmle.label | CallExpr | +| test.swift:117:35:117:49 | CallExpr | semmle.label | CallExpr | +| test.swift:120:9:120:13 | TupleExpr [0] | semmle.label | TupleExpr [0] | +| test.swift:120:9:120:13 | TupleExpr [1] | semmle.label | TupleExpr [1] | +| test.swift:120:10:120:10 | a | semmle.label | a | +| test.swift:120:12:120:12 | b | semmle.label | b | +| test.swift:120:17:120:21 | tuple [0] | semmle.label | tuple [0] | +| test.swift:120:17:120:21 | tuple [1] | semmle.label | tuple [1] | +| test.swift:125:10:125:10 | a | semmle.label | a | +| test.swift:126:10:126:10 | b | semmle.label | b | +| test.swift:131:5:131:5 | a | semmle.label | a | +| test.swift:131:14:131:14 | a | semmle.label | a | +| test.swift:131:19:131:33 | CallExpr | semmle.label | CallExpr | +| test.swift:132:10:132:10 | a | semmle.label | a | +subpaths +testFailures +#select +| implicit-self.swift:12:14:12:19 | MemberAccessExpr | implicit-self.swift:11:18:11:31 | CallExpr | implicit-self.swift:12:14:12:19 | MemberAccessExpr | $@ | implicit-self.swift:11:18:11:31 | CallExpr | CallExpr | +| implicit-self.swift:18:14:18:14 | x | implicit-self.swift:17:13:17:26 | CallExpr | implicit-self.swift:18:14:18:14 | x | $@ | implicit-self.swift:17:13:17:26 | CallExpr | CallExpr | +| implicit-self.swift:24:14:24:19 | MemberAccessExpr | implicit-self.swift:23:13:23:26 | CallExpr | implicit-self.swift:24:14:24:19 | MemberAccessExpr | $@ | implicit-self.swift:23:13:23:26 | CallExpr | CallExpr | +| implicit-self.swift:30:14:30:14 | x | implicit-self.swift:29:18:29:31 | CallExpr | implicit-self.swift:30:14:30:14 | x | $@ | implicit-self.swift:29:18:29:31 | CallExpr | CallExpr | +| implicit-self.swift:36:14:36:23 | MemberAccessExpr | implicit-self.swift:35:22:35:35 | CallExpr | implicit-self.swift:36:14:36:23 | MemberAccessExpr | $@ | implicit-self.swift:35:22:35:35 | CallExpr | CallExpr | +| implicit-self.swift:42:14:42:18 | MemberAccessExpr | implicit-self.swift:41:17:41:30 | CallExpr | implicit-self.swift:42:14:42:18 | MemberAccessExpr | $@ | implicit-self.swift:41:17:41:30 | CallExpr | CallExpr | +| implicit-self.swift:48:14:48:23 | MemberAccessExpr | implicit-self.swift:47:17:47:30 | CallExpr | implicit-self.swift:48:14:48:23 | MemberAccessExpr | $@ | implicit-self.swift:47:17:47:30 | CallExpr | CallExpr | +| implicit-self.swift:54:14:54:18 | MemberAccessExpr | implicit-self.swift:53:22:53:35 | CallExpr | implicit-self.swift:54:14:54:18 | MemberAccessExpr | $@ | implicit-self.swift:53:22:53:35 | CallExpr | CallExpr | +| test.swift:2:10:2:21 | CallExpr | test.swift:2:10:2:21 | CallExpr | test.swift:2:10:2:21 | CallExpr | $@ | test.swift:2:10:2:21 | CallExpr | CallExpr | +| test.swift:6:10:6:32 | BinaryExpr | test.swift:6:10:6:23 | CallExpr | test.swift:6:10:6:32 | BinaryExpr | $@ | test.swift:6:10:6:23 | CallExpr | CallExpr | +| test.swift:7:10:7:32 | BinaryExpr | test.swift:7:19:7:32 | CallExpr | test.swift:7:10:7:32 | BinaryExpr | $@ | test.swift:7:19:7:32 | CallExpr | CallExpr | +| test.swift:9:10:9:33 | StringInterpolationExpr | test.swift:9:13:9:26 | CallExpr | test.swift:9:10:9:33 | StringInterpolationExpr | $@ | test.swift:9:13:9:26 | CallExpr | CallExpr | +| test.swift:10:10:10:33 | StringInterpolationExpr | test.swift:10:18:10:31 | CallExpr | test.swift:10:10:10:33 | StringInterpolationExpr | $@ | test.swift:10:18:10:31 | CallExpr | CallExpr | +| test.swift:11:10:11:38 | StringInterpolationExpr | test.swift:11:18:11:31 | CallExpr | test.swift:11:10:11:38 | StringInterpolationExpr | $@ | test.swift:11:18:11:31 | CallExpr | CallExpr | +| test.swift:16:10:16:35 | MemberAccessExpr | test.swift:16:11:16:25 | CallExpr | test.swift:16:10:16:35 | MemberAccessExpr | $@ | test.swift:16:11:16:25 | CallExpr | CallExpr | +| test.swift:19:10:19:35 | MemberAccessExpr | test.swift:19:19:19:32 | CallExpr | test.swift:19:10:19:35 | MemberAccessExpr | $@ | test.swift:19:19:19:32 | CallExpr | CallExpr | +| test.swift:24:10:24:10 | a | test.swift:23:13:23:26 | CallExpr | test.swift:24:10:24:10 | a | $@ | test.swift:23:13:23:26 | CallExpr | CallExpr | +| test.swift:29:10:29:10 | a | test.swift:28:19:28:33 | CallExpr | test.swift:29:10:29:10 | a | $@ | test.swift:28:19:28:33 | CallExpr | CallExpr | +| test.swift:34:10:34:10 | d | test.swift:32:27:32:40 | CallExpr | test.swift:34:10:34:10 | d | $@ | test.swift:32:27:32:40 | CallExpr | CallExpr | +| test.swift:39:10:39:10 | a | test.swift:38:13:38:26 | CallExpr | test.swift:39:10:39:10 | a | $@ | test.swift:38:13:38:26 | CallExpr | CallExpr | +| test.swift:47:10:47:16 | MemberAccessExpr | test.swift:46:15:46:28 | CallExpr | test.swift:47:10:47:16 | MemberAccessExpr | $@ | test.swift:46:15:46:28 | CallExpr | CallExpr | +| test.swift:58:10:58:23 | MemberAccessExpr | test.swift:53:22:53:35 | CallExpr | test.swift:58:10:58:23 | MemberAccessExpr | $@ | test.swift:53:22:53:35 | CallExpr | CallExpr | +| test.swift:66:10:66:16 | MemberAccessExpr | test.swift:64:21:64:35 | CallExpr | test.swift:66:10:66:16 | MemberAccessExpr | $@ | test.swift:64:21:64:35 | CallExpr | CallExpr | +| test.swift:75:10:75:16 | MemberAccessExpr | test.swift:74:15:74:29 | CallExpr | test.swift:75:10:75:16 | MemberAccessExpr | $@ | test.swift:74:15:74:29 | CallExpr | CallExpr | +| test.swift:90:10:90:10 | x | test.swift:86:13:86:27 | CallExpr | test.swift:90:10:90:10 | x | $@ | test.swift:86:13:86:27 | CallExpr | CallExpr | +| test.swift:96:10:96:10 | y | test.swift:94:13:94:27 | CallExpr | test.swift:96:10:96:10 | y | $@ | test.swift:94:13:94:27 | CallExpr | CallExpr | +| test.swift:99:14:99:14 | x | test.swift:86:13:86:27 | CallExpr | test.swift:99:14:99:14 | x | $@ | test.swift:86:13:86:27 | CallExpr | CallExpr | +| test.swift:100:14:100:14 | y | test.swift:94:13:94:27 | CallExpr | test.swift:100:14:100:14 | y | $@ | test.swift:94:13:94:27 | CallExpr | CallExpr | +| test.swift:112:10:112:16 | MemberAccessExpr | test.swift:107:19:107:33 | CallExpr | test.swift:112:10:112:16 | MemberAccessExpr | $@ | test.swift:107:19:107:33 | CallExpr | CallExpr | +| test.swift:125:10:125:10 | a | test.swift:117:18:117:33 | CallExpr | test.swift:125:10:125:10 | a | $@ | test.swift:117:18:117:33 | CallExpr | CallExpr | +| test.swift:126:10:126:10 | b | test.swift:117:35:117:49 | CallExpr | test.swift:126:10:126:10 | b | $@ | test.swift:117:35:117:49 | CallExpr | CallExpr | +| test.swift:131:14:131:14 | a | test.swift:131:19:131:33 | CallExpr | test.swift:131:14:131:14 | a | $@ | test.swift:131:19:131:33 | CallExpr | CallExpr | +| test.swift:132:10:132:10 | a | test.swift:131:19:131:33 | CallExpr | test.swift:132:10:132:10 | a | $@ | test.swift:131:19:131:33 | CallExpr | CallExpr | diff --git a/unified/ql/test/library-tests/dataflow/test.ql b/unified/ql/test/library-tests/dataflow/test.ql new file mode 100644 index 000000000000..eccf3ccfd79d --- /dev/null +++ b/unified/ql/test/library-tests/dataflow/test.ql @@ -0,0 +1,15 @@ +/** + * @kind path-problem + * @id unified/test/library-tests/dataflow + * @severity info + * @precision low + */ + +private import unified +private import utils.test.InlineFlowTest +import DefaultFlowTest +import TaintFlow::PathGraph + +from TaintFlow::PathNode source, TaintFlow::PathNode sink +where TaintFlow::flowPath(source, sink) +select sink, source, sink, "$@", source, source.toString() diff --git a/unified/ql/test/library-tests/dataflow/test.swift b/unified/ql/test/library-tests/dataflow/test.swift new file mode 100644 index 000000000000..132afed56490 --- /dev/null +++ b/unified/ql/test/library-tests/dataflow/test.swift @@ -0,0 +1,145 @@ +func t1() { + sink(source("t1")); // $ hasValueFlow=t1 +} + +func t2() { + sink(source("t2.1") + "blah"); // $ hasTaintFlow=t2.1 + sink("blah" + source("t2.2")); // $ hasTaintFlow=t2.2 + + sink("\(source("t2.3")) blah"); // $ hasTaintFlow=t2.3 + sink("blah \(source("t2.4"))"); // $ hasTaintFlow=t2.4 + sink("blah \(source("t2.5")) blah"); // $ hasTaintFlow=t2.5 + sink("blah \(escape: source("t2.6")) blah"); // no flow +} + +func t3() { + sink((source("t3.1"), "safe").0); // $ hasValueFlow=t3.1 + sink((source("t3.2"), "safe").1); // no flow + sink(("safe", source("t3.3")).0); // no flow + sink(("safe", source("t3.4")).1); // $ hasValueFlow=t3.4 +} + +func t4() { + let a = source("t4.1"); + sink(a); // $ hasValueFlow=t4.1 +} + +func t5() { + let (a, b) = (source("t5.1"), "safe"); + sink(a); // $ hasValueFlow=t5.1 + sink(b); // no flow + + let (c, d) = ("safe", source("t5.2")); + sink(c); // no flow + sink(d); // $ hasValueFlow=t5.2 +} + +func t6() { + var a = source("t6.1"); + sink(a); // $ hasValueFlow=t6.1 + a = "safe"; + sink(a); +} + +func t7() { + var tuple = ("safe", "safe") + tuple.0 = source("t7.1"); + sink(tuple.0); // $ hasValueFlow=t7.1 + sink(tuple.1); // no flow +} + +func t8() { + var deep_tuple = (("safe", "safe"), ("safe", "safe")) + deep_tuple.1.0 = source("t8.1"); + sink(deep_tuple); // no flow + sink(deep_tuple.0); // no flow + sink(deep_tuple.1); // no flow + sink(deep_tuple.0.1); // no flow + sink(deep_tuple.1.0); // $ hasValueFlow=t8.1 + sink(deep_tuple.1.1); // no flow +} + +func t9() { + var tuple = ("safe", "safe") + (tuple.1, _) = (source("t9.1"), source("t9.2")); + sink(tuple.0); // no flow + sink(tuple.1); // $ hasValueFlow=t9.1 +} + +func t10() { + var tuple = ("safe", "safe") + sink(tuple.0); // no flow + sink(tuple.1); // no flow + + tuple.0 = source("t10.1"); + sink(tuple.0); // $ hasValueFlow=t10.1 + sink(tuple.1); // no flow + + tuple = ("safe", "safe"); + sink(tuple.0); // no flow + sink(tuple.1); // no flow +} + +func t11() { + var x = "safe"; + if (foo()) { + x = source("t11.1"); + } else { + sink(x); // no flow + } + sink(x); // $ hasValueFlow=t11.1 + + var y = "safe"; + if (foo()) { + y = source("t11.2"); + } + sink(y); // $ hasValueFlow=t11.2 + + if (foo()) { + sink(x); // $ hasValueFlow=t11.1 + sink(y); // $ hasValueFlow=t11.2 + } +} + +func t12() { + var tuple = ("safe", "safe"); + if (foo()) { + tuple.0 = source("t12.1"); + } else { + sink(tuple.0); // no flow + sink(tuple.1); // no flow + } + sink(tuple.0); // $ hasValueFlow=t12.1 + sink(tuple.1); // no flow +} + +func t13() { + var tuple = (source("t13.1"), source("t13.2")); + var (a,b) = ("safe", "safe") + if (foo()) { + (a,b) = tuple + } else { + sink(a); // no flow + sink(b); // no flow + } + sink(a); // $ hasValueFlow=t13.1 + sink(b); // $ hasValueFlow=t13.2 +} + +func t14() { + var a = "safe"; + a = sink(a) + source("t14.1"); // $ SPURIOUS: hasTaintFlow=t14.1 + sink(a); // $ hasTaintFlow=t14.1 +} + +func t15() { + var a = "safe"; + a += source("t15.1"); + sink(a); // $ MISSING: hasTaintFlow=t15.1 +} + +func t16() { + var a = "safe"; + a += sink(a) + source("t16.1"); + sink(a); // $ MISSING: hasTaintFlow=t16.1 +} diff --git a/unified/ql/test/query-tests/security/CWE-312/CleartextLogging/CleartextLogging.expected b/unified/ql/test/query-tests/security/CWE-312/CleartextLogging/CleartextLogging.expected new file mode 100644 index 000000000000..cb3f1cb61f2f --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-312/CleartextLogging/CleartextLogging.expected @@ -0,0 +1,8 @@ +#select +| CleartextLoggingBad.swift:2:7:2:44 | StringInterpolationExpr | CleartextLoggingBad.swift:2:35:2:42 | password | CleartextLoggingBad.swift:2:7:2:44 | StringInterpolationExpr | Logging of $@ | CleartextLoggingBad.swift:2:35:2:42 | password | sensitive data | +edges +| CleartextLoggingBad.swift:2:35:2:42 | password | CleartextLoggingBad.swift:2:7:2:44 | StringInterpolationExpr | provenance | | +nodes +| CleartextLoggingBad.swift:2:7:2:44 | StringInterpolationExpr | semmle.label | StringInterpolationExpr | +| CleartextLoggingBad.swift:2:35:2:42 | password | semmle.label | password | +subpaths diff --git a/unified/ql/test/query-tests/security/CWE-312/CleartextLogging/CleartextLogging.qlref b/unified/ql/test/query-tests/security/CWE-312/CleartextLogging/CleartextLogging.qlref new file mode 100644 index 000000000000..53e950061f85 --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-312/CleartextLogging/CleartextLogging.qlref @@ -0,0 +1,2 @@ +query: queries/security/CWE-312/CleartextLogging.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/unified/ql/test/query-tests/security/CWE-312/CleartextLogging/CleartextLoggingBad.swift b/unified/ql/test/query-tests/security/CWE-312/CleartextLogging/CleartextLoggingBad.swift new file mode 100644 index 000000000000..bb6e84d1892b --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-312/CleartextLogging/CleartextLoggingBad.swift @@ -0,0 +1,2 @@ +let password = "P@ssw0rd" +NSLog("User password changed to \(password)") // $ Alert diff --git a/unified/ql/test/query-tests/security/CWE-312/CleartextLogging/CleartextLoggingGood.swift b/unified/ql/test/query-tests/security/CWE-312/CleartextLogging/CleartextLoggingGood.swift new file mode 100644 index 000000000000..1d90ac5e5656 --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-312/CleartextLogging/CleartextLoggingGood.swift @@ -0,0 +1,2 @@ +let password = "P@ssw0rd" +NSLog("User password changed")