Skip to content

Commit 70ebb41

Browse files
committed
Restore nullability annotations
This is imperfect since arguments to those annotations will be missing, but at least the common case of a plain `@NotNull` or `@Nullable` will be right, and the `@NotNull`s introduced by the Kotlin compiler will be present as expected.
1 parent 8bbb34a commit 70ebb41

File tree

10 files changed

+111
-3
lines changed

10 files changed

+111
-3
lines changed

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

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.descriptors.java.JavaVisibilities
1414
import org.jetbrains.kotlin.ir.IrElement
1515
import org.jetbrains.kotlin.ir.IrStatement
1616
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
17+
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
1718
import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
1819
import org.jetbrains.kotlin.ir.builders.declarations.*
1920
import org.jetbrains.kotlin.ir.declarations.*
@@ -44,15 +45,21 @@ import org.jetbrains.kotlin.ir.util.parentClassOrNull
4445
import org.jetbrains.kotlin.ir.util.primaryConstructor
4546
import org.jetbrains.kotlin.ir.util.render
4647
import org.jetbrains.kotlin.ir.util.target
48+
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
49+
import org.jetbrains.kotlin.load.java.NOT_NULL_ANNOTATIONS
50+
import org.jetbrains.kotlin.load.java.NULLABLE_ANNOTATIONS
4751
import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
52+
import org.jetbrains.kotlin.load.java.structure.JavaAnnotation
4853
import org.jetbrains.kotlin.load.java.structure.JavaClass
54+
import org.jetbrains.kotlin.load.java.structure.JavaConstructor
4955
import org.jetbrains.kotlin.load.java.structure.JavaMethod
5056
import org.jetbrains.kotlin.load.java.structure.JavaTypeParameter
5157
import org.jetbrains.kotlin.load.java.structure.JavaTypeParameterListOwner
5258
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass
5359
import org.jetbrains.kotlin.name.FqName
5460
import org.jetbrains.kotlin.types.Variance
5561
import org.jetbrains.kotlin.util.OperatorNameConventions
62+
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
5663
import java.io.Closeable
5764
import java.util.*
5865
import kotlin.collections.ArrayList
@@ -887,7 +894,13 @@ open class KotlinFileExtractor(
887894
extractTypeAccessRecursive(substitutedType, location, id, -1)
888895
}
889896
val syntheticParameterNames = isUnderscoreParameter(vp) || ((vp.parent as? IrFunction)?.let { hasSynthesizedParameterNames(it) } ?: true)
890-
extractAnnotations(vp, id, extractTypeAccess)
897+
val javaParameter = when(val callable = (vp.parent as? IrFunction)?.let { getJavaCallable(it) }) {
898+
is JavaConstructor -> callable.valueParameters.getOrNull(idx)
899+
is JavaMethod -> callable.valueParameters.getOrNull(idx)
900+
else -> null
901+
}
902+
val extraAnnotations = listOfNotNull(getNullabilityAnnotation(vp.type, vp.origin, vp.annotations, javaParameter?.annotations))
903+
extractAnnotations(vp.annotations + extraAnnotations, id, extractTypeAccess)
891904
return extractValueParameter(id, substitutedType, vp.name.asString(), location, parent, idx, useValueParameter(vp, parentSourceDeclaration), syntheticParameterNames, vp.isVararg, vp.isNoinline, vp.isCrossinline)
892905
}
893906
}
@@ -1337,6 +1350,32 @@ open class KotlinFileExtractor(
13371350
logger.warn("Needed a signature for a type that doesn't have one")
13381351
}
13391352

1353+
private fun getNullabilityAnnotationName(t: IrType, declOrigin: IrDeclarationOrigin, existingAnnotations: List<IrConstructorCall>, javaAnnotations: Collection<JavaAnnotation>?): FqName? {
1354+
if (t !is IrSimpleType)
1355+
return null
1356+
1357+
return if (declOrigin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB) {
1358+
// Java declaration: restore a NotNull or Nullable annotation if the original Java member had one but the Kotlin compiler removed it.
1359+
javaAnnotations?.mapNotNull { it.classId?.asSingleFqName() }
1360+
?.singleOrNull { NOT_NULL_ANNOTATIONS.contains(it) || NULLABLE_ANNOTATIONS.contains(it) }
1361+
?.takeUnless { existingAnnotations.any { existing -> existing.type.classFqName == it } }
1362+
} else {
1363+
// Kotlin declaration: add a NotNull annotation to a non-nullable non-primitive type.
1364+
JvmAnnotationNames.JETBRAINS_NOT_NULL_ANNOTATION.takeUnless { t.isNullable() || primitiveTypeMapping.getPrimitiveInfo(t) != null }
1365+
}
1366+
}
1367+
1368+
private fun getNullabilityAnnotation(t: IrType, declOrigin: IrDeclarationOrigin, existingAnnotations: List<IrConstructorCall>, javaAnnotations: Collection<JavaAnnotation>?) =
1369+
getNullabilityAnnotationName(t, declOrigin, existingAnnotations, javaAnnotations)?.let {
1370+
pluginContext.referenceClass(it)?.let { annotationClass ->
1371+
annotationClass.owner.declarations.firstIsInstanceOrNull<IrConstructor>()?.let { annotationConstructor ->
1372+
IrConstructorCallImpl.fromSymbolOwner(
1373+
UNDEFINED_OFFSET, UNDEFINED_OFFSET, annotationConstructor.returnType, annotationConstructor.symbol, 0
1374+
)
1375+
}
1376+
}
1377+
}
1378+
13401379
private fun forceExtractFunction(f: IrFunction, parentId: Label<out DbReftype>, extractBody: Boolean, extractMethodAndParameterTypeAccesses: Boolean, extractAnnotations: Boolean, typeSubstitution: TypeSubstitution?, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?, extractOrigin: Boolean = true, overriddenAttributes: OverriddenFunctionAttributes? = null): Label<out DbCallable> {
13411380
with("function", f) {
13421381
DeclarationStackAdjuster(f, overriddenAttributes).use {
@@ -1427,8 +1466,10 @@ open class KotlinFileExtractor(
14271466

14281467
linesOfCode?.linesOfCodeInDeclaration(f, id)
14291468

1430-
if (extractAnnotations)
1431-
extractAnnotations(f, id, extractMethodAndParameterTypeAccesses)
1469+
if (extractAnnotations) {
1470+
val extraAnnotations = listOfNotNull(getNullabilityAnnotation(f.returnType, f.origin, f.annotations, getJavaCallable(f)?.annotations))
1471+
extractAnnotations(f.annotations + extraAnnotations, id, extractMethodAndParameterTypeAccesses)
1472+
}
14321473

14331474
return id
14341475
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import org.jetbrains.annotations.*;
2+
import zpkg.A;
3+
4+
public class AnnotatedMethods {
5+
6+
public @A @NotNull String notNullAnnotated(@A @NotNull String param) { return param; }
7+
8+
public @A @Nullable String nullableAnnotated(@A @Nullable String param) { return param; }
9+
10+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
public class JavaUser {
2+
3+
public static void test(KotlinAnnotatedMethods km) {
4+
km.f(null);
5+
}
6+
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import zpkg.A
2+
3+
class KotlinAnnotatedMethods {
4+
5+
@A fun f(@A m: AnnotatedMethods): String = m.notNullAnnotated("hello") + m.nullableAnnotated("world")!!
6+
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.jetbrains.annotations;
2+
3+
public @interface NotNull {
4+
String value() default "";
5+
Class<? extends Exception> exception() default Exception.class;
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package org.jetbrains.annotations;
2+
3+
public @interface Nullable {
4+
String value() default "";
5+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
| AnnotatedMethods.java:6:29:6:44 | notNullAnnotated | parameter | AnnotatedMethods.java:6:46:6:47 | A |
2+
| AnnotatedMethods.java:6:29:6:44 | notNullAnnotated | parameter | AnnotatedMethods.java:6:49:6:56 | NotNull |
3+
| AnnotatedMethods.java:6:29:6:44 | notNullAnnotated | return value | AnnotatedMethods.java:6:10:6:11 | A |
4+
| AnnotatedMethods.java:6:29:6:44 | notNullAnnotated | return value | AnnotatedMethods.java:6:13:6:20 | NotNull |
5+
| AnnotatedMethods.java:8:30:8:46 | nullableAnnotated | parameter | AnnotatedMethods.java:8:48:8:49 | A |
6+
| AnnotatedMethods.java:8:30:8:46 | nullableAnnotated | parameter | AnnotatedMethods.java:8:51:8:59 | Nullable |
7+
| AnnotatedMethods.java:8:30:8:46 | nullableAnnotated | return value | AnnotatedMethods.java:8:10:8:11 | A |
8+
| AnnotatedMethods.java:8:30:8:46 | nullableAnnotated | return value | AnnotatedMethods.java:8:13:8:21 | Nullable |
9+
| ktUser.kt:5:6:5:105 | f | parameter | ktUser.kt:0:0:0:0 | NotNull |
10+
| ktUser.kt:5:6:5:105 | f | parameter | ktUser.kt:5:12:5:13 | A |
11+
| ktUser.kt:5:6:5:105 | f | return value | ktUser.kt:0:0:0:0 | NotNull |
12+
| ktUser.kt:5:6:5:105 | f | return value | ktUser.kt:5:3:5:4 | A |
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from create_database_utils import *
2+
3+
os.mkdir('out')
4+
os.mkdir('out2')
5+
os.mkdir('out3')
6+
run_codeql_database_create(["javac AnnotatedMethods.java zpkg/A.java org/jetbrains/annotations/NotNull.java org/jetbrains/annotations/Nullable.java -d out", "kotlinc ktUser.kt -cp out -d out2", "javac JavaUser.java -cp out:out2 -d out3"], lang="java")
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import java
2+
3+
from Method m, string origin, Annotation a
4+
where
5+
m.fromSource() and
6+
(
7+
origin = "return value" and a = m.getAnAnnotation()
8+
or
9+
origin = "parameter" and a = m.getAParameter().getAnAnnotation()
10+
)
11+
select m, origin, a
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package zpkg;
2+
3+
public @interface A { }

0 commit comments

Comments
 (0)