From e7c8469cf76e5511d75fbb8c5bf57d0f042194ff Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 11 Sep 2026 14:06:51 +0200 Subject: [PATCH 1/4] Unified: Add tests for explicit `self`/`Self` qualifiers --- .../explicit-instance-field-access.swift | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 unified/ql/test/library-tests/static-name-binding/explicit-instance-field-access.swift diff --git a/unified/ql/test/library-tests/static-name-binding/explicit-instance-field-access.swift b/unified/ql/test/library-tests/static-name-binding/explicit-instance-field-access.swift new file mode 100644 index 000000000000..dd150fe98e16 --- /dev/null +++ b/unified/ql/test/library-tests/static-name-binding/explicit-instance-field-access.swift @@ -0,0 +1,47 @@ +private class A { + let x = 123 // name=A.instance.x + + func getX() { + return self.x // not handled by static name binding + } + + static let y = 456 // name=A.type.y + + func getY1() { + return Self.y // $ MISSING: access=A access=A.type.y + } + + static func getY2() { + return self.y // $ not handled by static name binding + } + + class func z() -> Int { // name=A.type.z + return 789 + } + + class func getZ() { + return self.z // $ not handled by static name binding + } +} + +private class B : A { // $ access=A + func getX2() { + return self.x // not handled by static name binding + } + + func getY3() { + return Self.y // $ MISSING: access=B access=A.type.y + } + + static func getY4() { + return self.y // $ not handled by static name binding + } + + class func z() -> Int { // name=B.type.z + return 789 + } + + class func getZ2() { + return self.z // $ not handled by static name binding + } +} From 2d10f98b6099cec2d4dce6a2adfd14c12e3b1f69 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 11 Sep 2026 14:07:12 +0200 Subject: [PATCH 2/4] Unified: Resolve `Self` in static name binding --- .../ql/lib/codeql/unified/internal/NameBindingPlugin.qll | 6 ++++++ .../lib/codeql/unified/internal/NameBindingPluginSwift.qll | 2 ++ .../ql/lib/codeql/unified/internal/StaticNameBinding.qll | 6 ++++++ .../explicit-instance-field-access.swift | 4 ++-- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/unified/ql/lib/codeql/unified/internal/NameBindingPlugin.qll b/unified/ql/lib/codeql/unified/internal/NameBindingPlugin.qll index c3173614feea..f8367f4e67f6 100644 --- a/unified/ql/lib/codeql/unified/internal/NameBindingPlugin.qll +++ b/unified/ql/lib/codeql/unified/internal/NameBindingPlugin.qll @@ -42,6 +42,12 @@ class NameBindingPlugin extends Unit { /** Gets the name of the implicit receiver parameter in `callable`, if it has one. */ string getImplicitReceiverParameterName(Callable callable) { none() } + + /** + * Gets the name through which static members of the enclosing class can be + * accessed, for example `Self` in Swift. + */ + string getStaticSelfName() { none() } } /** Holds if `member` is an instance member. */ diff --git a/unified/ql/lib/codeql/unified/internal/NameBindingPluginSwift.qll b/unified/ql/lib/codeql/unified/internal/NameBindingPluginSwift.qll index 8827d25ea453..5b5e4475bc06 100644 --- a/unified/ql/lib/codeql/unified/internal/NameBindingPluginSwift.qll +++ b/unified/ql/lib/codeql/unified/internal/NameBindingPluginSwift.qll @@ -44,6 +44,8 @@ class NameBindingPluginSwift extends NameBindingPlugin { callable = any(ClassLikeDeclaration cls).getAMember() and result = "self" } + + override string getStaticSelfName() { result = "Self" } } /** Holds if `node` is in a context where a bare name node should be seen as a reference rather than a declaration. */ diff --git a/unified/ql/lib/codeql/unified/internal/StaticNameBinding.qll b/unified/ql/lib/codeql/unified/internal/StaticNameBinding.qll index 230608b682f8..e53e43a92e87 100644 --- a/unified/ql/lib/codeql/unified/internal/StaticNameBinding.qll +++ b/unified/ql/lib/codeql/unified/internal/StaticNameBinding.qll @@ -210,6 +210,12 @@ predicate storeStep(NameBindingNode node1, string name, NameBindingNode node2) { ) or FolderHeuristic::storeStep(node1, name, node2) + or + exists(ClassLikeDeclaration cls | + name = any(NameBindingPlugin p).getStaticSelfName() and + node1.isIdentifier(cls.getNameNode()) and + node2.isStaticMemberNamespace(cls) + ) } predicate valueStep(NameBindingNode node1, NameBindingNode node2) { diff --git a/unified/ql/test/library-tests/static-name-binding/explicit-instance-field-access.swift b/unified/ql/test/library-tests/static-name-binding/explicit-instance-field-access.swift index dd150fe98e16..1113e2a51226 100644 --- a/unified/ql/test/library-tests/static-name-binding/explicit-instance-field-access.swift +++ b/unified/ql/test/library-tests/static-name-binding/explicit-instance-field-access.swift @@ -8,7 +8,7 @@ private class A { static let y = 456 // name=A.type.y func getY1() { - return Self.y // $ MISSING: access=A access=A.type.y + return Self.y // $ access=A access=A.type.y } static func getY2() { @@ -30,7 +30,7 @@ private class B : A { // $ access=A } func getY3() { - return Self.y // $ MISSING: access=B access=A.type.y + return Self.y // $ access=B access=A.type.y } static func getY4() { From 7926c7d5c6988fedcbb9aeec0c896159ee2f63e5 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 11 Sep 2026 14:59:35 +0200 Subject: [PATCH 3/4] Unified: Add test for `Self` access in nested class --- .../explicit-instance-field-access.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/unified/ql/test/library-tests/static-name-binding/explicit-instance-field-access.swift b/unified/ql/test/library-tests/static-name-binding/explicit-instance-field-access.swift index 1113e2a51226..732e8820533b 100644 --- a/unified/ql/test/library-tests/static-name-binding/explicit-instance-field-access.swift +++ b/unified/ql/test/library-tests/static-name-binding/explicit-instance-field-access.swift @@ -45,3 +45,11 @@ private class B : A { // $ access=A return self.z // $ not handled by static name binding } } + +private class C { + static let x = 1 + class D { + static let x = 2 + static let foo = Self.x // $ access=C.D access=C.D.x $ SPURIOUS: access=C.x + } +} From c0bc400e5e1d632d422798b9b4834bbd2ccc145f Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 11 Sep 2026 15:45:05 +0200 Subject: [PATCH 4/4] Address review comments --- .../lib/codeql/unified/internal/LocalNameBinding.qll | 6 ++++++ .../codeql/unified/internal/NameBindingPlugin.qll | 5 +++-- .../unified/internal/NameBindingPluginSwift.qll | 3 ++- .../codeql/unified/internal/StaticNameBinding.qll | 12 ++++++------ .../explicit-instance-field-access.swift | 2 +- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/unified/ql/lib/codeql/unified/internal/LocalNameBinding.qll b/unified/ql/lib/codeql/unified/internal/LocalNameBinding.qll index a944db724c5a..0844fe64bed9 100644 --- a/unified/ql/lib/codeql/unified/internal/LocalNameBinding.qll +++ b/unified/ql/lib/codeql/unified/internal/LocalNameBinding.qll @@ -333,6 +333,12 @@ private module LocalNameBindingInput implements LocalNameBindingInputSig