For example, Java code might use `HasOutVariance<? extends String>`, or `HasInVariance<? super Object>`, both of which are needless wildcards and which the Kotlin extractor would previously have refused to reintroduce due to their not specifying a larger type than their bound. However this led to inconsistency with Java extraction, which
extracts the type as it appears in source.
This seems to particularly happen with generated code, e.g. the output of the Kotlin protobuf compiler.
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.
Previously we accidentally named these something like <init>$main, which is a name-mangling the Kotlin compiler applies to internal methods but not to constructors, which look to Java just like regular public constructors.
Dataflow requires accounting for the fact that the varargs parameter isn't necessarily last in the parameter list in a couple more places. Default handling just requires that if the only null parameter is the varargs argument, and it has no default value, then no $default method is required-- the caller is expected to simply pass nothing (at QL
/ source level) or an empty array (at JVM level).
Intermediate interfaces don't need interface forwarders, since the Kotlin compiler won't try to make them non-abstract by synthesising methods.
Super references should always target an immediate superclass, not the ancestor containing the intended implementation.
Rather than using lock files and rewriting TRAP file, and storing the
metadata in a .metadata file, we now encode the metadata in the filename
and rename all but the newest TRAP file so that the importer doesn't
see them.
So we might end up with e.g.
Text.members#0.0-1664381081060-java.trap.gz
Text.members#55.0-1658481279000-java.trap-old.gz
Text.members#55.0-1664381081060-java.trap-old.gz
For now, you can go back to the old system by setting
CODEQL_EXTRACTOR_JAVA_TRAP_LOCKING=true
in the environment.
If the interface is Java-defined and it provides a default interface implementation then real class-file default methods are being used and kotlinc won't synthesise anything. If the loaded .class file wasn't made by Kotlin, then we see all the real methods and there is no need to synthesise anything either.
Kotlin's implementation of defaults depends on the -Xjvm-default setting (or the @JvmDefault deprecated annotation, not implemented here): by default, actual interface class files don't use default method, and any class that would inherit one instead implements the interface calling a static method defined on TheInterface$DefaultImpls. With
-Xjvm-default=all or =all-compatibility, real interface default methods are emitted, with the latter retaining the DefaultImpls methods so that other Kotlin can use it.
Here I adopt a hybrid solution: create a real default method implementation, but also emit a forwarding method like `@override int f(int x) { return super.TheInterface.f(x); }`, because the Java extractor will see `MyClass.f` in the emitted class file and try to dispatch directly to it. The only downside is that we emit a default interface
method body for a prototype that will appear to be `abstract` to the Java extractor and which it will extract as such. I work around this by tolerating the combination `default abstract` in QL. The alternative would be to fully mimic the DefaultImpls approach, giving 100% fidelity to kotlinc's strategy and therefore no clash with the Java
extractor's view of the world.