Skip to content

Commit b61f3b4

Browse files
authored
Merge pull request github#85 from github/igfoo/companion
Kotlin: Add support for companion objects
2 parents f6c9ee5 + d748529 commit b61f3b4

File tree

10 files changed

+190
-13
lines changed

10 files changed

+190
-13
lines changed

java/kotlin-extractor/src/main/kotlin/KotlinExtractorExtension.kt

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,9 @@ class X {
622622
}
623623
}
624624

625+
/*
626+
This returns the `X` in c's label `@"class;X"`.
627+
*/
625628
private fun getUnquotedClassLabel(c: IrClass, typeArgs: List<IrTypeArgument>): String {
626629
val pkg = c.packageFqName?.asString() ?: ""
627630
val cls = c.name.asString()
@@ -876,6 +879,28 @@ open class KotlinFileExtractor(
876879
val locId = tw.getLocation(c)
877880
tw.writeHasLocation(id, locId)
878881

882+
val parent = c.parent
883+
if (parent is IrClass) {
884+
val parentId = useClassInstance(parent, listOf()).classLabel
885+
tw.writeEnclInReftype(id, parentId)
886+
if(c.isCompanion) {
887+
// If we are a companion then our parent has a
888+
// public static final ParentClass$CompanionObjectClass CompanionObJectName;
889+
// that we need to fabricate here
890+
val instance = useCompanionObjectClassInstance(c)
891+
if(instance != null) {
892+
val type = useSimpleTypeClass(c, emptyList(), false)
893+
tw.writeFields(instance.id, instance.name, type.javaResult.id, type.kotlinResult.id, id, instance.id)
894+
tw.writeHasLocation(instance.id, locId)
895+
tw.writeHasModifier(instance.id, extractModifier("public"))
896+
tw.writeHasModifier(instance.id, extractModifier("static"))
897+
tw.writeHasModifier(instance.id, extractModifier("final"))
898+
@Suppress("UNCHECKED_CAST")
899+
tw.writeClass_companion_object(parentId as Label<DbClass>, instance.id, id as Label<DbClass>)
900+
}
901+
}
902+
}
903+
879904
c.typeParameters.map { extractTypeParameter(it) }
880905
c.declarations.map { extractDeclaration(it, id) }
881906
extractObjectInitializerFunction(c, id)
@@ -901,16 +926,35 @@ open class KotlinFileExtractor(
901926
return id
902927
}
903928

904-
data class ObjectClassInstance(val id: Label<DbField>, val name: String)
905-
fun useObjectClassInstance(c: IrClass): ObjectClassInstance {
929+
data class FieldResult(val id: Label<DbField>, val name: String)
930+
931+
fun useCompanionObjectClassInstance(c: IrClass): FieldResult? {
932+
val parent = c.parent
933+
if(!c.isCompanion) {
934+
logger.warn(Severity.ErrorSevere, "Using companion instance for non-companion class")
935+
return null
936+
}
937+
else if (parent !is IrClass) {
938+
logger.warn(Severity.ErrorSevere, "Using companion instance for non-companion class")
939+
return null
940+
} else {
941+
val parentId = useClassInstance(parent, listOf()).classLabel
942+
val instanceName = c.name.asString()
943+
val instanceLabel = "@\"field;{$parentId};$instanceName\""
944+
val instanceId: Label<DbField> = tw.getLabelFor(instanceLabel)
945+
return FieldResult(instanceId, instanceName)
946+
}
947+
}
948+
949+
fun useObjectClassInstance(c: IrClass): FieldResult {
906950
if(!c.isNonCompanionObject) {
907951
logger.warn(Severity.ErrorSevere, "Using instance for non-object class")
908952
}
909953
val classId = useClassInstance(c, listOf()).classLabel
910954
val instanceName = "INSTANCE"
911955
val instanceLabel = "@\"field;{$classId};$instanceName\""
912956
val instanceId: Label<DbField> = tw.getLabelFor(instanceLabel)
913-
return ObjectClassInstance(instanceId, instanceName)
957+
return FieldResult(instanceId, instanceName)
914958
}
915959

916960
private fun isQualifiedThis(vp: IrValueParameter): Boolean {
@@ -1657,15 +1701,17 @@ open class KotlinFileExtractor(
16571701
// field that we are accessing here.
16581702
val exprParent = parent.expr(e, callable)
16591703
val c: IrClass = e.symbol.owner
1660-
val instance = useObjectClassInstance(c)
1704+
val instance = if (c.isCompanion) useCompanionObjectClassInstance(c) else useObjectClassInstance(c)
16611705

1662-
val id = tw.getFreshIdLabel<DbVaraccess>()
1663-
val type = useType(e.type)
1664-
val locId = tw.getLocation(e)
1665-
tw.writeExprs_varaccess(id, type.javaResult.id, type.kotlinResult.id, exprParent.parent, exprParent.idx)
1666-
tw.writeHasLocation(id, locId)
1706+
if(instance != null) {
1707+
val id = tw.getFreshIdLabel<DbVaraccess>()
1708+
val type = useType(e.type)
1709+
val locId = tw.getLocation(e)
1710+
tw.writeExprs_varaccess(id, type.javaResult.id, type.kotlinResult.id, exprParent.parent, exprParent.idx)
1711+
tw.writeHasLocation(id, locId)
16671712

1668-
tw.writeVariableBinding(id, instance.id)
1713+
tw.writeVariableBinding(id, instance.id)
1714+
}
16691715
}
16701716
else -> {
16711717
logger.warnElement(Severity.ErrorSevere, "Unrecognised IrExpression: " + e.javaClass, e)

java/ql/lib/config/semmlecode.dbscheme

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,12 @@ class_object(
319319
unique int instance: @field ref
320320
);
321321

322+
class_companion_object(
323+
unique int id: @class ref,
324+
unique int instance: @field ref,
325+
unique int companion_object: @class ref
326+
);
327+
322328
kt_nullable_types(
323329
unique int id: @kt_nullable_type,
324330
int classid: @classorinterface ref

java/ql/lib/semmle/code/java/Type.qll

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,11 @@ class Class extends ClassOrInterface, @class {
647647
)
648648
}
649649

650+
/** Get the companion object of this class, if any. */
651+
ClassCompanionObject getCompanionObject() {
652+
class_companion_object(this, _, result)
653+
}
654+
650655
override string getAPrimaryQlClass() { result = "Class" }
651656
}
652657

@@ -662,6 +667,18 @@ class ClassObject extends Class {
662667
}
663668
}
664669

670+
/** A Kotlin `companion object`. */
671+
class ClassCompanionObject extends Class {
672+
ClassCompanionObject() {
673+
class_companion_object(_, _, this)
674+
}
675+
676+
/** Gets the instance variable that implements this `companion object`. */
677+
Field getInstance() {
678+
class_companion_object(_, result, this)
679+
}
680+
}
681+
665682
/**
666683
* A record declaration.
667684
*/
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
| companion_objects.kt:3:5:5:5 | MyClassCompanion | companion_objects.kt:9:5:9:11 | MyClassCompanion |
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import java
2+
3+
from VarAccess va, ClassCompanionObject cco
4+
where va.getVariable() = cco.getInstance()
5+
select cco, va
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
| companion_objects.kt:1:1:6:1 | MyClass | companion_objects.kt:3:5:5:5 | MyClassCompanion | companion_objects.kt:3:5:5:5 | MyClassCompanion | final,public,static |
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class MyClass {
2+
fun funInClass() {}
3+
companion object MyClassCompanion {
4+
fun funInCompanion() {}
5+
}
6+
}
7+
8+
fun user() {
9+
MyClass.funInCompanion()
10+
MyClass().funInClass()
11+
}
12+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import java
2+
3+
from Class c, ClassCompanionObject cco, Field f
4+
where c.fromSource()
5+
and cco = c.getCompanionObject()
6+
and f = cco.getInstance()
7+
select c, f, cco, concat(f.getAModifier().toString(), ",")

java/ql/test/kotlin/library-tests/object/accesses.ql

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,3 @@ import java
33
from VarAccess va, ClassObject co
44
where va.getVariable() = co.getInstance()
55
select co, va
6-
7-
// select count(VarAccess va)
8-

0 commit comments

Comments
 (0)