I'm building a kotlin AAR library which I need to obfuscate before publishing. I have the following structure:
package com.example.token
interface TokenManager {
suspend fun getTokenStatus(): String
}
The above is a public interface available for the api client. The implementation is moved to an internal package:
package com.example.token.internal
internal class RestApiTokenManager: TokenManager {
override suspend fun getTokenStatus(): String {
//....
}
}
My obfuscation exception includes only the public interface package:
-keep class com.example.token.*{*;}
Unfortunately this results in a class cast exception:
java.lang.ClassCastException: e.e.a.a.l.d.e$a cannot be cast to c.a.a.e.a.a$a
In order to fix this I need to add an obfuscation exception for the implementation package, which I want to avoid.
Now the true weird problem is that the ClassCastException disappears as soon as I remove the suspend modifier.
I tried adding -keeptkotlinmetadata but I get R8: Unknown option error.
I've been stuck with this for a long time now, without a solution which won't force me to add an exception for my internal implementation classes.
This could be a bug in R8, and I have opened issue 167373399 to track this.
Please follow up there with information on the version of Android Studio / R8 you are currently using?
Please also take a look at this Medium post, which has more details on shrinking Kotlin code with R8.
The discussion continued and was concluded on the issuetracker 167373399
Related
I want to use musicg to analysis audio fingerprint.
But I got following error in musicg library.
IllegalAccessError: class Test
(in unnamed module #0x33f88ab) cannot access class com.sun.media.sound.FFT
(in module java.desktop)
because module java.desktop does not export com.sun.media.sound to unnamed module #0x33f88ab
What should I do?
Environment
Kotlin
JDK 17(downgrade available)
musicg 1.4.2.2
My code
fun main(args: Array<String>) {
FFT(10, 20)
}
got exception in FFT(10, 20)
com.sun and its sub-packages are not part of the public Java API. They implement some standard Java APIs, but you shouldn't refer to them directly. (They're likely to change and/or be renamed or removed between JVM releases, and non-Sun/Oracle JVMs probably won't have them at all.)
In most cases you should access the public API classes (e.g. in javax.sound) instead. (Those may use sun.*/com.sun.*/etc. classes internally as needed, but that's merely an implementation detail.)
In early versions of Java, there was nothing to stop people using those internal implementation classes, and so some developers got into bad habits. But Java 9 added a module system, which restricts access to them. The error message you see is a result of that.
The details are in JEP 260. The intent was that there would be public APIs for all of the critical APIs that were being restricted. However, according to this Oracle forum page, the work wasn't completed, and so there are some internal APIs for which no public equivalent exists yet.
FFT looks like one of those classes that has been overlooked. I can't see a direct replacement for it, I'm afraid. Is there a third-party library you can use? This question gives some options.
It is not really a Kotlin question, maybe an Intellij question, I don't know.
Assume we have a data class
data class Person(val name: String = "untitled", val age: Int = 20)
And we have a function
fun factory(cstr: ()->Person) : Person {
return cstr()
}
Then we can invoke factory(::Person) and obtain an instance of Person class with default constructor parameters.
The fun factory can be invoked successfully wherever. But in IntelliJ I get a red underline error
Look like the IDE failed to recognize that there is a default constructor.
However, If I change the code like that, the error goes away. Everything runs perfectly and no error is shown in the IDE.
I am using IntelliJ 2020.2 and Kotlin 1.4.10.
Maybe it is about some IntelliJ inspection rules, but I cannot find one related.
Further, it is a piece of old code that showed no error before (maybe 5 months ago). I am not sure what has bee change since then caused the error.
So the problem is why is Intellij show error for lambda version and not for KFunction version?
I assume invoking factory like this works fine:
factory(() -> Person())
Then it seems your version of the Kotlin IDE plugin does not handle constructor references well in this case. The compiler does, since the code works. If the IDE plugin version is the latest, please file a bug at https://youtrack.jetbrains.com/issues/KT
I reproduced the problem with old inference enabled in the build.gradle file: freeCompilerArgs += ["-XXLanguage:-NewInference"]. Please make sure you're using new (default) type inference in Kotlin 1.4+. So, remove the compiler argument, that should fix the IDE highlighting after Gradle project reimport into IDEA.
I'm attempting to consume Kotlin multiplatform code (that uses Ktor and Kotlin Coroutines) on iOS. The framework is generated correctly and can invoke some of the classes/methods exposed without any problem. However if I try to add following (as is done in https://github.com/JetBrains/kotlinconf-app/blob/master/konfios/konfswift/ui/UI.swift for example). I get "Use of undeclared type 'KotlinCoroutineContext" (and I see in SharedCode.h that it's not present)
import Foundation
import UIKit
import SharedCode
public class CoroutineDispatcher: Kotlinx_coroutines_core_nativeCoroutineScope {
override public func dispatch(context: KotlinCoroutineContext, block: Kotlinx_coroutines_core_nativeRunnable) {
DispatchQueue.main.async {
block.run()
}
}
}
The gradle file for share code includes following (am using Kotlin 1.3.11, Ktor 1.0.1 and Coroutines 1.0.1 along with Gradle 4.7)
commonMain.dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:${Versions.kotlin}"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:${Versions.kotlinCoroutines}"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:${Versions.kotlinxSerialization}"
implementation "io.ktor:ktor-client-core:${Versions.ktor}"
implementation "io.ktor:ktor-client-json:${Versions.ktor}"
}
androidMain.dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${Versions.kotlin}"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.kotlinCoroutines}"
implementation "io.ktor:ktor-client-core-jvm:${Versions.ktor}"
implementation "io.ktor:ktor-client-json-jvm:${Versions.ktor}"
}
iOSMain.dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-native:${Versions.kotlinCoroutines}"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:${Versions.kotlinxSerialization}"
implementation "io.ktor:ktor-client-ios:${Versions.ktor}"
implementation "io.ktor:ktor-client-core-ios:${Versions.ktor}"
implementation "io.ktor:ktor-client-json-ios:${Versions.ktor}"
}
I'm suspecting that issue might be that those symbols aren't explicitly exposed (also tried using api instead of implementation for coroutine dependencies but that didn't help).
This is what I have so far: https://github.com/joreilly/galway-bus-android/tree/kotlin_native
UPDATE:
Tried newly released Kotlin v1.3.20 but now getting following
> Task :SharedCode:linkMainDebugFrameworkIOS
warning: skipping /Users/jooreill/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-client-ios_debug_ios_x64/1.0.1/7ac4ac71743dbff041cc51a117e1732a9133d3b8/ktor-client-ios.klib. The abi versions don't match. Expected '[5]', found '2'
warning: the compiler versions don't match either. Expected '[1.1.1]', found '1.0.2-release-4769'
error: compilation failed: Could not find "/Users/jooreill/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-client-ios_debug_ios_x64/1.0.1/7ac4ac71743dbff041cc51a117e1732a9133d3b8/ktor-client-ios.klib" in [/Users/jooreill/devroot/github/galway-bus-android/SharedCode, /Users/jooreill/.konan/klib, /Users/jooreill/.konan/kotlin-native-macos-1.1.1/klib/common, /Users/jooreill/.konan/kotlin-native-macos-1.1.1/klib/platform/ios_x64].
``
I had the same issue in your update
The abi versions don't match. Expected '[5]', found '2'1
Basically, with Kotlin 1.3.20, Ktor needs to be upgraded to 1.1.2 and then Gradle 4.10 is required to use the latest Kotlin native. That is because of Gradle Metadata
Before that update, I start to have a new issue to build the iOS artifact regarding the Kotlin Serializer:
error: compilation failed: Can’t locate polymorphic serializer definition
To fix that issue I removed the #Serializable annotation from classes and provided the KSerializer interface separated.
I have a Kotlin interface with a default implementation, for instance:
interface Foo {
fun bar(): String {
return "baz"
}
}
This would be okay until I try to implement this interface from Java. When I do, it says the class need to be marked as abstract or implement the method bar(). Also when I try to implement the method, I am unable to call super.bar().
Generating true default methods callable from Java is an experimental feature of Kotlin 1.2.40.
You need to annotate the methods with the #JvmDefault annotation:
interface Foo {
#JvmDefault
fun bar(): String {
return "baz"
}
}
This feature is still disabled by default, you need to pass the -Xjvm-default=enable flag to the compiler for it to work. (If you need to do this in Gradle, see here).
It really is experimental, however. The blog post warns that both design and implementation may change in the future, and at least in my IDE, Java classes are still marked with errors for not implementing these methods, despite compiling and working fine.
Please see the related issue.
There is a recommendation in the comments:
Write your interface in Java (with default methods) and both the Java and Kotlin classes correctly use those defaults
If you know you won't be overriding the function in any implementations of your interface, you can use extension functions as a nice workaround for this issue. Just put an extension function in the same file as the interface (and at the top level so other files can use it).
For example, what you're doing could be done this way:
interface Foo {
// presumably other stuff
}
fun Foo.bar(): String {
return "baz"
}
See the docs on extension functions for more information about them.
One "gotcha" worth noting:
We would like to emphasize that extension functions are dispatched statically, i.e. they are not virtual by receiver type. This means that the extension function being called is determined by the type of the expression on which the function is invoked, not by the type of the result of evaluating that expression at runtime.
Put simply, extension functions don't do what you might expect from regular polymorphism. What this means for this workaround is that the default function cannot be overridden like a regular function. If you try to override it, you'll get some weird behavior, because the "overridden" version will be called whenever you're dealing explicitly with the subclass, but the extension version will be called when you're dealing with the interface generically. For example:
interface MyInterface {
fun a()
}
fun MyInterface.b() {
println("MyInterface.b() default implementation")
}
class MyInterfaceImpl : MyInterface {
override fun a() {
println("MyInterfaceImpl.a()")
}
fun b() {
println("MyInterfaceImpl.b() \"overridden\" implementation")
}
}
fun main(args: Array<String>) {
val inst1: MyInterface = MyInterfaceImpl()
inst1.a()
inst1.b() // calls the "default" implementation
val inst2: MyInterfaceImpl = MyInterfaceImpl() // could also just do "val inst2 = MyInterfaceImpl()" (the type is inferred)
inst2.a()
inst2.b() // calls the "overridden" implementation
}
Since Kotlin 1.4.0, you can use one of the following compiler flags:
-Xjvm-default=all
-Xjvm-default=all-compatibility (for binary compatibility with old Kotlin code)
This will enable JVM default method compilation for all interfaces.
If you want to read up on how to set these flags in your IDE or Maven/Gradle project, check out the documentation on compiler options.
Progress on this is being tracked in issue KT-4779, which also includes a helpful summary of the current state. The #JvmDefault annotation and the older -Xjvm-default=enable and -Xjvm-default=compatibility compiler flags should no longer be used.
Unlike earlier version of Java8, Kotlin can have default implementation in interface.
When you implement Foo interface into a Java class. Kotlin hides those implementation of interface method. As stated here.
Arrays are used with primitive datatypes on the Java platform to avoid the cost of boxing/unboxing operations. As Kotlin hides those implementation details, a workaround is required to interface with Java code
This is specific for Arrays in above link but it also applies to all the classes (May be to give support for earlier version of Java8).
EDIT
Above explanation is opinion based.
One thing i came across and that is the main reason.
Kotlin binaries were compiled with java bytecode version 1.8 without default methods in interfaces. And they are facing critical issue solving it.
I'm in a situation where the implementation of a library we are using is newer than the implementation one of our dependencies was coded against. E.g. Dependency uses MyLibrary-1.0 and we're using MyLibrary-2.0.
In the newer implementation a deprecated method has been removed, which causes run-time errors for us.
I'm trying to use AOP (Spring-AOP to be specific) to intercept calls made to the missing method, and proxy them into an existing method... but I can't seem to get the Aspect right.
It feels like Java is raising the 'java.lang.NoSuchMethodError' exception before my Aspect has an opportunity to intercept. Is there some trick I'm missing, or is this just not feasible (e.g. the method must exist in order to aspect it)?
#Before("execution(* com.mylibrary.SomeClass.*(..))")
Fails with java.lang.NoSuchMethodError
#Around("target(com.mylibrary.SomeClass) && execution(* missingMethod(..))")
Fails with java.lang.NoSuchMethodError
Assuming that your are talking about a 3rd party library which is independent of Spring, you cannot use Spring AOP with its proxy-based "AOP lite" approach which only works for public, non-static methods of Spring components. Please use the more powerful AspectJ instead. The Spring manual explains how to integrate full AspectJ with load-time weaving (LTW) into Spring applications. If your application is not based on Spring so far and you just wanted to use the framework because of Spring AOP, you can skip the whole Spring stuff altogether and use plain AspectJ.
The feature you want to use is an inter-type declaration (ITD), more specifically AspectJ's ability to declare methods for existing classes. Here is some sample code:
3rd party library:
package org.library;
public class Utility {
public String toHex(int number) {
return Integer.toHexString(number);
}
// Let us assume that this method was removed from the new library version
/*
#Deprecated
public String toOct(int number) {
return Integer.toOctalString(number);
}
*/
}
Let us assume that the method I commented out was just removed from the latest version your own project depends on, but you know how to re-implement it.
Project dependency depending on old version of 3rd party library:
package com.dependency;
import org.library.Utility;
public class MyDependency {
public void doSomethingWith(int number) {
System.out.println(number + " in octal = " + new Utility().toOct(number));
}
}
Because the previously deprecated method Utility.toOct does not exist anymore in the version used by your own project, you will get NoSuchMethodError during runtime when calling MyDependency.doSomethingWith.
Your own application:
package de.scrum_master.app;
import org.library.Utility;
import com.dependency.MyDependency;
public class Application {
public static void main(String[] args) {
System.out.println("3333 in hexadecimal = " + new Utility().toHex(3333));
new MyDependency().doSomethingWith(987);
}
}
As you can see, the application also uses the same library, but a different method which still exists in the current version. Unfortunately, it also uses the dependency which relies on the existence of the removed method. So how should we repair this?
Aspect using ITD:
AspectJ to the rescue! We just add the missing method to the 3rd party library.
package de.scrum_master.aspect;
import org.library.Utility;
public aspect DeprecatedMethodProvider {
public String Utility.toOct(int number) {
return Integer.toOctalString(number);
}
}
If you compile this project with the AspectJ compiler Ajc, it just works. In your real life scenario, compile the aspect into its own aspect library, put the weaving agent aspectjweaver.jar on the JVM command line in order to activate LTW and enjoy how it weaves the method into the library class via byte code instrumentation during class-loading.
Log output:
3333 in hexadecimal = d05
987 in octal = 1733
Et voilà! Enjoy. :-)
When the JVM load a class, it resolves all dependencies in a "linker" phase : external classes, properties and method. You can't pass this phase in your case, because methods are missing.
There are two modes on (Spring-)AOP: Proxy, and weaving.
Proxy create... a proxy around a class: the targeted class must exist and be loaded
Weaving can happen before a class is loaded: when a classloader load a class, an array of byte[] is passed to the weaver, which can manipulate the class bytecode before the class is really reified. This type of aop can work in your case. However, it will not be an easy task.