Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions unified/ql/lib/codeql/unified/internal/AstPluginSwift.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
255 changes: 255 additions & 0 deletions unified/ql/lib/codeql/unified/internal/CallableEx.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
/**
* Provides logic for synthesizing callables and parameters.
*/

private import unified
private import CallableExPlugin
private import NameBinding

private newtype TCallableEx =
TAstCallableEx(Callable c) or
TDefaultConstructor(ClassLikeDeclaration cls, string name) {
mayHaveConstructor(cls, name) and
not cls.getAMember() instanceof ConstructorDeclaration and
not exists(ClassLikeDeclaration base |
base.getNameNode() = getStaticBindingTargetFromRef(cls.getABaseType().getType()) and
mayHaveConstructor(base, _)
)
}

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)
}

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 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
}

/**
* 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 = "<destructor>"
or
c instanceof InitializerDeclaration and
result = "<initializer>"
}

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() }
}

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)
}

/** 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() }
}
32 changes: 32 additions & 0 deletions unified/ql/lib/codeql/unified/internal/CallableExPlugin.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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 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() }
}

predicate mayHaveConstructor(ClassLikeDeclaration cls, string name) {
any(CallableExPlugin p).mayHaveConstructor(cls, name)
}

bindingset[cls]
predicate defaultConstructorParameter(ClassLikeDeclaration cls, int i, string name) {
any(CallableExPlugin p).defaultConstructorParameter(cls, i, name)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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[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
)
}
}
45 changes: 45 additions & 0 deletions unified/ql/test/library-tests/constructors/constructors.swift
Original file line number Diff line number Diff line change
@@ -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<T1, T2> {
init(x: T1, y: T2) {}

private init(s: String) {}

convenience init(x: T1) {
fatalError("Convenience initializer not implemented")
}
}

class C7<T3, T4>: C6<T4, T3> {} // inherits `init(x: T4, y: T3)` and `convenience init(x: T4)`

class C8: C7<Int, String> { // inherits `init(x: String, y: Int)`
convenience init(x: String) {
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
}
9 changes: 9 additions & 0 deletions unified/ql/test/library-tests/constructors/test.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
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] |
Loading
Loading