From d0d2095395dab4844e444179dc25cafe7600424c Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Thu, 5 Nov 2026 22:07:50 +0100 Subject: [PATCH 1/2] Unified: Add name binding test --- .../static-name-binding/test.expected | 36 +++++++++++++++++++ .../library-tests/static-name-binding/test.ql | 6 ++++ .../unqualified-access.swift | 25 ++++++++----- 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/unified/ql/test/library-tests/static-name-binding/test.expected b/unified/ql/test/library-tests/static-name-binding/test.expected index e69de29bb2d1..81067a381f70 100644 --- a/unified/ql/test/library-tests/static-name-binding/test.expected +++ b/unified/ql/test/library-tests/static-name-binding/test.expected @@ -0,0 +1,36 @@ +testFailures +unqualifiedMemberAccess +| extensions.swift:3:9:3:17 | ownMethod | extensions.swift:1:1:7:1 | class A | +| extensions.swift:4:9:4:24 | extensionMethod1 | extensions.swift:1:1:7:1 | class A | +| extensions.swift:5:9:5:24 | extensionMethod2 | extensions.swift:1:1:7:1 | class A | +| extensions.swift:11:9:11:17 | ownMethod | extensions.swift:1:1:7:1 | class A | +| extensions.swift:12:9:12:24 | extensionMethod1 | extensions.swift:9:1:15:1 | extension A | +| extensions.swift:13:9:13:24 | extensionMethod2 | extensions.swift:1:1:7:1 | class A | +| extensions.swift:19:9:19:17 | ownMethod | extensions.swift:1:1:7:1 | class A | +| extensions.swift:20:9:20:24 | extensionMethod1 | extensions.swift:1:1:7:1 | class A | +| extensions.swift:21:9:21:24 | extensionMethod2 | extensions.swift:17:1:23:1 | extension A | +| extensions.swift:34:20:34:20 | C | extensions.swift:25:1:26:1 | class B | +| extensions.swift:35:17:35:17 | D | extensions.swift:34:5:36:5 | class B.Nested | +| extensions.swift:51:9:51:18 | baseMethod | extensions.swift:49:1:58:1 | class X | +| extensions.swift:52:9:52:24 | baseMethodNoImpl | extensions.swift:49:1:58:1 | class X | +| extensions.swift:53:9:53:21 | baseMethodExt | extensions.swift:49:1:58:1 | class X | +| extensions.swift:56:9:56:29 | baseMethodDefaultImpl | extensions.swift:49:1:58:1 | class X | +| extensions.swift:66:9:66:18 | baseMethod | extensions.swift:64:1:69:1 | class Y | +| extensions.swift:67:9:67:29 | baseMethodDefaultImpl | extensions.swift:64:1:69:1 | class Y | +| implicit-instance-field-access.swift:5:16:5:16 | x | implicit-instance-field-access.swift:1:1:7:1 | class A | +| implicit-instance-field-access.swift:11:16:11:16 | x | implicit-instance-field-access.swift:9:1:13:1 | class B | +| not-a-package/Main/Runner.swift:9:14:9:19 | Nested | not-a-package/Main/Runner.swift:8:1:10:1 | class MyDriver | +| not-a-package/Mock/Runner.swift:9:14:9:19 | Nested | not-a-package/Mock/Runner.swift:8:1:10:1 | class MyDriver | +| package1/Sources/Target1/File1.swift:6:9:6:30 | target2ExtensionMethod | package1/Sources/Target1/File1.swift:4:1:8:1 | class ScopedExtensionTarget | +| test.swift:8:24:8:24 | B | test.swift:3:1:11:1 | class A | +| test.swift:37:13:37:13 | A | test.swift:31:1:39:1 | class E | +| test.swift:38:13:38:13 | A | test.swift:31:1:39:1 | class E | +| unqualified-access.swift:8:13:8:13 | B | unqualified-access.swift:7:1:20:1 | class ASub | +| unqualified-access.swift:9:13:9:13 | B | unqualified-access.swift:7:1:20:1 | class ASub | +| unqualified-access.swift:11:17:11:17 | B | unqualified-access.swift:7:1:20:1 | class ASub | +| unqualified-access.swift:12:17:12:17 | B | unqualified-access.swift:7:1:20:1 | class ASub | +| unqualified-access.swift:13:17:13:17 | C | unqualified-access.swift:11:5:14:5 | class ASub.BSub | +| unqualified-access.swift:16:18:16:18 | B | unqualified-access.swift:7:1:20:1 | class ASub | +| unqualified-access.swift:18:17:18:17 | C | unqualified-access.swift:16:5:19:5 | class ASub.BSub2 | +| unqualified-access.swift:24:16:24:19 | Leaf | unqualified-access.swift:23:5:26:5 | class Node.Leaf | +| unqualified-access.swift:24:23:24:26 | Leaf | unqualified-access.swift:23:5:26:5 | class Node.Leaf | diff --git a/unified/ql/test/library-tests/static-name-binding/test.ql b/unified/ql/test/library-tests/static-name-binding/test.ql index 3f9936d79efd..8149e40e299b 100644 --- a/unified/ql/test/library-tests/static-name-binding/test.ql +++ b/unified/ql/test/library-tests/static-name-binding/test.ql @@ -19,3 +19,9 @@ module StaticDeclAccess implements TestSig { } import MakeTest + +query predicate unqualifiedMemberAccess( + UnqualifiedMemberAccess uma, ClassLikeDeclaration accessingClass +) { + accessingClass = uma.getAccessingClass() +} diff --git a/unified/ql/test/library-tests/static-name-binding/unqualified-access.swift b/unified/ql/test/library-tests/static-name-binding/unqualified-access.swift index 732e65563b3b..c88072ecd44a 100644 --- a/unified/ql/test/library-tests/static-name-binding/unqualified-access.swift +++ b/unified/ql/test/library-tests/static-name-binding/unqualified-access.swift @@ -4,17 +4,24 @@ class A { } } -class ASub : A { // $ access=A - let x1: B = nil; // $ access=A.B - let x2: B.C = nil; // $ access=A.B access=A.B.C +class ASub: A { // $ access=A + let x1: B = nil // $ access=A.B + let x2: B.C = nil // $ access=A.B access=A.B.C - class BSub : B { // $ access=A.B - let x3: B = nil; // $ access=A.B - let x4: C = nil; // $ access=A.B.C + class BSub: B { // $ access=A.B + let x3: B = nil // $ access=A.B + let x4: C = nil // $ access=A.B.C } - class BSub2 : B { // $ access=A.B - class C {} // shadow the inherited C - let x5: C = nil; // $ access=ASub.BSub2.C + class BSub2: B { // $ access=A.B + class C {} // shadow the inherited C + let x5: C = nil // $ access=ASub.BSub2.C + } +} + +class Node { + class Leaf: Node { // $ access=Node + let x: Leaf = Leaf() // $ access=Node.Leaf + let y: Node.Leaf.Leaf.Leaf.Leaf = nil // $ access=Node access=Node.Leaf } } From c68de99562d6d70faca65dd98ce7ce017a2f2f2b Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 6 Nov 2026 09:19:11 +0100 Subject: [PATCH 2/2] Unified: Adjust `UnqualifiedMemberAccess.getAccessingClass()` --- .../unified/internal/StaticNameBinding.qll | 61 +++++++++++-------- .../static-name-binding/test.expected | 4 +- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/unified/ql/lib/codeql/unified/internal/StaticNameBinding.qll b/unified/ql/lib/codeql/unified/internal/StaticNameBinding.qll index a559dab61029..2bccff3e2663 100644 --- a/unified/ql/lib/codeql/unified/internal/StaticNameBinding.qll +++ b/unified/ql/lib/codeql/unified/internal/StaticNameBinding.qll @@ -692,6 +692,24 @@ private ClassLikeDeclaration tryResolveExtensionTarget(ClassLikeDeclaration cls) result = cls } +private predicate unqualifiedMemberAccessLocalScopingCand( + PotentialLocalNameAccess access, boolean instanceAccess, NameBinding target, + ClassLikeDeclaration accessingClass +) { + not access instanceof NameBinding and + exists(LocalName local | + target.getLocalName() = local and + access.getLocalName() = local and + target.getDeclaration() = accessingClass.getAMember() + | + instanceAccess = true and + isInstanceMember(target.getDeclaration()) + or + instanceAccess = false and + isStaticMember(target.getDeclaration()) + ) +} + /** * Holds if `access` may resolve to `target` through the enclosing `accessingClass`. * @@ -702,35 +720,24 @@ private predicate unqualifiedMemberAccessCand( PotentialLocalNameAccess access, boolean instanceAccess, NameBinding target, ClassLikeDeclaration accessingClass ) { + // Resolved by local scoping + unqualifiedMemberAccessLocalScopingCand(access, instanceAccess, target, accessingClass) + or + // Resolved in an uncertain scope not access instanceof NameBinding and - ( - // Resolved by local scoping - exists(LocalName local | - target.getLocalName() = local and - access.getLocalName() = local and - target.getDeclaration() = accessingClass.getAMember() - | - instanceAccess = true and - isInstanceMember(target.getDeclaration()) - or - instanceAccess = false and - isStaticMember(target.getDeclaration()) - ) + not unqualifiedMemberAccessLocalScopingCand(access, instanceAccess, target, _) and + exists(NamespaceNode namespace, string name | + name = access.getName() and + accessingClass = + tryResolveExtensionTarget(LocalNameBindingOutput::getAnUncertainScope(access, name)) + | + instanceAccess = true and + namespace.isInstanceMemberNamespace(accessingClass) and + namespace.getMember(name).isIdentifier(target) or - // Resolved in an uncertain scope - exists(NamespaceNode namespace, string name | - name = access.getName() and - accessingClass = - tryResolveExtensionTarget(LocalNameBindingOutput::getAnUncertainScope(access, name)) - | - instanceAccess = true and - namespace.isInstanceMemberNamespace(accessingClass) and - namespace.getMember(name).isIdentifier(target) - or - instanceAccess = false and - namespace.isStaticMemberNamespace(accessingClass) and - namespace.getMember(name).isIdentifier(target) - ) + instanceAccess = false and + namespace.isStaticMemberNamespace(accessingClass) and + namespace.getMember(name).isIdentifier(target) ) } diff --git a/unified/ql/test/library-tests/static-name-binding/test.expected b/unified/ql/test/library-tests/static-name-binding/test.expected index 81067a381f70..d7efa5cca31f 100644 --- a/unified/ql/test/library-tests/static-name-binding/test.expected +++ b/unified/ql/test/library-tests/static-name-binding/test.expected @@ -32,5 +32,5 @@ unqualifiedMemberAccess | unqualified-access.swift:13:17:13:17 | C | unqualified-access.swift:11:5:14:5 | class ASub.BSub | | unqualified-access.swift:16:18:16:18 | B | unqualified-access.swift:7:1:20:1 | class ASub | | unqualified-access.swift:18:17:18:17 | C | unqualified-access.swift:16:5:19:5 | class ASub.BSub2 | -| unqualified-access.swift:24:16:24:19 | Leaf | unqualified-access.swift:23:5:26:5 | class Node.Leaf | -| unqualified-access.swift:24:23:24:26 | Leaf | unqualified-access.swift:23:5:26:5 | class Node.Leaf | +| unqualified-access.swift:24:16:24:19 | Leaf | unqualified-access.swift:22:1:27:1 | class Node | +| unqualified-access.swift:24:23:24:26 | Leaf | unqualified-access.swift:22:1:27:1 | class Node |