Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
043701d
unified: Make data flow tests valid
asgerf Sep 18, 2026
ec82b66
unified: Add some basic call graph tests
asgerf Sep 17, 2026
11c7858
unified: Add ParameterPosition
asgerf Sep 17, 2026
724807f
unified: Add DataFlowCall and DataFlowCallable
asgerf Sep 17, 2026
80f557a
unified: ParameterNode, ArgumentNode, OutNode
asgerf Sep 17, 2026
0395232
unified: Add basic call graph
asgerf Sep 17, 2026
9da74ef
unified: Fix CFG node associated with parameter assignment
asgerf Sep 17, 2026
f098a84
unified: Populate external name when parameter has only one name
asgerf Sep 18, 2026
43aa59f
unified: Update data flow test output
asgerf Sep 18, 2026
6aca722
unified: Update BasicTest output after duplicating parameter name
asgerf Sep 19, 2026
76c39b6
unified: Insert post-updates for call arguments
asgerf Sep 18, 2026
f4f7e69
unified: Avoid stepping into post-update nodes
asgerf Sep 18, 2026
4ebd5f7
unified: Add call graph stats
asgerf Sep 18, 2026
a9340a8
unified: Add canonical receiver parameter
asgerf Sep 17, 2026
e1326fd
unified: Also add getEnclosingCallableEx
asgerf Sep 18, 2026
82ed701
unified: Add receiver argument node
asgerf Sep 18, 2026
25baa0b
unified: Link up receiver argument/parameter
asgerf Sep 17, 2026
8f2b812
unified: Refactor first column in performsVariableAccess
asgerf Sep 17, 2026
2e3081e
unified: Store the receiver in 'self'
asgerf Sep 17, 2026
6acf683
unified: Wire up call receiver
asgerf Sep 18, 2026
29499de
unified: Ensure implicit 'self' has a post-update
asgerf Sep 18, 2026
1175f84
unified: Add test
asgerf Sep 18, 2026
10bcc25
unified: Add 8 combinations of implicit/explicit self
asgerf Sep 18, 2026
414c86c
unified: Move some variables into fields
asgerf Sep 18, 2026
9494c90
unified: Fix getEnclosingCallable for LocalVariableRef
asgerf Sep 18, 2026
f144d38
unified: Exclude calls in pattern context
asgerf Sep 18, 2026
832dfb4
unified: Tolerate missing post-updates in unreachable code
asgerf Sep 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 19 additions & 14 deletions unified/extractor/src/languages/swift/swift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -596,26 +596,31 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
),
// A function parameter. With two names (`firstName`+`secondName`) the
// first is the external argument label and the second the internal name;
// with one name it is just the internal name. The declared type is
// emitted; the default value is optional.
// with one name it is both the external and internal name.
rule!(
(functionParameter
firstName: @@first
secondName: _? @@second
secondName: @@second
type: @ty
defaultValue: (initializerClause value: @val)?)
=>
parameter {
let (external, name) = match second {
Some(second) => (Some(tree!((identifier #{first}))), second),
None => (None, first),
};
tree!((parameter
external_name_node: {external}
pattern: (identifier #{name})
type: {ty}
default: {val}))
}
(parameter
external_name_node: (identifier #{first})
pattern: (identifier #{second})
type: {ty}
default: {val})
),
rule!(
(functionParameter
firstName: @@first
type: @ty
defaultValue: (initializerClause value: @val)?)
=>
(parameter
external_name_node: (identifier #{first}) // duplicate the parameter name
pattern: (identifier #{first})
type: {ty}
default: {val})
),
// Swift's `[T](...)` array-type constructor syntax is parsed as a call
// whose callee is an `arrayExpr` containing `T`. For a generic `T`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ top_level source="⟨body⟩"
function_declaration source="⟨body⟩⟨name_node⟩⟨parameter⟩"
name_node: identifier "greet" source="greet"
parameter:
parameter source="⟨pattern⟩: ⟨type⟩ = ⟨default⟩"
parameter source="⟨external_name_node⟩⟨pattern⟩: ⟨type⟩ = ⟨default⟩"
external_name_node: identifier "name" source="name"
type: identifier "String" source="String"
pattern: identifier "name" source="name"
default: string_literal "\"world\"" source="\"world\""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ top_level source="⟨body⟩"
type: identifier "Int" source="Int"
constructor_declaration source="⟨body⟩⟨parameter⟩"
parameter:
parameter source="⟨pattern⟩: ⟨type⟩"
parameter source="⟨external_name_node⟩⟨pattern⟩: ⟨type⟩"
external_name_node: identifier "x" source="x"
type: identifier "Int" source="Int"
pattern: identifier "x" source="x"
body:
Expand Down
6 changes: 5 additions & 1 deletion unified/ql/consistency-queries/DataFlowConsistency.ql
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ private import unified
private import codeql.unified.internal.dataflow.AllDataFlow
private import codeql.dataflow.internal.DataFlowImplConsistency

module ConsistencyInput implements InputSig<Location, DataFlowInput> { }
module ConsistencyInput implements InputSig<Location, DataFlowInput> {
predicate argHasPostUpdateExclude(DataFlowInput::ArgumentNode n) {
not exists(n.getBasicBlock()) // ignore unreachable data flow nodes
}
}

module ConsistencyOutput =
MakeConsistency<Location, DataFlowInput, TaintTrackingInput, ConsistencyInput>;
Expand Down
19 changes: 19 additions & 0 deletions unified/ql/lib/codeql/unified/internal/AnalysisQuality.qll
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
private import unified
private import codeql.util.ReportStats
private import codeql.unified.internal.NameBinding
private import codeql.unified.internal.dataflow.DataFlowCall
private import codeql.unified.internal.dataflow.DataFlowCallable
private import codeql.unified.internal.dataflow.CallGraph

/** Stats about name nodes that static name binding could resolve. */
module StaticNameResolutionStats implements EntityStatsSig {
Expand Down Expand Up @@ -87,3 +90,19 @@ module FilesCoveredByModuleManifestStats implements EntityStatsSig {

module FilesCoveredByModuleManifestStatsReport =
EntityReportStats<FilesCoveredByModuleManifestStats>;

module CallGraphStats implements EntityStatsSig {
class Candidate extends CallExpr {
DataFlowCall getDataFlowCall() { result.asExplicitCall() = this }

DataFlowCallable getTarget() { result = viableCallable(this.getDataFlowCall()) }

predicate isOk() { exists(this.getTarget()) }
}

string getOkText() { result = "calls with call target" }

string getNotOkText() { result = "calls with missing call target" }
}

module CallGraphStatsReport = EntityReportStats<CallGraphStats>;
37 changes: 35 additions & 2 deletions unified/ql/lib/codeql/unified/internal/FacadeAst.qll
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,19 @@ module Unified {
class Argument extends G::Argument {
/** Gets the name of this argument. */
string getName() { result = this.getNameNode().getValue() }

/** Holds if this is a positional argument. */
predicate isPositional() { not exists(this.getName()) }

/** Gets the 0-based index of this argument among the positional arguments in the surrounding call or tuple. */
int getPositionalIndex() {
this =
rank[result + 1](Argument a |
a.getParent() = this.getParent() and a.isPositional()
|
a order by a.getParentIndex()
)
}
}

class AssociatedTypeDeclaration extends G::AssociatedTypeDeclaration {
Expand Down Expand Up @@ -172,8 +185,28 @@ module Unified {
}

class Parameter extends G::Parameter {
/** Gets the external name of this parameter. */
string getExternalName() { result = this.getExternalNameNode().getValue() }
/**
* Gets the external name of this parameter.
*
* Has no result for pseudo-names like `_` that indicate that this is actually a positional parameter.
*/
string getExternalName() { result = this.getExternalNameNode().getValue() and not result = "_" }

/** Gets the callable on which this parameter appears. */
Callable getDeclaringCallable() { result = this.getParent() }

/** Holds if this is a positional parameter. */
predicate isPositional() { not exists(this.getExternalName()) }

/** Gets the 0-based index of this parameter among the positional parameters of the declaring callable. */
int getPositionalIndex() {
this =
rank[result + 1](Parameter p |
p.getDeclaringCallable() = this.getDeclaringCallable() and p.isPositional()
|
p order by p.getParentIndex()
)
}
}

class TypeAliasDeclaration extends G::TypeAliasDeclaration {
Expand Down
8 changes: 8 additions & 0 deletions unified/ql/lib/codeql/unified/internal/LocalNameBinding.qll
Original file line number Diff line number Diff line change
Expand Up @@ -474,3 +474,11 @@ class PotentialLocalNameAccess extends IdentifierExpr {
/** Holds if this is one of the binding sites for a name, such as the `x` in `let x = 123`. */
predicate isBindingSite() { this instanceof NameBinding }
}

/** Gets implicitly-declared variable through which the given callable refers to its receiver. */
LocalVariable getImplicitReceiverVariable(Callable callable) {
exists(string name |
name = any(NameBindingPlugin p).getImplicitReceiverParameterName(callable) and
result.(LocalNameBindingOutput::ImplicitLocal).hasNameAndScope(name, callable)
)
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
/** Re-exports all the files in the internal dataflow folder (except DataFlowPublic). */

import CallGraph
import Content
import DataFlowCall
import DataFlowCallable
import DataFlowGraph
import DataFlowInstantiation
import DataFlowNode
import DataFlowPlugin
import Step
import LocalSsa
import ParameterPositions
import Step
import TaintTrackingInstantiation
import VariableRefKind
16 changes: 16 additions & 0 deletions unified/ql/lib/codeql/unified/internal/dataflow/CallGraph.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
private import unified
private import AllDataFlow
private import codeql.unified.internal.NameBinding as N

private Callable getCallableFromNameBinding(NameBinding binding) {
binding = result.(FunctionDeclaration).getNameNode()
}

DataFlowCallable viableCallable(DataFlowCall c) {
exists(CallExpr call, Callable callable, NameBinding target |
c.asExplicitCall() = call and
target = N::getStaticBindingTarget(N::getIdentifierFromRef(call.getCallee())) and
callable = getCallableFromNameBinding(target) and
result.asSourceCallable() = callable
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
private import unified
private import AllDataFlow
private import codeql.unified.internal.ExprPositions

private newtype TDataFlowCall =
TExplicitCall(CallExpr call) {
not isInBindingContext(call, _) // ignore constructor patterns
}

/**
* A call site, covering both explicit calls such as `foo(1,2)`, as well an implicit
* calls and calls derived from library models.
*
* Currently only explicit calls are implemented.
*/
class DataFlowCall extends TDataFlowCall {
/** Gets the `CallExpr` wrapped by this dataflow call, if any. */
CallExpr asExplicitCall() { this = TExplicitCall(result) }

/** Gets a string representation of this call. */
string toString() { result = this.asExplicitCall().toString() }

/** Gets the location of this call, if any. */
Location getLocation() { result = this.asExplicitCall().getLocation() }

/** Gets the callable containing this call. */
DataFlowCallable getEnclosingCallable() {
result.asSourceCallable() = this.asExplicitCall().getEnclosingCallable()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
private import unified
private import AllDataFlow

private newtype TDataFlowCallable = TSourceCallable(Callable callable)

/**
* A callable entity, either an function-like entity in the source code,
* an entity derived from a library model.
*
* Currently only callables in source code are implemented.
*/
class DataFlowCallable extends TDataFlowCallable {
/** Gets the `Callable` wrapped by this dataflow callable, if any. */
Callable asSourceCallable() { this = TSourceCallable(result) }

/** Gets a string representation of this call. */
string toString() { result = this.asSourceCallable().toString() }

/** Gets the location of this call, if any. */
Location getLocation() { result = this.asSourceCallable().getLocation() }
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,38 @@
private import unified
private import AllDataFlow
private import codeql.unified.internal.LocalNameBinding

predicate step(Node node1, Step step, Node node2) {
any(DataFlowPlugin p).step(node1, step, node2)
or
exists(Callable callable |
node1.isReceiverParameter(callable) and
step.value() and
node2.isLocalVariableWrite(callable, getImplicitReceiverVariable(callable))
)
or
exists(CallExpr call, Expr receiverExpr |
receiverExpr = call.getCallee().(MemberAccessExpr).getBase()
|
node1.isResultValue(receiverExpr) and
step.value() and
node2.isReceiverArgument(call)
or
node1.isReceiverPostUpdate(call) and
step.value() and
node2.isPostUpdate(receiverExpr)
)
or
exists(CallExpr call, UnqualifiedMemberAccess callee | callee = call.getCallee() |
node1.isLocalVariableRead(callee, callee.getImplicitQualifierVariable()) and
step.value() and
node2.isReceiverArgument(call)
or
node1.isReceiverPostUpdate(call) and
step.value() and
node2.isLocalVariablePostUpdate(callee, callee.getImplicitQualifierVariable())
)
or
exists(VariableDeclaration decl |
node1.isResultValue(decl.getValue()) and
step.value() and
Expand Down
Loading
Loading