mozilla / uniffi-rs

a multi-language bindings generator for rust
https://mozilla.github.io/uniffi-rs/
Mozilla Public License 2.0
2.68k stars 219 forks source link

java.lang.VerifyError: Bad type on operand stack #1390

Open Lonami opened 1 year ago

Lonami commented 1 year ago

My uniffi version is 0.21.0. I think there might be an issue when trying to construct uniffi types within a Jetpack Compose @Preview @Composable, but I'm not sure if it's a problem with uniffi, the Kotlin compiler, the Jetpack library, or my own code.

My .udl contains something like this:

enum Formatting {
    "Bold",
    "Italic",
    "Pre",
};

dictionary TextFormat {
    Formatting format;
    i32 offset;
    i32 length;
    string? extra;
};

dictionary Message {
    i32 id;
    string sender;
    string text;
    timestamp date;
    timestamp? edit_date;
    sequence<TextFormat> formatting;
};

I have the following Kotlin code constructing the Message instance:

@Preview
@Composable
fun MessagePreview() {
    val msg = remember {
        Message(
            id = 1,
            sender = "Alice",
            text = "Testing",
            date = Instant.now(),
            editDate = null,
            formatting = listOf(),
        )
    }

    Text(text = msg.text)
}
Trimmed build.gradle ```gradle // using gradle-7.4 // project buildscript { ext { compose_ui_version = '1.2.1' } } plugins { id 'com.android.application' version '7.2.2' apply false id 'com.android.library' version '7.2.2' apply false id 'org.jetbrains.kotlin.android' version '1.6.10' apply false id "org.mozilla.rust-android-gradle.rust-android" version "0.9.3" } // module plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' id "org.mozilla.rust-android-gradle.rust-android" } android { namespace 'com.example.myapp' compileSdk 33 defaultConfig { applicationId "com.example.myapp" minSdk 26 targetSdk 33 versionCode 1 versionName "0.1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary true } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion '1.1.1' } packagingOptions { resources { excludes += '/META-INF/{AL2.0,LGPL2.1}' } } ndkVersion '25.1.8937393' } dependencies { implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1" implementation 'androidx.activity:activity-compose:1.6.0' implementation "androidx.compose.ui:ui:$compose_ui_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version" implementation 'androidx.compose.material:material:1.2.1' implementation "androidx.navigation:navigation-compose:2.5.2" implementation "net.java.dev.jna:jna:5.12.0@aar" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_ui_version" debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_version" debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version" } ```

The render preview fails as follows:

Stack Trace ``` java.lang.VerifyError: Bad type on operand stack Exception Details: Location: uniffi/myapp/LiveLiterals$MyappKt.Byte$arg-0$call-put$branch$if$fun-write$class-FfiConverterOptionalString()B @11: ireturn Reason: Type 'java/lang/Object' (current frame, stack[0]) is not assignable to integer Current Frame: bci: @11 flags: { } locals: { 'uniffi/myapp/LiveLiterals$MyappKt' } stack: { 'java/lang/Object' } Bytecode: 0000000: 2a13 030c 1303 7401 b803 1cac at uniffi.myapp.Message.(myapp.kt) at com.example.myapp.ui.screens.SomeScreenKt.MessagePreview(SomeScreen.kt:151) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at androidx.compose.ui.tooling.ComposableInvoker.invokeComposableMethod(ComposableInvoker.kt:155) at androidx.compose.ui.tooling.ComposableInvoker.invokeComposable(ComposableInvoker.kt:195) at androidx.compose.ui.tooling.ComposeViewAdapter$init$3$1$composable$1.invoke(ComposeViewAdapter.kt:590) at androidx.compose.ui.tooling.ComposeViewAdapter$init$3$1$composable$1.invoke(ComposeViewAdapter.kt:588) at androidx.compose.ui.tooling.ComposeViewAdapter$init$3$1.invoke(ComposeViewAdapter.kt:625) at androidx.compose.ui.tooling.ComposeViewAdapter$init$3$1.invoke(ComposeViewAdapter.kt:583) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34) at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228) at androidx.compose.ui.tooling.InspectableKt.Inspectable(Inspectable.kt:61) at androidx.compose.ui.tooling.ComposeViewAdapter$WrapPreview$1.invoke(ComposeViewAdapter.kt:531) at androidx.compose.ui.tooling.ComposeViewAdapter$WrapPreview$1.invoke(ComposeViewAdapter.kt:530) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34) at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228) at androidx.compose.ui.tooling.ComposeViewAdapter.WrapPreview(ComposeViewAdapter.kt:525) at androidx.compose.ui.tooling.ComposeViewAdapter.access$WrapPreview(ComposeViewAdapter.kt:124) at androidx.compose.ui.tooling.ComposeViewAdapter$init$3.invoke(ComposeViewAdapter.kt:583) at androidx.compose.ui.tooling.ComposeViewAdapter$init$3.invoke(ComposeViewAdapter.kt:580) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34) at androidx.compose.ui.platform.ComposeView.Content(ComposeView.android.kt:402) at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:248) at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:247) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34) at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228) at androidx.compose.ui.platform.CompositionLocalsKt.ProvideCommonCompositionLocals(CompositionLocals.kt:177) at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals.android.kt:123) at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals.android.kt:122) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34) at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228) at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt.ProvideAndroidCompositionLocals(AndroidCompositionLocals.android.kt:114) at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$3.invoke(Wrapper.android.kt:157) at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$3.invoke(Wrapper.android.kt:156) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34) at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228) at androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:156) at androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:140) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34) at androidx.compose.runtime.ActualJvm_jvmKt.invokeComposable(ActualJvm.jvm.kt:74) at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3193) at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3183) at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:252) at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source) at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3183) at androidx.compose.runtime.ComposerImpl.composeContent$runtime_release(Composer.kt:3119) at androidx.compose.runtime.CompositionImpl.composeContent(Composition.kt:584) at androidx.compose.runtime.Recomposer.composeInitial$runtime_release(Recomposer.kt:811) at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:519) at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:140) at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:131) at androidx.compose.ui.platform.AndroidComposeView.setOnViewTreeOwnersAvailable(AndroidComposeView.android.kt:1015) at androidx.compose.ui.platform.WrappedComposition.setContent(Wrapper.android.kt:131) at androidx.compose.ui.platform.WrappedComposition.onStateChanged(Wrapper.android.kt:182) at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:360) at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:202) at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:138) at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:131) at androidx.compose.ui.platform.AndroidComposeView.onAttachedToWindow(AndroidComposeView.android.kt:1102) at android.view.View.dispatchAttachedToWindow(View.java:20753) at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3490) at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3497) at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3497) at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3497) at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3497) at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3497) at android.view.AttachInfo_Accessor.setAttachInfo(AttachInfo_Accessor.java:58) at com.android.layoutlib.bridge.impl.RenderSessionImpl.inflate(RenderSessionImpl.java:366) at com.android.layoutlib.bridge.Bridge.createSession(Bridge.java:436) at com.android.tools.idea.layoutlib.LayoutLibrary.createSession(LayoutLibrary.java:121) at com.android.tools.idea.rendering.RenderTask.createRenderSession(RenderTask.java:717) at com.android.tools.idea.rendering.RenderTask.lambda$inflate$9(RenderTask.java:873) at com.android.tools.idea.rendering.RenderExecutor$runAsyncActionWithTimeout$3.run(RenderExecutor.kt:192) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) at java.base/java.lang.Thread.run(Thread.java:829) ```

Because the error says:

java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    uniffi/myapp/LiveLiterals$MyappKt.Byte$arg-0$call-put$branch$if$fun-write$class-FfiConverterOptionalString()B @11: ireturn

I assumed it might be an issue with uniffi, but I totally understand it might be some problem on my end. If that's the case, please don't hesitate to close this issue.

Generated FfiConverterOptionalString ```kotlin public object FfiConverterOptionalString: FfiConverterRustBuffer { override fun read(buf: ByteBuffer): String? { if (buf.get().toInt() == 0) { return null } return FfiConverterString.read(buf) } override fun allocationSize(value: String?): Int { if (value == null) { return 1 } else { return 1 + FfiConverterString.allocationSize(value) } } override fun write(value: String?, buf: ByteBuffer) { if (value == null) { buf.put(0) } else { buf.put(1) FfiConverterString.write(value, buf) } } } ```

Getting rid of string? extra in TextFormat yields a similar error, but this time the location is different:

uniffi/myapp/LiveLiterals$MyappKt.Byte$arg-0$call-put$branch$if$fun-write$class-FfiConverterOptionalTimestamp()B @11: ireturn

Getting rid of that fails elsewhere in my project with a different unrelated optional. It might come down to the way Kotlin is initializing stuff, but I really have no idea.

┆Issue is synchronized with this Jira Task ┆friendlyId: UNIFFI-208

Lonami commented 1 year ago

If I comment out all offending FfiConverter until the preview stops complaining (and replace their uses in method definitions with a TODO()), the @Preview starts working again. I don't think the implementation itself (read, allocationSize and write) have any part on this. It might be the way the sub-classing is defined (as in FfiConverterTypeTextFormat : FfiConverterRustBuffer<TextFormat>). But I really have no idea why it could possibly fail with VerifyError… Maybe it's the way the interface is defined?

interface FfiConverterRustBuffer<KotlinType> : FfiConverter<KotlinType, RustBuffer.ByValue>

But this really seems like a bug in the compiler, perhaps being unable these generics.

What is most strange to me is that the application runs fine when not inside a @Preview.

badboy commented 1 year ago

It's definitely not easily reproducable in just plain Kotlin. Could you put a full failing example into a repository so I can run and test it? Might be easier than me trying to piece together the parts to get it to build. I'm happy to take a look then.

Lonami commented 1 year ago

Thanks a lot for taking the time to look into this. I've created a new project from a Compose template, and after configuring the Rust plugins and uniffi and setting up the NDK version, setup the code necessary to trigger the issue.

I've published the failing code at https://github.com/Lonami/VerifyError-Repro.

Please let me know if you need any more details to import or run the project, or if I can help in any other way. After importing the project, assuming both the NDK and uniffi are configured and installed in the system correctly, it should be possible to open MainActivity.kt in Android Studio and select the Split or Design view (to the right) to trigger the VerifyError during the @Preview of DefaultPreview:

image

(The squiggly line under Instant.now() is Call requires API level 26 (current min is 21): java.time.Instant#now, which can be safely ignored for this demo.)

The code generated by uniffi-bindgen is placed at app/build/generated/source/uniffi/debug/java/uniffi/verifyerror:

image

The code generation is done by app/build.gradle (at the bottom of the file, android.applicationVariants.all { ...) and invokes the following command:

commandLine 'uniffi-bindgen', 'generate', '../native/src/verifyerror.udl', '--language', 'kotlin', '--no-format', '--out-dir', "${buildDir}/generated/source/uniffi/${variant.name}/java"
badboy commented 1 year ago

I spent a bit of time on it this morning. I am able to reproduce it using the provided code. I'm fairly sure now this is some miscompilation in whatever does the preview rendering. I don't think the uniffi code is inherently wrong. But I also don't know where the buggy behavior is hiding.

Unfortunately I can't spent much more time on this right now.

Lonami commented 1 year ago

Thanks, yeah, it's probably safe to close this issue as the bug is unlikely to be uniffi's fault (technically, I'm sure the code could be generated in a different way to not trigger this problem, but that's a workaround and not the right solution).

Lonami commented 11 months ago

The repository I mentioned earlier was deleted but here's the main contents of the files:

MainActivity.kt ```kt package com.example.verifyerror import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import com.example.verifyerror.ui.theme.VerifyErrorReproTheme import uniffi.verifyerror.Message import java.time.Instant class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { VerifyErrorReproTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background ) { Greeting("Android") } } } } } @Composable fun Greeting(name: String) { Text(text = "Hello $name!") } @Preview(showBackground = true) @Composable fun DefaultPreview() { val msg = remember { Message( id = 1, sender = "Alice", text = "Testing", date = Instant.now(), editDate = null, formatting = listOf(), ) } Text(text = msg.text) } ``` AndroidManifest.xml ```xml ``` build.gradle ```gradle plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' id "org.mozilla.rust-android-gradle.rust-android" } android { namespace 'com.example.verifyerror' compileSdk 32 defaultConfig { applicationId "com.example.verifyerror" minSdk 21 targetSdk 32 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary true } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion '1.1.1' } packagingOptions { resources { excludes += '/META-INF/{AL2.0,LGPL2.1}' } } ndkVersion '25.1.8937393' } dependencies { implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' implementation 'androidx.activity:activity-compose:1.3.1' implementation "androidx.compose.ui:ui:$compose_ui_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version" implementation 'androidx.compose.material:material:1.1.1' implementation "net.java.dev.jna:jna:5.12.0@aar" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_ui_version" debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_version" debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version" } cargo { module = "../native" libname = "uniffi_verifyerror" targets = ["arm64"] profile = 'release' } tasks.whenTaskAdded { task -> if ((task.name == 'javaPreCompileDebug' || task.name == 'javaPreCompileRelease')) { task.dependsOn 'cargoBuild' } } android.applicationVariants.all { variant -> def t = tasks.register("generate${variant.name.capitalize()}UniFFIBindings", Exec) { workingDir "${project.projectDir}" commandLine 'uniffi-bindgen', 'generate', '../native/src/verifyerror.udl', '--language', 'kotlin', '--no-format', '--out-dir', "${buildDir}/generated/source/uniffi/${variant.name}/java" } variant.javaCompileProvider.get().dependsOn(t) def sourceSet = variant.sourceSets.find { it.name == variant.name } sourceSet.java.srcDir new File(buildDir, "generated/source/uniffi/${variant.name}/java") } ```

Maybe this can be closed as stale.