Skip to content
Draft
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
29 changes: 29 additions & 0 deletions unified/ql/lib/codeql/unified/internal/AstExtra.qll
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ module Public {
final class EnumCaseClass extends ClassLikeDeclaration {
EnumCaseClass() { this.hasModifier("enum_case") }

/** Gets the enum declaration that this case class belongs to. */
ClassLikeDeclaration getEnum() { this = result.getAMember() }

/** Gets the constructor of this algebraic data type. */
EnumCaseConstructor getConstructor() { result = this.getAMember() }
}
Expand Down Expand Up @@ -168,5 +171,31 @@ module Public {

/** Gets the class containing this constructor. */
EnumCaseClass getEnumCaseClass() { result.getAMember() = this }

/** Gets the enum declaration that this constructor belongs to. */
ClassLikeDeclaration getEnum() { result = this.getEnumCaseClass().getEnum() }
}

/**
* A field of an enum case in an algebraic data type. For example `a` and
* `b` in
*
* ```swift
* enum E {
* case a
* case b
* }
* ```
*/
final class EnumField extends VariableDeclaration {
private ClassLikeDeclaration enum;

EnumField() {
this = enum.getAMember() and
this.hasModifier("enum_case")
}

/** Gets the enum declaration that this enum field belongs to. */
ClassLikeDeclaration getEnum() { result = enum }
}
}
23 changes: 20 additions & 3 deletions unified/ql/lib/codeql/unified/internal/ExprPositions.qll
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ private import NameBinding as NameBinding
private import ExprPositionsPlugin

/**
* Holds if `expr` appears in a context where it must refer to a type.
* Holds if `expr` appears in a context where it must refer to a type,
* and where omission of type arguments implicitly refer to the type
* parameters of the type being accessed (provided that they are in scope).
*/
predicate isInTypeContext(Expr expr) {
predicate isInTypeContextWithImplicitTypeArgs(Expr expr) {
expr = any(TypeCastExpr n).getType()
or
expr = any(TypeTestExpr n).getType()
Expand Down Expand Up @@ -36,11 +38,26 @@ predicate isInTypeContext(Expr expr) {
or
expr.getParent() instanceof TypeConstraint
or
any(ExprPositionsPlugin p).isInTypeContextWithImplicitTypeArgs(expr)
or
isInTypeContextWithImplicitTypeArgs(expr.getEnclosingExpr())
}

private predicate isInTypeContextWithoutImplicitTypeArgs(Expr expr) {
exists(NameBinding::getTypeFromRef(expr))
or
any(ExprPositionsPlugin p).isInTypeContext(expr)
or
isInTypeContext(expr.getEnclosingExpr())
isInTypeContextWithoutImplicitTypeArgs(expr.getEnclosingExpr())
}

/**
* Holds if `expr` appears in a context where it must refer to a type.
*/
predicate isInTypeContext(Expr expr) {
isInTypeContextWithImplicitTypeArgs(expr)
or
isInTypeContextWithoutImplicitTypeArgs(expr)
}

/** Holds if `e` appears in a name-binding position inside `declaration` */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ private module Plugins {

class ExprPositionsPlugin extends Unit {
predicate isInTypeContext(Expr e) { none() }

predicate isInTypeContextWithImplicitTypeArgs(Expr e) { none() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ private import unified
private import ExprPositionsPlugin

private class ExprPositionsPluginSwift extends ExprPositionsPlugin {
override predicate isInTypeContext(Expr e) { e = any(GenericTypeExpr g).getBase() }
override predicate isInTypeContextWithImplicitTypeArgs(Expr e) {
e = any(GenericTypeExpr g).getBase()
}
}
5 changes: 5 additions & 0 deletions unified/ql/lib/codeql/unified/internal/FacadeAst.qll
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ module Unified {
)
}

/**
* Gets the nearest resolved enclosing class declaration, if any.
*/
ClassLikeDeclaration getEnclosingClassResolved() { result = this.getEnclosingClass().resolve() }

private AstNode overrideEnclosingCallableParent() {
exists(FunctionExpr func |
// Capture declarations are evaluated as part of the outer context, and
Expand Down
15 changes: 15 additions & 0 deletions unified/ql/lib/codeql/unified/internal/StaticNameBinding.qll
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,21 @@ Identifier getTypeFromRef(AstNode access) {
)
}

/**
* Gets the type being accessed by `access`, where type aliases are resolved, if any.
*/
Identifier getTypeFromRefAliasesResolved(AstNode access) {
exists(Identifier i | i = getTypeFromRef(access) |
not i = any(TypeAliasDeclaration t).getNameNode() and
result = i
or
exists(TypeAliasDeclaration alias |
i = alias.getNameNode() and
result = getTypeFromRefAliasesResolved(alias.getType())
)
)
}

/**
* Gets the name of the implicit receiver parameter in scope at `callable` (possibly declared by an outer callable).
*
Expand Down
30 changes: 29 additions & 1 deletion unified/ql/lib/codeql/unified/internal/typeinference/Type.qll
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ predicate associatedTypeParameterInherited(
name = a.getName()
}

private predicate isNestedType(ClassLikeDeclaration nested, ClassLikeDeclaration parent) {
exists(nested.getNameNode()) and
exists(parent.getNameNode()) and
parent.getAMemberIncludingExtensions() = nested
}

cached
newtype TType =
TClassLikeDeclarationType(ClassLikeDeclaration c) {
Expand All @@ -56,6 +62,7 @@ newtype TType =
) {
associatedTypeParameter(c, a, inherited)
} or
TParentTypeParameterType(ClassLikeDeclaration c) { isNestedType(c, _) } or
TUnknownType() or
TUnknownTypeTypeParameter(int i) { i in [0 .. 20] }

Expand Down Expand Up @@ -112,9 +119,11 @@ class ClassLikeDeclarationType extends TypeImpl, TClassLikeDeclarationType {
result = super.getATypeParameter()
or
result = TAssociatedTypeParameterType(c, _, _)
or
result = TParentTypeParameterType(c)
}

override string toString() { result = c.getName() }
override string toString() { result = c.getQualifiedName() }

override Location getLocation() { result = c.getLocation() }
}
Expand Down Expand Up @@ -214,6 +223,25 @@ class AssociatedTypeParameterType extends TypeParameter, TAssociatedTypeParamete
}
}

/**
* A special type parameter used to represent the parent type of a nested class.
*/
class ParentTypeParameterType extends TypeParameter, TParentTypeParameterType {
private Unified::ClassLikeDeclaration c;

ParentTypeParameterType() { this = TParentTypeParameterType(c) }

ClassLikeDeclaration getParent() { isNestedType(c, result) }

override ClassLikeDeclaration getDeclaringItem() { result = c }

override Identifier getNameNode() { none() }

override string toString() { result = "(parent:" + c.getName() + ")" }

override Location getLocation() { result = c.getLocation() }
}

/**
* A type parameter of the special `UnknownType`.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@ abstract class TypeAbstraction extends AstNode {
*/
private class ClassLikeDeclarationTypeAbstraction extends TypeAbstraction instanceof Unified::ClassLikeDeclaration
{
override TypeParameter getATypeParameter() { this = result.getDeclaringItem() }
override TypeParameter getATypeParameter() {
// type parameters from outer classes are also in scope
super.resolve().getEnclosingClassResolved*() = result.getDeclaringItem()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ private module Input1 implements InputSig1<Location> {
kind = 3 and
tp0 = TUnknownTypeTypeParameter(id1) and
id2 = 0
or
kind = 4 and
exists(ClassLikeDeclaration c |
tp0 = TParentTypeParameterType(c) and
id1 = idOfTypeParameterAstNode(c) and
id2 = 0
)
|
tp0 order by kind, id1, id2
)
Expand Down Expand Up @@ -389,9 +396,6 @@ module Input3 implements InputSig3 {
Unified::Callable getCallable() { result = c }

override TypeMention getDeclaringType() {
result = c.(EnumConstructor).getEnum().getNameNode()
or
not c instanceof EnumConstructor and
exists(ClassLikeDeclaration cls |
c = cls.getAMember() and
result = getClassLikeDeclarationTypeMention(cls)
Expand All @@ -405,9 +409,9 @@ module Input3 implements InputSig3 {
or
result = c.(FunctionDeclaration).getNameNode()
or
result = c.(EnumConstructor).getEnclosingClass().getNameNode()
result = c.(EnumCaseConstructor).getEnumCaseClass().getNameNode()
or
not c instanceof EnumConstructor and
not c instanceof EnumCaseConstructor and
result = c.(ConstructorDeclaration).getNameNode()
}

Expand Down Expand Up @@ -492,15 +496,17 @@ module Input3 implements InputSig3 {
}

Callable getTargetViaStaticNameBinding() {
exists(NameBinding b | b = getStaticBindingTargetFromRef(this.getCallee()) |
exists(NameBinding b |
// object creation (including enum constructors): `String(42)`, `Optional.Some(42)`
b = getTypeFromRefAliasesResolved(this.getCallee()) and
exists(ClassLikeDeclaration cls |
cls.getNameNode() = b and
result.isInstanceMemberOf(cls, _) and
result.asCallable() instanceof ConstructorDeclaration
)
or
// call without explicit receiver: `foo(42)`
b = getStaticBindingTargetFromRef(this.getCallee()) and
result.getNameNode() = b
)
}
Expand Down Expand Up @@ -659,10 +665,20 @@ module Input3 implements InputSig3 {
result = getQualifierTypeMention(invocation)
}

private predicate constructionReceiver(Invocation i, Callable constructor, Expr receiver) {
constructor = i.getTargetImpl(_) and
constructor.asCallable() instanceof ConstructorDeclaration and
receiver = i.getArgument(0)
private predicate constructionReceiver(
Invocation i, Callable constructor, Expr receiver, TypePath enumPrefixPath
) {
exists(ConstructorDeclaration cd |
constructor = i.getTargetImpl(_) and
cd = constructor.asCallable() and
receiver = i.getArgument(0)
|
enumPrefixPath =
TypePath::singleton(TParentTypeParameterType(cd.(EnumCaseConstructor).getEnumCaseClass()))
or
not cd instanceof EnumCaseConstructor and
enumPrefixPath.isEmpty()
)
}

/**
Expand Down Expand Up @@ -696,11 +712,15 @@ module Input3 implements InputSig3 {
M3::ContextualTyping::ContextualReturnType<ContextualConstructorReturnTypeInput>;

private predicate needsContextualTyping(Invocation i, TypePath path) {
exists(Callable constructor, Expr receiver, ClassLikeDeclarationType receiverType |
constructionReceiver(i, constructor, receiver) and
receiverType = inferType(receiver)
exists(
Callable constructor, Expr receiver, TypePath enumPrefixPath,
ClassLikeDeclarationType receiverType, TypePath path0
|
constructionReceiver(i, constructor, receiver, enumPrefixPath) and
receiverType = inferType(receiver) and
path = path0.stripPrefix(enumPrefixPath)
|
ContextualConstructorReturnType::needsContextualTyping(i, path) and
ContextualConstructorReturnType::needsContextualTyping(i, path0) and
receiverType = constructor.getDeclaringType().getType()
or
// inherited constructor
Expand All @@ -710,18 +730,19 @@ module Input3 implements InputSig3 {
|
declType = constructor.getDeclaringType().getType() and
BaseTypes::rootTypesSatisfaction(receiverType, declType, abs, _, constraint) and
path = TypePath::singleton(tp)
tp =
getClassLikeDeclarationTypeMention(receiverType.getClassLikeDeclaration())
.getTypeAt(path0)
|
// `A<T> : B<T>`
exists(TypePath path0 |
BaseTypes::conditionSatisfiesConstraintTypeAt(abs, _, constraint, path0, tp) and
ContextualConstructorReturnType::needsContextualTyping(i, path0)
exists(TypePath path1 |
BaseTypes::conditionSatisfiesConstraintTypeAt(abs, _, constraint, path1, tp) and
ContextualConstructorReturnType::needsContextualTyping(i, path1)
)
or
// `A<T> : B`
tp = receiverType.getATypeParameter() and
not BaseTypes::conditionSatisfiesConstraintTypeAt(abs, _, constraint, _, tp) and
not exists(Type t | t = i.getTypeQualifier(path) and not t instanceof PseudoType)
not exists(Type t | t = i.getTypeQualifier(path0) and not t instanceof PseudoType)
)
)
}
Expand All @@ -734,8 +755,7 @@ module Input3 implements InputSig3 {
* as well as calls receivers, `self.init(0)` or `super.init(0)`.
*/
predicate step(AstNode n1, TypePath prefix1, AstNode n2, TypePath prefix2) {
constructionReceiver(n2, _, n1) and
prefix1.isEmpty() and
constructionReceiver(n2, _, n1, prefix1) and
prefix2.isEmpty()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,6 @@ class TypeInferencePlugin extends Unit {
/** Holds if `field` is a field of the tuple type `tuple` with the given `name`. */
abstract predicate tupleField(Type tuple, string name, VariableDeclaration field);

/** Holds if `case` is an enum case constructor of `enum` with identifier `i`. */
abstract predicate isEnumConstructor(
ClassLikeDeclaration enum, ConstructorDeclaration case, Identifier i
);

/** Holds if `field` is an enum field of `enum`. */
abstract predicate isEnumField(ClassLikeDeclaration enum, VariableDeclaration field);

/** Gets the inferred type for the given AST node and type path. */
Type inferType(AstNode n, TypePath path) { none() }
}
Expand Down Expand Up @@ -139,25 +131,6 @@ class TupleType extends Type {
}
}

class EnumConstructor extends ConstructorDeclaration {
private ClassLikeDeclaration enum;
private Identifier id;

EnumConstructor() { any(TypeInferencePlugin p).isEnumConstructor(enum, this, id) }

ClassLikeDeclaration getEnum() { result = enum }

Identifier getNameNode() { result = id }
}

class EnumField extends VariableDeclaration {
private ClassLikeDeclaration enum;

EnumField() { any(TypeInferencePlugin p).isEnumField(enum, this) }

ClassLikeDeclaration getEnum() { result = enum }
}

Type inferType(AstNode n, TypePath path) { result = any(TypeInferencePlugin p).inferType(n, path) }

Callable getFunctionInvoke(Type t) { result = any(TypeInferencePlugin p).getFunctionInvoke(t) }
Expand Down
Loading
Loading