Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
60baec6
unified: Add test showing problem with literal getValue()
asgerf Aug 6, 2026
51fe273
unified: Fix extraction of literals
asgerf Aug 6, 2026
b71ff8f
unified: Factor some code into CommentUtil.qll
asgerf Aug 6, 2026
c9fc793
unified: Fix mapping for compound type names
asgerf Aug 6, 2026
f50c80a
unified: Initial static name binding pass
asgerf Aug 6, 2026
375924e
unified: Track through aliases
asgerf Aug 6, 2026
db8b97a
unified: Add inheritance steps
asgerf Aug 6, 2026
6341bba
unified: Support unqualified access
asgerf Aug 6, 2026
ee8753b
unified: Allow numbers in key=value comments
asgerf Aug 6, 2026
7e3a4e3
unified: Cross-file name binding
asgerf Aug 6, 2026
574a5c9
unified: Handle bracketed generic array constructors
asgerf Aug 7, 2026
55b96f1
unified: Handle bracketed generic array metatypes
asgerf Aug 7, 2026
171aa7a
unified: Use '.' as location for inferred_type_expr
asgerf Aug 7, 2026
a9792e5
unified: Fix handling of exprPattern
asgerf Aug 7, 2026
8080bac
unified: Add newlines at EOF
asgerf Aug 7, 2026
0cf2871
unified: Update comment
asgerf Aug 7, 2026
a3f21a5
unified: Support scoped imports
asgerf Aug 7, 2026
50f934b
unified: Don't track trivial name aliasse
asgerf Aug 7, 2026
a79c32a
unified: Bulk imports
asgerf Aug 7, 2026
05fe395
unified: Fix access level in test case
asgerf Aug 7, 2026
659da08
unified: Fix incorrect expectation
asgerf Aug 7, 2026
35c54b1
unified: Fix toString and getLocation for TLocalNamespace
asgerf Aug 7, 2026
af2f5a2
unified: Remove location of TModuleRoot
asgerf Aug 7, 2026
970e4c6
unified: Fix handling of unscoped imports
asgerf Aug 7, 2026
f596a67
unified: Fix Swift source folder documentation
asgerf Aug 10, 2026
d6164c3
unified: Preserve array constructor trailing closures
asgerf Aug 10, 2026
a658c77
unified: Add test showing AST mapping error
asgerf Aug 11, 2026
c58a096
unified: Fix translation of callee in constructor pattern
asgerf Aug 11, 2026
3999894
unified: Clarify description of derivedStoreReadStep
asgerf Aug 13, 2026
e88dde7
unified: Make debug graph subset more configurable
asgerf Aug 13, 2026
d30b7a0
unified: Simplify uncertain scopes
asgerf Aug 13, 2026
e23f3e4
unified: Don't step into declaration sites
asgerf Aug 13, 2026
bec213b
shared: Factor out declInScope(name, scope)
asgerf Aug 13, 2026
4eade9e
shared: Rephrase a qldoc
asgerf Aug 13, 2026
828816c
unified: QLdoc fix
asgerf Aug 13, 2026
78671cd
unified: Prefer 'and' instead of '|'
asgerf Aug 13, 2026
5d7e64e
unified: Add test with @_exported import
asgerf Aug 13, 2026
61b3720
unified: Support @_exported imports
asgerf Aug 13, 2026
811932f
unified: Add test for spurious re-export
asgerf Aug 13, 2026
1f380d2
unified: Fix spurious resolution by refining isPrivateToLocalScope
asgerf Aug 13, 2026
719e8b4
unified: Prefer instanceof
asgerf Aug 13, 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
29 changes: 29 additions & 0 deletions shared/namebinding/codeql/namebinding/LocalNameBinding.qll
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ signature module LocalNameBindingInputSig<LocationSig Location> {
* full control of scope resolution for specific types of references.
*/
default predicate lookupStartsAt(AstNode n, AstNode scope) { none() }

/**
* Holds if the set of names available in `scope` is not known ahead of time,
* and thus any lookup chain that goes through `scope` may need to be reconciled at a later stage.
*/
default predicate uncertainScope(AstNode scope) { none() }
}

/**
Expand All @@ -154,6 +160,8 @@ module LocalNameBinding<LocationSig Location, LocalNameBindingInputSig<Location>
implicitDeclInScope(_, this)
or
isTopScope(this)
or
uncertainScope(this)
}
}

Expand Down Expand Up @@ -353,6 +361,27 @@ module LocalNameBinding<LocationSig Location, LocalNameBindingInputSig<Location>
)
}

/**
* Holds if `name`, when resolved from `lookup`, may resolve to one of the uncertain members of `scope`.
*/
pragma[nomagic]
private predicate lookupInUncertainScope(string name, Scope lookup, Scope scope) {
lookupInScope(name, lookup, scope) and
uncertainScope(scope) and
not declInScope(_, name, scope) and
not implicitDeclInScope(name, scope)
Comment thread
hvitved marked this conversation as resolved.
Outdated
}

/**
* Gets an uncertain scope that the given `accessCand` pair may resolve to.
Comment thread
hvitved marked this conversation as resolved.
Outdated
*/
AstNode getAnUncertainScope(AstNode access, string name) {
exists(Scope lookup |
accessCandInLookupScope(access, name, lookup) and
lookupInUncertainScope(name, lookup, result)
)
}

cached
private newtype TLocal =
TExplicitLocal(AstNode definingNode, string name, AstNode scope) {
Expand Down
188 changes: 121 additions & 67 deletions unified/extractor/src/languages/swift/swift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,30 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
// swift-syntax does not distinguish the lexical integer/string forms
// (hex/binary/octal, single- vs multi-line, raw): each is a single
// `*LiteralExpr` kind, so one rule per literal type suffices.
rule!((integerLiteralExpr) => (int_literal)),
rule!((floatLiteralExpr) => (float_literal)),
rule!((booleanLiteralExpr) => (boolean_literal)),
rule!((nilLiteralExpr) => (builtin_expr)),
rule!((stringLiteralExpr) => (string_literal)),
rule!((regexLiteralExpr) => (regex_literal)),
rule!((integerLiteralExpr) @@node => expr {
let value = tree!((int_literal #{node}));
if ctx.in_pattern { tree!((expr_equality_pattern expr: {value})) } else { value }
}),
rule!((floatLiteralExpr) @@node => expr {
let value = tree!((float_literal #{node}));
if ctx.in_pattern { tree!((expr_equality_pattern expr: {value})) } else { value }
}),
rule!((booleanLiteralExpr) @@node => expr {
let value = tree!((boolean_literal #{node}));
if ctx.in_pattern { tree!((expr_equality_pattern expr: {value})) } else { value }
}),
rule!((nilLiteralExpr) @@node => expr {
let value = tree!((builtin_expr #{node}));
if ctx.in_pattern { tree!((expr_equality_pattern expr: {value})) } else { value }
}),
rule!((stringLiteralExpr) @@node => expr {
let value = tree!((string_literal #{node}));
if ctx.in_pattern { tree!((expr_equality_pattern expr: {value})) } else { value }
}),
rule!((regexLiteralExpr) @@node => expr {
let value = tree!((regex_literal #{node}));
if ctx.in_pattern { tree!((expr_equality_pattern expr: {value})) } else { value }
}),
// ---- Names ----
// A function reference spelled with argument labels (`f(x:y:z:)`) is a
// `declReferenceExpr` carrying `argumentNames`. Mark it unsupported for
Expand All @@ -176,6 +194,14 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
=>
(unsupported_node)
),
rule!((declReferenceExpr baseName: (identifier) @name) => expr {
let name = tree!((name_expr identifier: (identifier #{name})));
if ctx.in_pattern {
tree!((expr_equality_pattern expr: {name}))
} else {
name
}
}),
// A bare name reference (`x`), and an operator used as a value (`+` in
// `reduce(0, +)`), are both `declReferenceExpr`; its `baseName` is the
// referenced identifier / operator symbol.
Expand Down Expand Up @@ -507,29 +533,6 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
// introduces a new binding; it unwraps to its inner pattern (a
// `name_pattern`).
rule!((valueBindingPattern pattern: @p) => pattern { p }),
// An enum-case pattern with associated values (`case .foo(let x)`,
// `case Color.foo(let x)`) is an expression pattern wrapping a call of a
// member access. It becomes a `constructor_pattern`; its arguments are
// translated as pattern elements (see the `labeledExpr` rules, gated by
// `ctx.in_pattern`). Matched before the generic `expressionPattern` rule.
// The base is optional: a leading-dot form (`.foo`) has none, so the
// constructor's base is an `inferred_type_expr`.
rule!(
(expressionPattern expression: (functionCallExpr
calledExpression: (memberAccessExpr base: _? @base period: @dot declName: (declReferenceExpr baseName: @name))
arguments: _* @@args))
=>
constructor_pattern {
ctx.in_pattern = true;
let elements = ctx.translate(args)?;
let base = base.unwrap_or_else(|| tree!((inferred_type_expr #{dot})));
tree!((constructor_pattern
constructor: (member_access_expr
base: {base}
member: (identifier #{name}))
element: {elements}))
}
),
// A tuple destructuring pattern (`let (a, b) = …`). A labelled element
// (`let (x: a) = …`) carries its label through as the `pattern_element`
// key; unlabelled elements have no key.
Expand All @@ -544,36 +547,16 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
// handling in the future. (Redundant with the catch-all fallback, but
// kept as a signpost.)
rule!((isTypePattern) => (unsupported_node)),
// A standalone wildcard pattern (`case _:`, `if case _`): swift-syntax
// models the bare `_` as an `expressionPattern` wrapping a
// `discardAssignmentExpr`. Matched before the generic `expressionPattern`
// rule so `_` becomes an `ignore_pattern` rather than an equality match.
// (Wildcards *inside* an enum-case argument list are handled by the
// `labeledExpr`/`discardAssignmentExpr` rules.)
rule!((expressionPattern expression: (discardAssignmentExpr)) => (ignore_pattern)),
// A wildcard *binding* pattern (`let _ = x`, `for _ in xs`). swift-syntax
// models this as a `wildcardPattern` — distinct from the `_` *match*
// pattern above, which is an `expressionPattern` over a
// `discardAssignmentExpr`.
// models this as a `wildcardPattern`, distinct from the `_` match form
// handled by the context-aware `discardAssignmentExpr` rule.
rule!((wildcardPattern) => (ignore_pattern)),
// A tuple pattern in a match position (`case (let a, 3):`) is parsed by
// swift-syntax as an `expressionPattern` wrapping a `tupleExpr` — unlike a
// binding tuple (`let (a, b)`), which is a real `tuplePattern`. Recognise
// it as a `tuple_pattern`; its `labeledExpr` elements translate to
// `pattern_element`s under `ctx.in_pattern` (a binding element becomes a
// `name_pattern`, any other expression an `expr_equality_pattern`).
rule!(
(expressionPattern expression: (tupleExpr elements: _* @@els))
=>
tuple_pattern {
ctx.in_pattern = true;
let elements = ctx.translate(els)?;
tree!((tuple_pattern element: {elements}))
}
),
// A bare expression pattern (`case 1:`, `case someConstant:`) matches by
// equality.
rule!((expressionPattern expression: @e) => (expr_equality_pattern expr: {e})),
// An expression pattern only establishes pattern context; its child
// determines the concrete pattern shape.
rule!((expressionPattern expression: @@e) => expr {
ctx.in_pattern = true;
ctx.translate(e)?.into_iter().next().ok_or("expression pattern has no child")?
Comment thread
asgerf marked this conversation as resolved.
}),
// ---- Functions ----
// A function declaration (parameters/return type/body optional). The
// parameters and return type nest under `signature`; the body is a
Expand Down Expand Up @@ -633,6 +616,22 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
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`,
// translating that callee as an array literal would place a type
// expression in an expression-only element field. Normalize it to an
// `Array<T>` generic type constructor instead.
rule!(
(functionCallExpr
calledExpression: (arrayExpr elements: (arrayElement expression: (genericSpecializationExpr) @element))
arguments: _* @args)
=>
(call_expr
callee: (generic_type_expr
base: (named_type_expr name: (identifier "Array"))
type_argument: {element})
argument: {args})
),
Comment thread
asgerf marked this conversation as resolved.
// A function/method call (`foo(1, 2)`). `calledExpression` is the callee
// and `arguments` is an (elided) list of `labeledExpr`, each translated
// to an `argument` below. A trailing closure (`xs.map { … }`) becomes a
Expand All @@ -645,7 +644,13 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
rule!(
(functionCallExpr calledExpression: @callee arguments: _* @args)
=>
(call_expr callee: {callee} argument: {args})
expr {
if ctx.in_pattern {
tree!((constructor_pattern constructor: {callee} element: {args}))
} else {
tree!((call_expr callee: {callee} argument: {args}))
}
}
),
// A call argument or an enum-case pattern argument. When translating an
// enum-case `constructor_pattern`'s arguments (`ctx.in_pattern`), a
Expand All @@ -656,6 +661,27 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
// Otherwise the argument keeps its label as the `name` and its value.
// The pattern-only shapes (`patternExpr`, `discardAssignmentExpr`) are
// matched first; they never occur as ordinary call arguments.
rule!(
(labeledExpr
label: _? @@lbl
expression: (functionCallExpr
calledExpression: @constructor
arguments: _* @elements))
=>
argument {
if ctx.in_pattern {
tree!((pattern_element
key: (identifier #{lbl})?
pattern: (constructor_pattern
constructor: {constructor}
element: {elements})))
} else {
tree!((argument
name: (identifier #{lbl})?
value: (call_expr callee: {constructor} argument: {elements})))
}
}
),
rule!(
(labeledExpr label: _? @@lbl expression: (patternExpr pattern: @p))
=>
Expand All @@ -673,7 +699,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
if ctx.in_pattern {
tree!((pattern_element
key: (identifier #{lbl})?
pattern: (expr_equality_pattern expr: {val})))
pattern: {val}))
} else {
tree!((argument name: (identifier #{lbl})? value: {val}))
}
Expand All @@ -683,15 +709,29 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
// `declReferenceExpr`; pull its `baseName` out as the member identifier.
// A leading-dot access (`.foo`) has no explicit base — the base is an
// `inferred_type_expr`. The base-ful form is matched first.
// A bracketed generic array type used as a metatype or static-member
// base (`[T].self`) is parsed as an `arrayExpr`; preserve its type
// meaning as `Array<T>` rather than an array literal.
rule!(
(memberAccessExpr
base: (arrayExpr elements: (arrayElement expression: (genericSpecializationExpr) @element))
declName: (declReferenceExpr baseName: @member))
=>
(member_access_expr
base: (generic_type_expr
base: (named_type_expr name: (identifier "Array"))
type_argument: {element})
member: (identifier #{member}))
),
rule!(
(memberAccessExpr base: @base declName: (declReferenceExpr baseName: @member))
=>
(member_access_expr base: {base} member: (identifier #{member}))
),
rule!(
(memberAccessExpr declName: (declReferenceExpr baseName: @member))
(memberAccessExpr period: @dot declName: (declReferenceExpr baseName: @member))
=>
(member_access_expr base: (inferred_type_expr) member: (identifier #{member}))
(member_access_expr base: (inferred_type_expr #{dot}) member: (identifier #{member}))
),
// Control transfer, one rule per keyword. `return` carries an optional
// value; `break` / `continue` an optional target label; `throw` its
Expand Down Expand Up @@ -903,7 +943,18 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
),
// ---- Optionals and errors ----
// Optional chaining — unwrap the marker
rule!((optionalChainingExpr expression: @inner) => expr { inner }),
rule!((optionalChainingExpr expression: @@inner) => expr {
let inner = ctx.translate(inner)?.into_iter().next().ok_or("optional chaining expression has no child")?;
if ctx.in_pattern {
tree!((constructor_pattern
constructor: (member_access_expr
base: (named_type_expr name: (identifier "Optional"))
member: (identifier "some"))
element: (pattern_element pattern: {inner})))
} else {
inner
}
}),
// try/try?/try! expr → unary_expr with operator "try", "try?" or "try!"
rule!(
(tryExpr questionOrExclamationMark: _? @@m expression: @e)
Expand Down Expand Up @@ -993,8 +1044,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
// becomes a `modifier`; its source text is the modifier spelling.
rule!((attribute) @m => (modifier #{m})),
rule!((declModifier) @m => (modifier #{m})),
// A `super` expression. (`self` needs no rule: swift-syntax models it as
// an ordinary `declReferenceExpr`, already mapped to a `name_expr`.)
// A `super` expression.
rule!((superExpr) => (super_expr)),
// Type expressions. A generic type applied with explicit arguments
// (`Set<Int>`) becomes a `generic_type_expr` whose `base` is the type
Expand All @@ -1014,9 +1064,13 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
// A named type (`Int`). `identifierType.name` is the type-name token.
rule!((identifierType name: @@n) => (named_type_expr name: (identifier #{n}))),
// A qualified type (`Outer.Inner`, `NSString.CompareOptions`). swift-syntax
// nests these as `memberType` nodes; we keep the whole dotted path as the
// opaque `named_type_expr` name.
rule!((memberType) @ty => (named_type_expr name: (identifier #{ty}))),
// nests these as `memberType` nodes; preserve the nesting in the
// named_type_expr qualifier field.
rule!(
(memberType baseType: @base name: @@name)
=>
(named_type_expr qualifier: {base} name: (identifier #{name}))
),
// Sugared types desugar to `generic_type_expr`: `T?` -> Optional<T>,
// `[T]` -> Array<T>, `[K: V]` -> Dictionary<K, V>.
rule!(
Expand Down
Loading