Status Update
Comments
sa...@google.com <sa...@google.com>
an...@google.com <an...@google.com> #2
Thank you for reporting this issue. For us to further investigate this issue, please provide the following additional information:
Please provide a sample project or apk to reproduce the issue. Also mention the steps to be followed for reproducing the issue with the given sample project or apk.
Android bug report (to be captured after reproducing the issue) For steps to capture a bug report, please refer:
Note: Please avoid uploading directly to the issue using attachments. Please upload to google drive and share the folder to
ga...@spotify.com <ga...@spotify.com> #3
We are using Bazel internally at spotify. I have tried reproducing using Gradle but no luck.
The class posted above compiles with the new and the old android jar on the classpath, but it compiles to different bytecode as you can see from my original post.
Do you have any special handling in the AGP for this? Would a Bazel sample project help?
Essentially the Jake wharton blogpost here:
Users of the Kotlin Android plugin or the Android targets of the Kotlin multiplatform plugin do not need to do this, as the use of the android.jar as the boot classpath limits the java.* APIs to those of your compileSdk (and Android Lint ensures you don’t use anything newer than your minSdk).
But since the android.jar in the android 35 sdk does have the conflicting method I'm suprised I'm not seeing the same issue when using the AGP to build a sample app using the problematic APIs.
ga...@spotify.com <ga...@spotify.com> #4
Actually a Bazel project would probably not be possible to provide since our kotlin to android integration is custom in our Bazel setup.
The question still stands if there is any special handling of this in the AGP.
ga...@spotify.com <ga...@spotify.com> #5
I was able to reproduce the issue using Gradle and I created a repo here:
ga...@spotify.com <ga...@spotify.com> #6
To reproduce this I had to bump kotlin from 1.9.0 to 1.9.20.
an...@google.com <an...@google.com> #7
Please share the bugreport to proceed further
an...@google.com <an...@google.com> #9
We have passed this to the development team and will update this issue with more information as it becomes available.
ga...@spotify.com <ga...@spotify.com> #10
Thank you!
b9...@gmail.com <b9...@gmail.com> #11
bi...@google.com <bi...@google.com> #12
Hey gabrielb@, I am from the build team.
IIUC, the problem is users kotlin extension function is shadowed by the new function introduced in jdk21. I think the best way is to change your extension function to avoid the conflict.
If you end up using the new api introduced in jdk21 and want it to be available in android 14- device, that is a story of desugaring new java api. (I can ask r8 forlks to checkout our support of desugaring jdk21 apis)
ga...@spotify.com <ga...@spotify.com> #13
Hi!
These extension functions are not written by us. This is part of the Kotlin standard library:
If this would be fixed by r8 desugaring that would be great!
We could definitely change our calls, but that doesn't change the fact that this is hard to detect if not impossible if you are not using android studio.
bi...@google.com <bi...@google.com> #14
Soren, can you share our status regarding supporting jdk21 apis? I assume we don't have support yet because it is quite new. However, given the conflict is between kotlin standard library vs jdk21, it is more helpful than others.
(Based on my research, the compiler only raises warning instead of error when the kotlin extension function is shadowed by the jdk21 function. )
dm...@spotify.com <dm...@spotify.com> #15
an...@crowdstrike.com <an...@crowdstrike.com> #16
thanks for taking the time to file this issue and for creating a simple test case, Spotify team. We've been
If you're in need of a workaround, the straightforward one is to replace the calls with code that has the same semantics (see
This behaviour change has now also been documented at the
dm...@spotify.com <dm...@spotify.com> #17
lb...@gmail.com <lb...@gmail.com> #18
It's just removing the first item of a list...
Are there other weird changes that break stuff?
Also why should it affect API 34, if API 35 is the newest one, and should have backward compatibility when not targeting it anyway...
lb...@gmail.com <lb...@gmail.com> #19
sg...@google.com <sg...@google.com> #20
This is an unfortunate interaction between the Android runtime evolving and how Kotlin extension functions works. See
We did consider adding backporting/desugaring, using code like this, but decided against it.
public static <T> T removeFirst(List<T> list) {
if (Build.VERSION.SDK_INT >= 35) {
return list.removeFirst();
} else if (list.getClass() == ArrayList.class) {
return list.remove(0);
} else {
try {
Method removeFirst =
list.getClass().getDeclaredMethod("removeFirst");
try {
return (T) removeFirst.invoke(list);
} catch (IllegalAccessException
| InvocationTargetException e) {
throw new RuntimeException(
"removeFirst backport failed", e);
}
} catch (NoSuchMethodException e) {
return list.remove(0);
}
}
}
sg...@google.com <sg...@google.com>
b9...@gmail.com <b9...@gmail.com> #21
This kind of issue seems to be more and more in the future. Does this mean that there will be more kotlin extension apis will conflict with java and we couldn't use them? That's weird.
sg...@google.com <sg...@google.com> #22
The lint error and quick fix mentioned in
mh...@snapchat.com <mh...@snapchat.com> #23
I can't find much info in bug reports specifically regarding the .reversed method, but it seems related to this as it's also part of the new SequencedCollection class.
We can get around this issue by making our own .reversed method (pretty much just an extension function wrapper with a different name), but ideally devs would be able to continue using the standard kotlin method that they are familiar with.
b9...@gmail.com <b9...@gmail.com> #24
sg...@google.com <sg...@google.com> #25
@mh...@snapchat.com, thank you for the reporting the issue with reversed
. Can you give some additional information on how to reproduce that. If I use Kotlin 1.9.22 or 2.0.10 and the following code
fun removeFirst(list: MutableList<String>) {
list.removeFirst()
}
fun reversed(list: MutableList<String>) {
list.reversed()
}
I get a lint warning on removeFirst
, but not on reversed
, as kotlinc generated this code:
public static final void reversed(java.util.Collection<java.lang.String>);
descriptor: (Ljava/util/Collection;)V
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: ldc #12 // String list
3: invokestatic #18 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
6: aload_0
7: checkcast #49 // class java/lang/Iterable
10: invokestatic #52 // Method kotlin/collections/CollectionsKt.reversed:(Ljava/lang/Iterable;)Ljava/util/List;
13: pop
14: return
LineNumberTable:
line 20: 6
line 21: 14
LocalVariableTable:
Start Length Slot Name Signature
0 15 0 list Ljava/util/Collection;
Signature: #47 // (Ljava/util/Collection<Ljava/lang/String;>;)V
RuntimeInvisibleParameterAnnotations:
parameter 0:
0: #8()
org.jetbrains.annotations.NotNull
The list is cast to java.lang.Iterable
(not sure why kotlinc decides to do that), and Iterable
does not reversed method, so resolution with the android-35 SDK with SequencedCollection
included will not find a reversed
method and the extension method is used as before. This results in the following DEX:
.method public static final removeFirst(Ljava/util/List;)V
.locals 1
.param p0, "list" # Ljava/util/List;
.annotation system Ldalvik/annotation/Signature;
value = {
"(",
"Ljava/util/List<",
"Ljava/lang/String;",
">;)V"
}
.end annotation
const-string v0, "list"
invoke-static {p0, v0}, Lkotlin/jvm/internal/Intrinsics;->checkNotNullParameter(Ljava/lang/Object;Ljava/lang/String;)V
.line 16
invoke-interface {p0}, Ljava/util/List;->removeFirst()Ljava/lang/Object;
.line 17
return-void
.end method
.method public static final reversed(Ljava/util/List;)V
.locals 1
.param p0, "list" # Ljava/util/List;
.annotation system Ldalvik/annotation/Signature;
value = {
"(",
"Ljava/util/List<",
"Ljava/lang/String;",
">;)V"
}
.end annotation
const-string v0, "list"
invoke-static {p0, v0}, Lkotlin/jvm/internal/Intrinsics;->checkNotNullParameter(Ljava/lang/Object;Ljava/lang/String;)V
.line 20
move-object v0, p0
check-cast v0, Ljava/lang/Iterable;
invoke-static {v0}, Lkotlin/collections/CollectionsKt;->reversed(Ljava/lang/Iterable;)Ljava/util/List;
.line 21
return-void
.end method
tr...@gmail.com <tr...@gmail.com> #26
This happens with reversed
in our codebase too, when it is invoked on the Stack
object:
java.lang.NoSuchMethodError: No virtual method reversed()Ljava/util/List; in class Ljava/util/Stack; or its super classes (declaration of 'java.util.Stack' appears in /apex/com.android.art/javalib/core-oj.jar)
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
freeCompilerArgs = ['-Xjvm-default=enable']
jvmTarget = JavaVersion.VERSION_17.toString()
}
kotlin = "1.8.10"
mh...@snapchat.com <mh...@snapchat.com> #27
If you:
- start an empty android project in android studio,
- set your kotlin version to 1.8.10,
- and then put a call to `reversed` in your main activity
You should see the app hit a runtime exception on compilesdk 35 with a sdk 34 test device. If you then upgrade the project to kotlin 1.9.0 you should see the issue goes away. In the decompiled code you can see the kotlin 1.8 test project is trying to reference the `reversed` method from java.util.list, whereas the kotlin 1.9 example correctly references the extension function.
Decompiled code for `Kotlin 1.8.10` + `compilesdk 35`:
```
.line 22
.local v0, "mylist":Ljava/util/List;
invoke-interface {v0}, Ljava/util/List;->reversed()Ljava/util/List;
move-result-object v1
.line 23
.local v1, "reversed":Ljava/util/List;
```
Decompiled code for `Kotlin 1.9.0` + `compilesdk 35`:
```
.line 22
.local v0, "mylist":Ljava/util/List;
move-object v1, v0
check-cast v1, Ljava/lang/Iterable;
invoke-static {v1}, Lkotlin/collections/CollectionsKt;->reversed(Ljava/lang/Iterable;)Ljava/util/List;
move-result-object v1
.line 23
.local v1, "reversed":Ljava/util/List;
```
I recall seeing that kotlin 1.9 has support for newer JDK versions such as 20 and 21, which android sdk 35 seems to use under the hood. Perhaps that is what prevents kotlin 1.9 from hitting this issue? I'm not sure but if someone knows I'd be very curious to hear why this is happening.
It might also be helpful to mention this kotlin 1.8 / `reversed` issue somewhere in the upgrade documentation. I'm not yet sure though what are all the methods are affected.
eo...@gmail.com <eo...@gmail.com> #28
Our own code can be updated to match this without problem. But what about those Kotlin extensions and other libraries? Our app is crashing with Kotlin Refecltion. We are using it for safety check in JSON parsing.
So, what is the recommended way to deal with this situation?
sg...@google.com <sg...@google.com> #29
If libraries are compiled with a bootclasspath without removeFirst
/removeLast
, then kotlinc will have used the extension function, and the library will not have calls to removeFirst
/removeLast
. If the library is compiled with a bootclasspath with removeFirst
/removeLast
, then they will stay, and there is not much you can do as a user of the dependency. However, the provider of the dependency should have gotten lint warnings about using APIs only present from API level 35.
If you have Kotlin reflect issues related to this, please open a new issue on that.
vn...@gmail.com <vn...@gmail.com> #30
Android lint check didn't report the "reversed()" method being called.
sa...@gmail.com <sa...@gmail.com> #31
sg...@google.com <sg...@google.com> #32
Which lint warning are you getting for removeFirst
/removeLast
? Is it the one to rewrite the to use removeAt
?
Description
There are conflicting kotlin extension functions and methods in the java standard library in JDK 21. This leads to issues at runtime.
An example would be the following class:
That compiled to use the kotlin extension when compiling with android-34. The bytecode becomes
// Method kotlin/collections/CollectionsKt.removeFirst:(Ljava/util/List;)Ljava/lang/Object;
.However since android-35 compiles using the JDK 21 standard library shipped in the android.jar we are now instead having the code above compile to:
// InterfaceMethod java/util/List.removeFirst:()Ljava/lang/Object;
This works well on android-15 devices, but crashes on android-14 devices since the methods doesn't exist there at runtime.