Skip to content

Commit c6f4742

Browse files
committed
Kotlin: extract interface redeclarations of Object methods
Due to a probable compiler bug (?) the redeclaration looks like a fake symbol, leading to Java dispatching against a declaration that Kotlin doesn't believe exists.
1 parent 1d2087b commit c6f4742

File tree

6 files changed

+45
-7
lines changed

6 files changed

+45
-7
lines changed

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

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.load.java.structure.JavaClass
2727
import org.jetbrains.kotlin.load.java.structure.JavaMethod
2828
import org.jetbrains.kotlin.load.java.structure.JavaTypeParameter
2929
import org.jetbrains.kotlin.load.java.structure.JavaTypeParameterListOwner
30+
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass
3031
import org.jetbrains.kotlin.name.FqName
3132
import org.jetbrains.kotlin.types.Variance
3233
import org.jetbrains.kotlin.util.OperatorNameConventions
@@ -98,15 +99,29 @@ open class KotlinFileExtractor(
9899
}
99100
}
100101

102+
private fun javaBinaryDeclaresMethod(c: IrClass, name: String) =
103+
((c.source as? JavaSourceElement)?.javaElement as? BinaryJavaClass)?.methods?.any { it.name.asString() == name }
104+
105+
private fun isJavaBinaryDeclaration(f: IrFunction) =
106+
f.parentClassOrNull?.let { javaBinaryDeclaresMethod(it, f.name.asString()) } ?: false
107+
108+
private fun isJavaBinaryObjectMethodRedeclaration(d: IrDeclaration) =
109+
when (d) {
110+
is IrFunction ->
111+
when (d.name.asString()) {
112+
"toString" -> d.valueParameters.isEmpty()
113+
"hashCode" -> d.valueParameters.isEmpty()
114+
"equals" -> d.valueParameters.singleOrNull()?.type?.isNullableAny() ?: false
115+
else -> false
116+
} && isJavaBinaryDeclaration(d)
117+
else -> false
118+
}
119+
101120
@OptIn(ObsoleteDescriptorBasedAPI::class)
102121
private fun isFake(d: IrDeclarationWithVisibility): Boolean {
103-
val visibility = d.visibility
104-
if (visibility is DelegatedDescriptorVisibility && visibility.delegate == Visibilities.InvisibleFake) {
105-
return true
106-
}
107-
if (d.isFakeOverride) {
122+
val hasFakeVisibility = d.visibility.let { it is DelegatedDescriptorVisibility && it.delegate == Visibilities.InvisibleFake } || d.isFakeOverride
123+
if (hasFakeVisibility && !isJavaBinaryObjectMethodRedeclaration(d))
108124
return true
109-
}
110125
try {
111126
if ((d as? IrFunction)?.descriptor?.isHiddenToOvercomeSignatureClash == true) {
112127
return true
@@ -908,7 +923,9 @@ open class KotlinFileExtractor(
908923
else
909924
null
910925
} else {
911-
forceExtractFunction(f, parentId, extractBody, extractMethodAndParameterTypeAccesses, typeSubstitution, classTypeArgsIncludingOuterClasses).also {
926+
// Work around an apparent bug causing redeclarations of `fun toString(): String` specifically in interfaces loaded from Java classes show up like fake overrides.
927+
val overriddenVisibility = if (f.isFakeOverride && isJavaBinaryObjectMethodRedeclaration(f)) OverriddenFunctionAttributes(visibility = DescriptorVisibilities.PUBLIC) else null
928+
forceExtractFunction(f, parentId, extractBody, extractMethodAndParameterTypeAccesses, typeSubstitution, classTypeArgsIncludingOuterClasses, overriddenAttributes = overriddenVisibility).also {
912929
// The defaults-forwarder function is a static utility, not a member, so we only need to extract this for the unspecialised instance of this class.
913930
if (classTypeArgsIncludingOuterClasses.isNullOrEmpty())
914931
extractDefaultsFunction(f, parentId, extractBody, extractMethodAndParameterTypeAccesses)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
public interface Test {
2+
String toString();
3+
int hashCode();
4+
boolean equals(Object other);
5+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
| equals | Test |
2+
| hashCode | Test |
3+
| toString | Test |
4+
| toString | java.lang.CharSequence |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from create_database_utils import *
2+
3+
runSuccessfully(["javac", "Test.java", "-d", "bin"])
4+
run_codeql_database_create(["kotlinc user.kt -cp bin"], lang="java")
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import java
2+
3+
from Method m
4+
where
5+
m.getDeclaringType().getName() = ["Test", "CharSequence"] and
6+
m.getName() = ["toString", "equals", "hashCode"]
7+
select m.getName(), m.getDeclaringType().getQualifiedName()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fun f(t: Test, cs: CharSequence) = t.toString() + cs.toString() + t.equals(1) + t.hashCode()

0 commit comments

Comments
 (0)