Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
22 changes: 13 additions & 9 deletions unified/extractor/src/languages/swift/swift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,12 @@ 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 => (int_literal #{node})),
rule!((floatLiteralExpr) @@node => (float_literal #{node})),
rule!((booleanLiteralExpr) @@node => (boolean_literal #{node})),
rule!((nilLiteralExpr) @@node => (builtin_expr #{node})),
rule!((stringLiteralExpr) @@node => (string_literal #{node})),
rule!((regexLiteralExpr) @@node => (regex_literal #{node})),
// ---- Names ----
// A function reference spelled with argument labels (`f(x:y:z:)`) is a
// `declReferenceExpr` carrying `argumentNames`. Mark it unsupported for
Expand Down Expand Up @@ -1014,9 +1014,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
139 changes: 139 additions & 0 deletions unified/extractor/tests/corpus/swift/types/qualified-type.output
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
struct Outer {
struct Inner {
struct Deep {}
}
}

let value: Outer.Inner
let nested: Outer.Inner.Deep

---

sourceFile
endOfFileToken: endOfFile
statements:
codeBlockItem
item:
structDecl
attributes:
name: identifier "Outer"
memberBlock:
memberBlock
leftBrace: {
rightBrace: }
members:
memberBlockItem
decl:
structDecl
attributes:
name: identifier "Inner"
memberBlock:
memberBlock
leftBrace: {
rightBrace: }
members:
memberBlockItem
decl:
structDecl
attributes:
name: identifier "Deep"
memberBlock:
memberBlock
leftBrace: {
rightBrace: }
members:
modifiers:
structKeyword: struct
modifiers:
structKeyword: struct
modifiers:
structKeyword: struct
codeBlockItem
item:
variableDecl
attributes:
modifiers:
bindingSpecifier: let
bindings:
patternBinding
pattern:
identifierPattern
identifier: identifier "value"
typeAnnotation:
typeAnnotation
colon: :
type:
memberType
name: identifier "Inner"
baseType:
identifierType
name: identifier "Outer"
period: .
codeBlockItem
item:
variableDecl
attributes:
modifiers:
bindingSpecifier: let
bindings:
patternBinding
pattern:
identifierPattern
identifier: identifier "nested"
typeAnnotation:
typeAnnotation
colon: :
type:
memberType
name: identifier "Deep"
baseType:
memberType
name: identifier "Inner"
baseType:
identifierType
name: identifier "Outer"
period: .
period: .

---

top_level
body:
block
stmt:
class_like_declaration
modifier: modifier "struct"
name: identifier "Outer"
member:
class_like_declaration
modifier: modifier "struct"
name: identifier "Inner"
member:
class_like_declaration
modifier: modifier "struct"
name: identifier "Deep"
variable_declaration
modifier: modifier "let"
pattern:
name_pattern
identifier: identifier "value"
type:
named_type_expr
qualifier:
named_type_expr
name: identifier "Outer"
name: identifier "Inner"
variable_declaration
modifier: modifier "let"
pattern:
name_pattern
identifier: identifier "nested"
type:
named_type_expr
qualifier:
named_type_expr
qualifier:
named_type_expr
name: identifier "Outer"
name: identifier "Inner"
name: identifier "Deep"
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
struct Outer {
struct Inner {
struct Deep {}
}
}

let value: Outer.Inner
let nested: Outer.Inner.Deep
8 changes: 8 additions & 0 deletions unified/ql/lib/codeql/unified/internal/FacadeAst.qll
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ module Unified {
mod.getValue() = text
)
}

/** Gets the nearest enclosing class declaration, possibly this node itself. */
ClassLikeDeclaration getEnclosingClass() {
result = this
or
not this instanceof ClassLikeDeclaration and
result = this.getParent().getEnclosingClass()
}
}

/** The base class for all patterns. */
Expand Down
67 changes: 48 additions & 19 deletions unified/ql/lib/codeql/unified/internal/LocalNameBinding.qll
Original file line number Diff line number Diff line change
Expand Up @@ -184,79 +184,92 @@ private module LocalNameBindingInput implements LocalNameBindingInputSig<Locatio
override AstNode getElse() { none() }
}

additional predicate bindingContext(AstNode pattern, AstNode scope) {
additional predicate bindingContext(AstNode pattern, AstNode scope, AstNode declaration) {
exists(SiblingShadowingDecl decl |
scope = decl and
pattern = decl.getPattern()
pattern = decl.getPattern() and
declaration = decl
)
or
exists(VariableDeclaration decl |
not decl instanceof SiblingShadowingDecl and
getChild(scope, _) = decl and
pattern = decl.getPattern()
pattern = decl.getPattern() and
declaration = decl
)
or
exists(FunctionDeclaration func |
getChild(scope, _) = func and
pattern = func.getName()
pattern = func.getName() and
declaration = func
)
or
exists(Parameter param |
scope = param.getParent() and // TODO: add SourceCallable and use .getParameter() instead
pattern = param.getPattern()
pattern = param.getPattern() and
declaration = param
)
or
exists(CatchClause catch |
scope = catch and // ensure both body and pattern are in scope
pattern = catch.getPattern()
pattern = catch.getPattern() and
declaration = catch
)
or
exists(SwitchCase case |
scope = case and // ensure both body and pattern are in scope
pattern = case.getPattern()
pattern = case.getPattern() and
declaration = case
)
or
exists(ForEachStmt stmt |
scope = stmt and // ensure both 'body' and 'guard' are in scope
pattern = stmt.getPattern()
pattern = stmt.getPattern() and
declaration = stmt
)
or
exists(ClassLikeDeclaration cls |
getChild(scope, _) = cls and
pattern = cls.getName()
pattern = cls.getName() and
declaration = cls
)
or
exists(TypeAliasDeclaration decl |
getChild(scope, _) = decl and
pattern = decl.getName()
pattern = decl.getName() and
declaration = decl
)
or
exists(TypeParameter param |
scope = param.getParent() and
pattern = param.getName()
pattern = param.getName() and
declaration = param
)
or
exists(AssociatedTypeDeclaration decl |
getChild(scope, _) = decl and
pattern = decl.getName()
pattern = decl.getName() and
declaration = decl
)
or
exists(AccessorDeclaration decl |
getChild(scope, _) = decl and
pattern = decl.getName()
pattern = decl.getName() and
declaration = decl
)
or
exists(ImportDeclaration imprt |
getChild(scope, _) = imprt and
pattern = imprt.getPattern()
pattern = imprt.getPattern() and
declaration = imprt
)
or
exists(NamePattern p |
bindingContext(p, scope) and
bindingContext(p, scope, declaration) and
pattern = p.getIdentifier()
)
or
bindingContext(pattern.(Pattern).getEnclosingPattern(), scope)
bindingContext(pattern.(Pattern).getEnclosingPattern(), scope, declaration)
}

/**
Expand Down Expand Up @@ -284,7 +297,7 @@ private module LocalNameBindingInput implements LocalNameBindingInputSig<Locatio

predicate declInScope(AstNode definingNode, string name, AstNode scope) {
exists(AstNode pattern |
bindingContext(pattern, scope) and
bindingContext(pattern, scope, _) and
pattern.(Identifier).getValue() = name and
(
definingNode = getEnclosingOrPatternFromIdentifier(pattern)
Expand All @@ -303,6 +316,8 @@ private module LocalNameBindingInput implements LocalNameBindingInputSig<Locatio
predicate accessCand(AstNode n, string name) { n.(PotentialLocalNameAccess).getName() = name }
}

import LocalNameBindingInput

module LocalNameBindingOutput = LocalNameBinding<Location, LocalNameBindingInput>;

module Public {
Expand All @@ -319,6 +334,20 @@ module Public {
/** Gets the name of this local, as a string. */
string getName() { result = super.getName() }
}

/** An identifier that appears as the declaration site of a name, such as the `x` in `let x = 123`. */
class NameDeclaration extends Identifier {
NameDeclaration() { LocalNameBindingInput::bindingContext(this, _, _) }

/** Gets the statement-like node declaring this name, such as a `VariableDeclaration` or `CatchClause`. */
AstNode getDeclaration() { LocalNameBindingInput::bindingContext(this, _, result) }

/** Gets the name being declared. */
string getName() { result = this.getValue() }

/** Gets the representative for the local name introduced by this declaration. */
LocalName getLocalName() { result = this.(LocalNameBindingOutput::LocalAccess).getLocal() }
}
}

/**
Expand All @@ -342,13 +371,13 @@ class PotentialLocalNameAccess extends Identifier {
or
this = any(NamedTypeExpr e | not exists(e.getQualifier())).getName()
or
LocalNameBindingInput::bindingContext(this, _)
this instanceof NameDeclaration
}

LocalName getLocalName() { result = this.(LocalNameBindingOutput::LocalAccess).getLocal() }

string getName() { result = this.getValue() }

/** Holds if this is one of the declaration sites for a name, such as the `x` in `let x = 123`. */
predicate isDeclarationSite() { LocalNameBindingInput::bindingContext(this, _) }
predicate isDeclarationSite() { this instanceof NameDeclaration }
}
Loading