From 5d25f68c3cdd51040cd6fb3e2d5da734f60a4755 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 28 Sep 2026 19:37:19 +0200 Subject: [PATCH 1/3] Unified: Introduce `CallableEx` and `ParameterEx` --- .../unified/internal/AstPluginSwift.qll | 6 + .../codeql/unified/internal/CallableEx.qll | 344 ++++++++++++++++++ .../unified/internal/CallableExPlugin.qll | 56 +++ .../internal/CallableExPluginSwift.qll | 40 ++ .../constructors/constructors.swift | 45 +++ .../library-tests/constructors/test.expected | 32 ++ .../test/library-tests/constructors/test.ql | 29 ++ 7 files changed, 552 insertions(+) create mode 100644 unified/ql/lib/codeql/unified/internal/CallableEx.qll create mode 100644 unified/ql/lib/codeql/unified/internal/CallableExPlugin.qll create mode 100644 unified/ql/lib/codeql/unified/internal/CallableExPluginSwift.qll create mode 100644 unified/ql/test/library-tests/constructors/constructors.swift create mode 100644 unified/ql/test/library-tests/constructors/test.expected create mode 100644 unified/ql/test/library-tests/constructors/test.ql diff --git a/unified/ql/lib/codeql/unified/internal/AstPluginSwift.qll b/unified/ql/lib/codeql/unified/internal/AstPluginSwift.qll index 801055188218..e277e4fec074 100644 --- a/unified/ql/lib/codeql/unified/internal/AstPluginSwift.qll +++ b/unified/ql/lib/codeql/unified/internal/AstPluginSwift.qll @@ -10,6 +10,12 @@ private class AstPluginSwift extends AstPlugin { exists(f) and result = "func" } + bindingset[c] + override string getConstructorDeclarationKeyword(ConstructorDeclaration c) { + c.hasModifier(result) and + result = "convenience" + } + override string getClassLikeDeclarationKeyword(ClassLikeDeclaration cls) { cls.hasModifier(result) and result in ["class", "struct", "enum", "actor", "extension", "protocol"] diff --git a/unified/ql/lib/codeql/unified/internal/CallableEx.qll b/unified/ql/lib/codeql/unified/internal/CallableEx.qll new file mode 100644 index 000000000000..8cce03c6991c --- /dev/null +++ b/unified/ql/lib/codeql/unified/internal/CallableEx.qll @@ -0,0 +1,344 @@ +/** + * Provides logic for synthesizing callables and parameters. + */ + +private import unified +private import CallableExPlugin +private import NameBinding + +private predicate mayHaveImplicitConstructor(ClassLikeDeclaration cls, string name) { + mayHaveConstructor(cls, name) and + forall(ConstructorDeclaration c | c = cls.getAMember() | not constructorPreventsImplicit(c)) +} + +private predicate mayInheritConstructorFrom(ClassLikeDeclaration cls, ClassLikeDeclaration base) { + mayInheritConstructor(cls) and + mayHaveImplicitConstructor(cls, _) and + base.getNameNode() = getStaticBindingTargetFromRef(cls.getABaseType().getType()) and + mayHaveConstructor(base, _) +} + +private newtype TCallableEx = + TAstCallableEx(Callable c) or + TDefaultConstructor(ClassLikeDeclaration cls, string name) { + mayHaveImplicitConstructor(cls, name) and + forall(ClassLikeDeclaration base | mayInheritConstructorFrom(cls, base) | + not mayHaveConstructor(cls, _) + ) + } or + TInheritedConstructor(ClassLikeDeclaration cls, CallableEx baseCtor) { + exists(ClassLikeDeclaration baseClass | + mayInheritConstructorFrom(cls, baseClass) and + baseCtor.isConstructor(baseClass, true) + ) + } + +private newtype TParameterEx = + TAstParameter(Parameter p) or + TImplicitReceiverParameter(Callable c) { + exists(LocalVariable v | v.isImplicitReceiverParameter(c)) + } or + TDefaultConstructorParameter(ClassLikeDeclaration cls, int i, string name) { + defaultConstructorParameter(cls, i, name) + } or + TInheritedConstructorParameter(CallableEx ctor, CallableEx baseCtor, int i) { + ctor.isInheritedConstructor(_, baseCtor) and + exists(baseCtor.getParameter(i)) + } + +final class CallableEx = CallableExImpl; + +/** + * A callable from source code or a synthesized callable. + */ +abstract private class CallableExImpl extends TCallableEx { + /** + * Gets the source code callable that this entity represents, if any. + */ + Callable asCallable() { this = TAstCallableEx(result) } + + /** + * Holds if this entity represents a default constructor for `cls`, for example a + * parameterless Swift `class` constructor or a Swift `struct` constructor + * with parameters for relevant fields. + */ + predicate isDefaultConstructor(ClassLikeDeclaration cls) { this = TDefaultConstructor(cls, _) } + + /** + * Holds if this entity represents an inherited constructor for `cls` from the + * base constructor `baseCtor` (which may itself be inherited). + */ + predicate isInheritedConstructor(ClassLikeDeclaration cls, CallableEx baseCtor) { + this = TInheritedConstructor(cls, baseCtor) + } + + /** + * Holds if this entity represents a constructor for `cls`. + */ + predicate isConstructor(ClassLikeDeclaration cls, boolean inheritable) { + exists(ConstructorDeclaration c | + c = this.asCallable() and + c = cls.getAMember() and + if isInheritableMember(c) then inheritable = true else inheritable = false + ) + or + this.isDefaultConstructor(cls) and inheritable = true + or + this.isInheritedConstructor(cls, _) and inheritable = true + } + + /** + * Holds if this entity is a direct member of `cls`. + */ + predicate isMemberOf(ClassLikeDeclaration cls) { + this.asCallable() = cls.getAMember() + or + this.isConstructor(cls, _) + } + + /** Gets the `i`-th type parameter of this callable. */ + abstract TypeParameter getTypeParameter(int i); + + /** + * Gets the `i`-th parameter of this callable. Implicit receiver parameters are + * at index `0`, and all other parameters start at index `1`. + */ + abstract ParameterEx getParameter(int i); + + /** Gets a parameter of this callable. */ + final ParameterEx getAParameter() { result = this.getParameter(_) } + + /** Gets the declared return type of this callable, if any. */ + abstract Expr getReturnType(); + + /** Gets the body of this callable, if any. */ + abstract AstNode getBody(); + + /** Gets the name node of this callable, if any. */ + abstract Identifier getNameNode(); + + /** Gets the name of this callable. */ + abstract string getName(); + + /** Gets a textual representation of this callable. */ + abstract string toString(); + + /** Gets the location of this callable. */ + abstract Location getLocation(); +} + +private class AstCallableEx extends CallableExImpl, TAstCallableEx { + Callable c; + + AstCallableEx() { this = TAstCallableEx(c) } + + override TypeParameter getTypeParameter(int i) { + result = c.(FunctionDeclaration).getTypeParameter(i) + } + + override ParameterEx getParameter(int i) { + result.isImplicitReceiverParameter(c) and + i = 0 + or + exists(int j, Parameter p | + result.asParameter() = p and + i = j + 1 + | + p = c.(FunctionDeclaration).getParameter(j) + or + p = c.(ConstructorDeclaration).getParameter(j) + or + p = c.(FunctionExpr).getParameter(j) + or + p = c.(AccessorDeclaration).getParameter(j) + ) + } + + override Expr getReturnType() { + result = c.(FunctionDeclaration).getReturnType() + or + result = c.(FunctionExpr).getReturnType() + } + + override AstNode getBody() { result = c.getBody() } + + override Identifier getNameNode() { + result = c.(AccessorDeclaration).getNameNode() + or + result = c.(ConstructorDeclaration).getNameNode() + or + result = c.(FunctionDeclaration).getNameNode() + } + + override string getName() { + result = this.getNameNode().getValue() + or + c instanceof DestructorDeclaration and + result = "" + or + c instanceof InitializerDeclaration and + result = "" + } + + override string toString() { result = c.toString() } + + override Location getLocation() { result = c.getLocation() } +} + +private class DefaultConstructor extends CallableExImpl, TDefaultConstructor { + ClassLikeDeclaration c; + string name; + + DefaultConstructor() { this = TDefaultConstructor(c, name) } + + override TypeParameter getTypeParameter(int i) { none() } + + override ParameterEx getParameter(int i) { result = TDefaultConstructorParameter(c, i, _) } + + override Expr getReturnType() { none() } + + override AstNode getBody() { none() } + + override Identifier getNameNode() { none() } + + override string getName() { result = name } + + override string toString() { result = c.getName() + " [default constructor]" } + + override Location getLocation() { result = c.getLocation() } +} + +private class InheritedConstructor extends CallableExImpl, TInheritedConstructor { + ClassLikeDeclaration cls; + CallableEx baseCtor; + + InheritedConstructor() { this = TInheritedConstructor(cls, baseCtor) } + + override TypeParameter getTypeParameter(int i) { none() } + + override ParameterEx getParameter(int i) { + result.isInheritedConstructorParameter(this, baseCtor, i) + } + + override Expr getReturnType() { none() } + + override AstNode getBody() { none() } + + override Identifier getNameNode() { none() } + + override string getName() { result = baseCtor.getName() } + + override string toString() { + exists(ClassLikeDeclaration baseCls | + baseCtor.isMemberOf(baseCls) and + result = cls.getName() + " [inherited from " + baseCls.getName() + "]" + ) + } + + override Location getLocation() { result = cls.getLocation() } +} + +final class ParameterEx = ParameterExImpl; + +/** + * A parameter from source code or a synthesized parameter. + */ +abstract private class ParameterExImpl extends TParameterEx { + /** + * Gets the source code parameter that this entity represents, if any. + */ + Parameter asParameter() { this = TAstParameter(result) } + + /** + * Holds if this entity represents a regular parameter from source code. + */ + predicate isParameter() { exists(this.asParameter()) } + + /** + * Holds if this entity represents an implicit receiver parameter of `c`. + */ + predicate isImplicitReceiverParameter(Callable c) { this = TImplicitReceiverParameter(c) } + + /** + * Holds if this entity represents the `i`th parameter of the default + * constructor of `cls` with name `name`. + */ + predicate isDefaultConstructorParameter(ClassLikeDeclaration cls, int i, string name) { + this = TDefaultConstructorParameter(cls, i, name) + } + + /** + * Holds if this entity represents the `i`th parameter of the inherited constructor + * `ctor` where `baseCtor` is the base constructor. + */ + predicate isInheritedConstructorParameter(CallableEx ctor, CallableEx baseCtor, int i) { + this = TInheritedConstructorParameter(ctor, baseCtor, i) + } + + /** Gets the callable that this parameter belongs to. */ + CallableEx getCallable() { this = result.getAParameter() } + + /** Gets a textual representation of this parameter. */ + abstract string toString(); + + /** Gets the location of this parameter. */ + abstract Location getLocation(); +} + +private class AstParameterEx extends ParameterExImpl, TAstParameter { + Parameter p; + + AstParameterEx() { this = TAstParameter(p) } + + override string toString() { result = p.toString() } + + override Location getLocation() { result = p.getLocation() } +} + +private class ImplicitReceiverParameterEx extends ParameterExImpl, TImplicitReceiverParameter { + Callable c; + + ImplicitReceiverParameterEx() { this = TImplicitReceiverParameter(c) } + + override string toString() { + exists(LocalVariable v | + v.isImplicitReceiverParameter(c) and + result = v.getName() + ) + } + + override Location getLocation() { result = c.getLocation() } +} + +private class DefaultConstructorParameterEx extends ParameterExImpl, TDefaultConstructorParameter { + ClassLikeDeclaration c; + int i; + string name; + + DefaultConstructorParameterEx() { this = TDefaultConstructorParameter(c, i, name) } + + override string toString() { result = name + " [" + c.getName() + " default constructor]" } + + override Location getLocation() { result = c.getLocation() } +} + +private class InheritedConstructorParameterEx extends ParameterExImpl, + TInheritedConstructorParameter +{ + CallableEx ctor; + CallableEx baseCtor; + int i; + + InheritedConstructorParameterEx() { this = TInheritedConstructorParameter(ctor, baseCtor, i) } + + override string toString() { + exists(ParameterEx baseParam, ClassLikeDeclaration baseCls | + baseCtor.isMemberOf(baseCls) and + baseParam = baseCtor.getParameter(i) and + result = + "parameter " + i + " of " + ctor.getName() + " [inherited from " + baseCls.getName() + "]" + ) + } + + override Location getLocation() { result = ctor.getLocation() } +} diff --git a/unified/ql/lib/codeql/unified/internal/CallableExPlugin.qll b/unified/ql/lib/codeql/unified/internal/CallableExPlugin.qll new file mode 100644 index 000000000000..d6bc3c372f9f --- /dev/null +++ b/unified/ql/lib/codeql/unified/internal/CallableExPlugin.qll @@ -0,0 +1,56 @@ +private import unified +private import codeql.util.Unit + +private module Plugins { + private import codeql.unified.internal.CallableExPluginSwift +} + +/** Extension point for language-specific inputs to `CallableEx.qll`. */ +class CallableExPlugin extends Unit { + /** + * Holds if the class-like declaration `cls` may have a constructor with the + * given name. + */ + predicate mayHaveConstructor(ClassLikeDeclaration cls, string name) { none() } + + /** + * Holds if the presence of the constructor `c` prevents the class from having + * an implicit (default or inherited) constructor. + */ + bindingset[c] + predicate constructorPreventsImplicit(ConstructorDeclaration c) { none() } + + /** + * Holds if a default constructor for `cls` would need to have a parameter + * at index `i` with the given name. `i = 0` should be the implicit receiver + * parameter. + */ + bindingset[cls] + predicate defaultConstructorParameter(ClassLikeDeclaration cls, int i, string name) { none() } + + /** + * Holds if the class-like declaration `cls` may inherit a constructor from + * a base class, provided that no explicit constructor exists. + */ + bindingset[cls] + predicate mayInheritConstructor(ClassLikeDeclaration cls) { none() } +} + +predicate mayHaveConstructor(ClassLikeDeclaration cls, string name) { + any(CallableExPlugin p).mayHaveConstructor(cls, name) +} + +bindingset[c] +predicate constructorPreventsImplicit(ConstructorDeclaration c) { + any(CallableExPlugin p).constructorPreventsImplicit(c) +} + +bindingset[cls] +predicate defaultConstructorParameter(ClassLikeDeclaration cls, int i, string name) { + any(CallableExPlugin p).defaultConstructorParameter(cls, i, name) +} + +bindingset[cls] +predicate mayInheritConstructor(ClassLikeDeclaration cls) { + any(CallableExPlugin p).mayInheritConstructor(cls) +} diff --git a/unified/ql/lib/codeql/unified/internal/CallableExPluginSwift.qll b/unified/ql/lib/codeql/unified/internal/CallableExPluginSwift.qll new file mode 100644 index 000000000000..be3e330809ab --- /dev/null +++ b/unified/ql/lib/codeql/unified/internal/CallableExPluginSwift.qll @@ -0,0 +1,40 @@ +private import unified +private import codeql.unified.internal.CallableExPlugin + +class CallableExPluginSwift extends CallableExPlugin { + override predicate mayHaveConstructor(ClassLikeDeclaration cls, string name) { + cls.hasModifier(["actor", "class", "struct"]) and + name = "init" + } + + bindingset[c] + override predicate constructorPreventsImplicit(ConstructorDeclaration c) { + not c.hasModifier("convenience") + } + + bindingset[cls] + override predicate defaultConstructorParameter(ClassLikeDeclaration cls, int i, string name) { + i = 0 and + name = "self" + or + cls.hasModifier("struct") and + name = + rank[i](VariableDeclaration decl, int j, string s | + decl = cls.getMember(j) and + not decl.hasModifier("static") and + ( + // if `decl` has an initializer then this parameter has that initializer as a default value + decl.hasModifier("var") + or + decl.hasModifier("let") and + not exists(decl.getValue()) + ) and + s = decl.getPattern().(Identifier).getValue() + | + s order by j + ) + } + + bindingset[cls] + override predicate mayInheritConstructor(ClassLikeDeclaration cls) { any() } +} diff --git a/unified/ql/test/library-tests/constructors/constructors.swift b/unified/ql/test/library-tests/constructors/constructors.swift new file mode 100644 index 000000000000..6b15d47fa951 --- /dev/null +++ b/unified/ql/test/library-tests/constructors/constructors.swift @@ -0,0 +1,45 @@ +class C1 {} // implicit parameterless `init` + +class C2: C1 {} // inherits `init` + +class C3 { + init() {} +} + +class C4: C3 { + init(_ x: Int) { + super.init() + } + + override convenience init() { + self.init(0) + } +} + +class C5: C4 {} // inherits `init` and `convenience init` + +class C6 { + init(x: T1, y: T2) {} + + private init(s: String) {} + + convenience init(x: T1) { + fatalError("Convenience initializer not implemented") + } +} + +class C7: C6 {} // inherits `init(x: T4, y: T3)` and `convenience init(x: T4)` + +class C8: C7 { // inherits `init(x: String, y: Int)` + convenience init(x: String) { // todo: currently also inherits `convenience init` from C6 + self.init(x: x, y: 0) + } +} + +struct S1 { // implicit `init(f1: Int = 0, f2: String, f4: Double)` + var f1: Int = 0 + var f2: String + let f3: Bool = false + let f4: Double + static var f5: Float = 0.0 +} diff --git a/unified/ql/test/library-tests/constructors/test.expected b/unified/ql/test/library-tests/constructors/test.expected new file mode 100644 index 000000000000..c8b832985fcb --- /dev/null +++ b/unified/ql/test/library-tests/constructors/test.expected @@ -0,0 +1,32 @@ +callableExDefaultConstructor +| constructors.swift:1:1:1:11 | C1 [default constructor] | +| constructors.swift:39:1:45:1 | S1 [default constructor] | +callableExDefaultConstructorParameter +| constructors.swift:1:1:1:11 | C1 [default constructor] | 0 | constructors.swift:1:1:1:11 | self [C1 default constructor] | +| constructors.swift:39:1:45:1 | S1 [default constructor] | 0 | constructors.swift:39:1:45:1 | self [S1 default constructor] | +| constructors.swift:39:1:45:1 | S1 [default constructor] | 1 | constructors.swift:39:1:45:1 | f1 [S1 default constructor] | +| constructors.swift:39:1:45:1 | S1 [default constructor] | 2 | constructors.swift:39:1:45:1 | f2 [S1 default constructor] | +| constructors.swift:39:1:45:1 | S1 [default constructor] | 3 | constructors.swift:39:1:45:1 | f4 [S1 default constructor] | +callableExInheritedConstructor +| constructors.swift:3:1:3:15 | C2 [inherited from C1] | constructors.swift:1:1:1:11 | C1 [default constructor] | +| constructors.swift:19:1:19:15 | C5 [inherited from C4] | constructors.swift:10:5:12:5 | init | +| constructors.swift:19:1:19:15 | C5 [inherited from C4] | constructors.swift:14:5:16:5 | convenience init | +| constructors.swift:31:1:31:31 | C7 [inherited from C6] | constructors.swift:22:5:22:25 | init | +| constructors.swift:31:1:31:31 | C7 [inherited from C6] | constructors.swift:26:5:28:5 | convenience init | +| constructors.swift:33:1:37:1 | C8 [inherited from C7] | constructors.swift:31:1:31:31 | C7 [inherited from C6] | +| constructors.swift:33:1:37:1 | C8 [inherited from C7] | constructors.swift:31:1:31:31 | C7 [inherited from C6] | +callableExInheritedConstructorParameter +| constructors.swift:3:1:3:15 | C2 [inherited from C1] | 0 | constructors.swift:3:1:3:15 | parameter 0 of init [inherited from C1] | constructors.swift:1:1:1:11 | self [C1 default constructor] | +| constructors.swift:19:1:19:15 | C5 [inherited from C4] | 0 | constructors.swift:19:1:19:15 | parameter 0 of init [inherited from C4] | constructors.swift:10:5:12:5 | self | +| constructors.swift:19:1:19:15 | C5 [inherited from C4] | 0 | constructors.swift:19:1:19:15 | parameter 0 of init [inherited from C4] | constructors.swift:14:5:16:5 | self | +| constructors.swift:19:1:19:15 | C5 [inherited from C4] | 1 | constructors.swift:19:1:19:15 | parameter 1 of init [inherited from C4] | constructors.swift:10:10:10:17 | Parameter | +| constructors.swift:31:1:31:31 | C7 [inherited from C6] | 0 | constructors.swift:31:1:31:31 | parameter 0 of init [inherited from C6] | constructors.swift:22:5:22:25 | self | +| constructors.swift:31:1:31:31 | C7 [inherited from C6] | 0 | constructors.swift:31:1:31:31 | parameter 0 of init [inherited from C6] | constructors.swift:26:5:28:5 | self | +| constructors.swift:31:1:31:31 | C7 [inherited from C6] | 1 | constructors.swift:31:1:31:31 | parameter 1 of init [inherited from C6] | constructors.swift:22:10:22:14 | Parameter | +| constructors.swift:31:1:31:31 | C7 [inherited from C6] | 1 | constructors.swift:31:1:31:31 | parameter 1 of init [inherited from C6] | constructors.swift:26:22:26:26 | Parameter | +| constructors.swift:31:1:31:31 | C7 [inherited from C6] | 2 | constructors.swift:31:1:31:31 | parameter 2 of init [inherited from C6] | constructors.swift:22:17:22:21 | Parameter | +| constructors.swift:33:1:37:1 | C8 [inherited from C7] | 0 | constructors.swift:33:1:37:1 | parameter 0 of init [inherited from C7] | constructors.swift:22:5:22:25 | self | +| constructors.swift:33:1:37:1 | C8 [inherited from C7] | 0 | constructors.swift:33:1:37:1 | parameter 0 of init [inherited from C7] | constructors.swift:26:5:28:5 | self | +| constructors.swift:33:1:37:1 | C8 [inherited from C7] | 1 | constructors.swift:33:1:37:1 | parameter 1 of init [inherited from C7] | constructors.swift:22:10:22:14 | Parameter | +| constructors.swift:33:1:37:1 | C8 [inherited from C7] | 1 | constructors.swift:33:1:37:1 | parameter 1 of init [inherited from C7] | constructors.swift:26:22:26:26 | Parameter | +| constructors.swift:33:1:37:1 | C8 [inherited from C7] | 2 | constructors.swift:33:1:37:1 | parameter 2 of init [inherited from C7] | constructors.swift:22:17:22:21 | Parameter | diff --git a/unified/ql/test/library-tests/constructors/test.ql b/unified/ql/test/library-tests/constructors/test.ql new file mode 100644 index 000000000000..75ad9b235181 --- /dev/null +++ b/unified/ql/test/library-tests/constructors/test.ql @@ -0,0 +1,29 @@ +import unified +import codeql.unified.internal.CallableEx + +query predicate callableExDefaultConstructor(CallableEx c) { + c.isDefaultConstructor(any(ClassLikeDeclaration cls | cls.fromSource())) +} + +query predicate callableExDefaultConstructorParameter(CallableEx c, int i, ParameterEx p) { + callableExDefaultConstructor(c) and + p = c.getParameter(i) +} + +query predicate callableExInheritedConstructor(CallableEx c, CallableEx base) { + c.isInheritedConstructor(any(ClassLikeDeclaration cls | cls.fromSource()), base) +} + +query predicate callableExInheritedConstructorParameter( + CallableEx c, int i, ParameterEx p, ParameterEx rootParam +) { + exists(CallableEx base | + callableExInheritedConstructor(c, base) and + p = c.getParameter(i) + | + rootParam = base.getParameter(i) and + not rootParam.isInheritedConstructorParameter(_, _, _) + or + callableExInheritedConstructorParameter(base, i, _, rootParam) + ) +} From da6b3e54871f63de16aa9688b6abbc190b99b8ca Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Thu, 1 Oct 2026 11:55:09 +0200 Subject: [PATCH 2/3] Unified: Removed inherited constructors/parameters from CallableEx.qll --- .../codeql/unified/internal/CallableEx.qll | 79 ------------------- .../test/library-tests/constructors/test.ql | 18 ----- 2 files changed, 97 deletions(-) diff --git a/unified/ql/lib/codeql/unified/internal/CallableEx.qll b/unified/ql/lib/codeql/unified/internal/CallableEx.qll index 8cce03c6991c..3886440f17c1 100644 --- a/unified/ql/lib/codeql/unified/internal/CallableEx.qll +++ b/unified/ql/lib/codeql/unified/internal/CallableEx.qll @@ -25,12 +25,6 @@ private newtype TCallableEx = forall(ClassLikeDeclaration base | mayInheritConstructorFrom(cls, base) | not mayHaveConstructor(cls, _) ) - } or - TInheritedConstructor(ClassLikeDeclaration cls, CallableEx baseCtor) { - exists(ClassLikeDeclaration baseClass | - mayInheritConstructorFrom(cls, baseClass) and - baseCtor.isConstructor(baseClass, true) - ) } private newtype TParameterEx = @@ -40,10 +34,6 @@ private newtype TParameterEx = } or TDefaultConstructorParameter(ClassLikeDeclaration cls, int i, string name) { defaultConstructorParameter(cls, i, name) - } or - TInheritedConstructorParameter(CallableEx ctor, CallableEx baseCtor, int i) { - ctor.isInheritedConstructor(_, baseCtor) and - exists(baseCtor.getParameter(i)) } final class CallableEx = CallableExImpl; @@ -64,14 +54,6 @@ abstract private class CallableExImpl extends TCallableEx { */ predicate isDefaultConstructor(ClassLikeDeclaration cls) { this = TDefaultConstructor(cls, _) } - /** - * Holds if this entity represents an inherited constructor for `cls` from the - * base constructor `baseCtor` (which may itself be inherited). - */ - predicate isInheritedConstructor(ClassLikeDeclaration cls, CallableEx baseCtor) { - this = TInheritedConstructor(cls, baseCtor) - } - /** * Holds if this entity represents a constructor for `cls`. */ @@ -83,8 +65,6 @@ abstract private class CallableExImpl extends TCallableEx { ) or this.isDefaultConstructor(cls) and inheritable = true - or - this.isInheritedConstructor(cls, _) and inheritable = true } /** @@ -208,36 +188,6 @@ private class DefaultConstructor extends CallableExImpl, TDefaultConstructor { override Location getLocation() { result = c.getLocation() } } -private class InheritedConstructor extends CallableExImpl, TInheritedConstructor { - ClassLikeDeclaration cls; - CallableEx baseCtor; - - InheritedConstructor() { this = TInheritedConstructor(cls, baseCtor) } - - override TypeParameter getTypeParameter(int i) { none() } - - override ParameterEx getParameter(int i) { - result.isInheritedConstructorParameter(this, baseCtor, i) - } - - override Expr getReturnType() { none() } - - override AstNode getBody() { none() } - - override Identifier getNameNode() { none() } - - override string getName() { result = baseCtor.getName() } - - override string toString() { - exists(ClassLikeDeclaration baseCls | - baseCtor.isMemberOf(baseCls) and - result = cls.getName() + " [inherited from " + baseCls.getName() + "]" - ) - } - - override Location getLocation() { result = cls.getLocation() } -} - final class ParameterEx = ParameterExImpl; /** @@ -267,14 +217,6 @@ abstract private class ParameterExImpl extends TParameterEx { this = TDefaultConstructorParameter(cls, i, name) } - /** - * Holds if this entity represents the `i`th parameter of the inherited constructor - * `ctor` where `baseCtor` is the base constructor. - */ - predicate isInheritedConstructorParameter(CallableEx ctor, CallableEx baseCtor, int i) { - this = TInheritedConstructorParameter(ctor, baseCtor, i) - } - /** Gets the callable that this parameter belongs to. */ CallableEx getCallable() { this = result.getAParameter() } @@ -321,24 +263,3 @@ private class DefaultConstructorParameterEx extends ParameterExImpl, TDefaultCon override Location getLocation() { result = c.getLocation() } } - -private class InheritedConstructorParameterEx extends ParameterExImpl, - TInheritedConstructorParameter -{ - CallableEx ctor; - CallableEx baseCtor; - int i; - - InheritedConstructorParameterEx() { this = TInheritedConstructorParameter(ctor, baseCtor, i) } - - override string toString() { - exists(ParameterEx baseParam, ClassLikeDeclaration baseCls | - baseCtor.isMemberOf(baseCls) and - baseParam = baseCtor.getParameter(i) and - result = - "parameter " + i + " of " + ctor.getName() + " [inherited from " + baseCls.getName() + "]" - ) - } - - override Location getLocation() { result = ctor.getLocation() } -} diff --git a/unified/ql/test/library-tests/constructors/test.ql b/unified/ql/test/library-tests/constructors/test.ql index 75ad9b235181..f5400e6e1db5 100644 --- a/unified/ql/test/library-tests/constructors/test.ql +++ b/unified/ql/test/library-tests/constructors/test.ql @@ -9,21 +9,3 @@ query predicate callableExDefaultConstructorParameter(CallableEx c, int i, Param callableExDefaultConstructor(c) and p = c.getParameter(i) } - -query predicate callableExInheritedConstructor(CallableEx c, CallableEx base) { - c.isInheritedConstructor(any(ClassLikeDeclaration cls | cls.fromSource()), base) -} - -query predicate callableExInheritedConstructorParameter( - CallableEx c, int i, ParameterEx p, ParameterEx rootParam -) { - exists(CallableEx base | - callableExInheritedConstructor(c, base) and - p = c.getParameter(i) - | - rootParam = base.getParameter(i) and - not rootParam.isInheritedConstructorParameter(_, _, _) - or - callableExInheritedConstructorParameter(base, i, _, rootParam) - ) -} From 04fec213a5789384ae071477b162ba15952abf34 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Thu, 1 Oct 2026 12:27:26 +0200 Subject: [PATCH 3/3] Unified: Do not generate inherited constructors after all --- .../codeql/unified/internal/CallableEx.qll | 20 ++++------------ .../unified/internal/CallableExPlugin.qll | 24 ------------------- .../internal/CallableExPluginSwift.qll | 8 ------- .../constructors/constructors.swift | 2 +- .../library-tests/constructors/test.expected | 23 ------------------ 5 files changed, 6 insertions(+), 71 deletions(-) diff --git a/unified/ql/lib/codeql/unified/internal/CallableEx.qll b/unified/ql/lib/codeql/unified/internal/CallableEx.qll index 3886440f17c1..4e6a9607b31d 100644 --- a/unified/ql/lib/codeql/unified/internal/CallableEx.qll +++ b/unified/ql/lib/codeql/unified/internal/CallableEx.qll @@ -6,24 +6,14 @@ private import unified private import CallableExPlugin private import NameBinding -private predicate mayHaveImplicitConstructor(ClassLikeDeclaration cls, string name) { - mayHaveConstructor(cls, name) and - forall(ConstructorDeclaration c | c = cls.getAMember() | not constructorPreventsImplicit(c)) -} - -private predicate mayInheritConstructorFrom(ClassLikeDeclaration cls, ClassLikeDeclaration base) { - mayInheritConstructor(cls) and - mayHaveImplicitConstructor(cls, _) and - base.getNameNode() = getStaticBindingTargetFromRef(cls.getABaseType().getType()) and - mayHaveConstructor(base, _) -} - private newtype TCallableEx = TAstCallableEx(Callable c) or TDefaultConstructor(ClassLikeDeclaration cls, string name) { - mayHaveImplicitConstructor(cls, name) and - forall(ClassLikeDeclaration base | mayInheritConstructorFrom(cls, base) | - not mayHaveConstructor(cls, _) + mayHaveConstructor(cls, name) and + not cls.getAMember() instanceof ConstructorDeclaration and + not exists(ClassLikeDeclaration base | + base.getNameNode() = getStaticBindingTargetFromRef(cls.getABaseType().getType()) and + mayHaveConstructor(base, _) ) } diff --git a/unified/ql/lib/codeql/unified/internal/CallableExPlugin.qll b/unified/ql/lib/codeql/unified/internal/CallableExPlugin.qll index d6bc3c372f9f..91d0c16c3dbc 100644 --- a/unified/ql/lib/codeql/unified/internal/CallableExPlugin.qll +++ b/unified/ql/lib/codeql/unified/internal/CallableExPlugin.qll @@ -13,13 +13,6 @@ class CallableExPlugin extends Unit { */ predicate mayHaveConstructor(ClassLikeDeclaration cls, string name) { none() } - /** - * Holds if the presence of the constructor `c` prevents the class from having - * an implicit (default or inherited) constructor. - */ - bindingset[c] - predicate constructorPreventsImplicit(ConstructorDeclaration c) { none() } - /** * Holds if a default constructor for `cls` would need to have a parameter * at index `i` with the given name. `i = 0` should be the implicit receiver @@ -27,30 +20,13 @@ class CallableExPlugin extends Unit { */ bindingset[cls] predicate defaultConstructorParameter(ClassLikeDeclaration cls, int i, string name) { none() } - - /** - * Holds if the class-like declaration `cls` may inherit a constructor from - * a base class, provided that no explicit constructor exists. - */ - bindingset[cls] - predicate mayInheritConstructor(ClassLikeDeclaration cls) { none() } } predicate mayHaveConstructor(ClassLikeDeclaration cls, string name) { any(CallableExPlugin p).mayHaveConstructor(cls, name) } -bindingset[c] -predicate constructorPreventsImplicit(ConstructorDeclaration c) { - any(CallableExPlugin p).constructorPreventsImplicit(c) -} - bindingset[cls] predicate defaultConstructorParameter(ClassLikeDeclaration cls, int i, string name) { any(CallableExPlugin p).defaultConstructorParameter(cls, i, name) } - -bindingset[cls] -predicate mayInheritConstructor(ClassLikeDeclaration cls) { - any(CallableExPlugin p).mayInheritConstructor(cls) -} diff --git a/unified/ql/lib/codeql/unified/internal/CallableExPluginSwift.qll b/unified/ql/lib/codeql/unified/internal/CallableExPluginSwift.qll index be3e330809ab..56225ff62711 100644 --- a/unified/ql/lib/codeql/unified/internal/CallableExPluginSwift.qll +++ b/unified/ql/lib/codeql/unified/internal/CallableExPluginSwift.qll @@ -7,11 +7,6 @@ class CallableExPluginSwift extends CallableExPlugin { name = "init" } - bindingset[c] - override predicate constructorPreventsImplicit(ConstructorDeclaration c) { - not c.hasModifier("convenience") - } - bindingset[cls] override predicate defaultConstructorParameter(ClassLikeDeclaration cls, int i, string name) { i = 0 and @@ -34,7 +29,4 @@ class CallableExPluginSwift extends CallableExPlugin { s order by j ) } - - bindingset[cls] - override predicate mayInheritConstructor(ClassLikeDeclaration cls) { any() } } diff --git a/unified/ql/test/library-tests/constructors/constructors.swift b/unified/ql/test/library-tests/constructors/constructors.swift index 6b15d47fa951..2638bbd91d9d 100644 --- a/unified/ql/test/library-tests/constructors/constructors.swift +++ b/unified/ql/test/library-tests/constructors/constructors.swift @@ -31,7 +31,7 @@ class C6 { class C7: C6 {} // inherits `init(x: T4, y: T3)` and `convenience init(x: T4)` class C8: C7 { // inherits `init(x: String, y: Int)` - convenience init(x: String) { // todo: currently also inherits `convenience init` from C6 + convenience init(x: String) { self.init(x: x, y: 0) } } diff --git a/unified/ql/test/library-tests/constructors/test.expected b/unified/ql/test/library-tests/constructors/test.expected index c8b832985fcb..79070fac587c 100644 --- a/unified/ql/test/library-tests/constructors/test.expected +++ b/unified/ql/test/library-tests/constructors/test.expected @@ -7,26 +7,3 @@ callableExDefaultConstructorParameter | constructors.swift:39:1:45:1 | S1 [default constructor] | 1 | constructors.swift:39:1:45:1 | f1 [S1 default constructor] | | constructors.swift:39:1:45:1 | S1 [default constructor] | 2 | constructors.swift:39:1:45:1 | f2 [S1 default constructor] | | constructors.swift:39:1:45:1 | S1 [default constructor] | 3 | constructors.swift:39:1:45:1 | f4 [S1 default constructor] | -callableExInheritedConstructor -| constructors.swift:3:1:3:15 | C2 [inherited from C1] | constructors.swift:1:1:1:11 | C1 [default constructor] | -| constructors.swift:19:1:19:15 | C5 [inherited from C4] | constructors.swift:10:5:12:5 | init | -| constructors.swift:19:1:19:15 | C5 [inherited from C4] | constructors.swift:14:5:16:5 | convenience init | -| constructors.swift:31:1:31:31 | C7 [inherited from C6] | constructors.swift:22:5:22:25 | init | -| constructors.swift:31:1:31:31 | C7 [inherited from C6] | constructors.swift:26:5:28:5 | convenience init | -| constructors.swift:33:1:37:1 | C8 [inherited from C7] | constructors.swift:31:1:31:31 | C7 [inherited from C6] | -| constructors.swift:33:1:37:1 | C8 [inherited from C7] | constructors.swift:31:1:31:31 | C7 [inherited from C6] | -callableExInheritedConstructorParameter -| constructors.swift:3:1:3:15 | C2 [inherited from C1] | 0 | constructors.swift:3:1:3:15 | parameter 0 of init [inherited from C1] | constructors.swift:1:1:1:11 | self [C1 default constructor] | -| constructors.swift:19:1:19:15 | C5 [inherited from C4] | 0 | constructors.swift:19:1:19:15 | parameter 0 of init [inherited from C4] | constructors.swift:10:5:12:5 | self | -| constructors.swift:19:1:19:15 | C5 [inherited from C4] | 0 | constructors.swift:19:1:19:15 | parameter 0 of init [inherited from C4] | constructors.swift:14:5:16:5 | self | -| constructors.swift:19:1:19:15 | C5 [inherited from C4] | 1 | constructors.swift:19:1:19:15 | parameter 1 of init [inherited from C4] | constructors.swift:10:10:10:17 | Parameter | -| constructors.swift:31:1:31:31 | C7 [inherited from C6] | 0 | constructors.swift:31:1:31:31 | parameter 0 of init [inherited from C6] | constructors.swift:22:5:22:25 | self | -| constructors.swift:31:1:31:31 | C7 [inherited from C6] | 0 | constructors.swift:31:1:31:31 | parameter 0 of init [inherited from C6] | constructors.swift:26:5:28:5 | self | -| constructors.swift:31:1:31:31 | C7 [inherited from C6] | 1 | constructors.swift:31:1:31:31 | parameter 1 of init [inherited from C6] | constructors.swift:22:10:22:14 | Parameter | -| constructors.swift:31:1:31:31 | C7 [inherited from C6] | 1 | constructors.swift:31:1:31:31 | parameter 1 of init [inherited from C6] | constructors.swift:26:22:26:26 | Parameter | -| constructors.swift:31:1:31:31 | C7 [inherited from C6] | 2 | constructors.swift:31:1:31:31 | parameter 2 of init [inherited from C6] | constructors.swift:22:17:22:21 | Parameter | -| constructors.swift:33:1:37:1 | C8 [inherited from C7] | 0 | constructors.swift:33:1:37:1 | parameter 0 of init [inherited from C7] | constructors.swift:22:5:22:25 | self | -| constructors.swift:33:1:37:1 | C8 [inherited from C7] | 0 | constructors.swift:33:1:37:1 | parameter 0 of init [inherited from C7] | constructors.swift:26:5:28:5 | self | -| constructors.swift:33:1:37:1 | C8 [inherited from C7] | 1 | constructors.swift:33:1:37:1 | parameter 1 of init [inherited from C7] | constructors.swift:22:10:22:14 | Parameter | -| constructors.swift:33:1:37:1 | C8 [inherited from C7] | 1 | constructors.swift:33:1:37:1 | parameter 1 of init [inherited from C7] | constructors.swift:26:22:26:26 | Parameter | -| constructors.swift:33:1:37:1 | C8 [inherited from C7] | 2 | constructors.swift:33:1:37:1 | parameter 2 of init [inherited from C7] | constructors.swift:22:17:22:21 | Parameter |