Skip to content

Commit fe40d99

Browse files
committed
Fix JNA bug MethodTooLargeException #2340 by splitting out checksum fn from UniffiLib interface into separate one.
1 parent 2c003b1 commit fe40d99

File tree

2 files changed

+80
-19
lines changed

2 files changed

+80
-19
lines changed

uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt

+56-16
Original file line numberDiff line numberDiff line change
@@ -55,20 +55,65 @@ internal open class {{ ffi_struct.name()|ffi_struct_name }}(
5555
{%- endmatch %}
5656
{%- endfor %}
5757

58+
59+
{%- macro decl_kotlin_functions(func_list) -%}
60+
{% for func in func_list -%}
61+
fun {{ func.name() }}(
62+
{%- call kt::arg_list_ffi_decl(func) %}
63+
): {% match func.return_type() %}{% when Some with (return_type) %}{{ return_type.borrow()|ffi_type_name_by_value }}{% when None %}Unit{% endmatch %}
64+
{% endfor %}
65+
{%- endmacro %}
66+
67+
// For large crates we prevent `MethodTooLargeException` (see #2340)
68+
// N.B. the name of the extension is very misleading, since it is
69+
// rather `InterfaceTooLargeException`, caused by too many methods
70+
// in the interface for large crates.
71+
//
72+
// By splitting the otherwise huge interface into two parts
73+
// * UniffiLib
74+
// * UniffiLibChecksums (this)
75+
// And all checksum methods are put into `UniffiLibChecksums`
76+
// we allow for ~2x as many methods in the UniffiLib interface.
77+
internal interface UniffiLibChecksums : Library {
78+
{%- call decl_kotlin_functions(ci.iter_checksum_ffi_functions()) %}
79+
}
80+
5881
// A JNA Library to expose the extern-C FFI definitions.
5982
// This is an implementation detail which will be called internally by the public API.
60-
6183
internal interface UniffiLib : Library {
6284
companion object {
6385
internal val INSTANCE: UniffiLib by lazy {
64-
loadIndirect<UniffiLib>(componentName = "{{ ci.namespace() }}")
65-
.also { lib: UniffiLib ->
66-
uniffiCheckContractApiVersion(lib)
67-
uniffiCheckApiChecksums(lib)
68-
{% for fn in self.initialization_fns() -%}
69-
{{ fn }}(lib)
70-
{% endfor -%}
71-
}
86+
val componentName = "{{ ci.namespace() }}"
87+
// For large crates we prevent `MethodTooLargeException` (see #2340)
88+
// N.B. the name of the extension is very misleading, since it is
89+
// rather `InterfaceTooLargeException`, caused by too many methods
90+
// in the interface for large crates.
91+
//
92+
// By splitting the otherwise huge interface into two parts
93+
// * UniffiLib (this)
94+
// * UniffiLibChecksums
95+
// And all checksum methods are put into `UniffiLibChecksums`
96+
// we allow for ~2x as many methods in the UniffiLib interface.
97+
//
98+
// Thus we first load the library with `loadIndirect` as `UniffiLibChecksums`
99+
// so that we can call `uniffiCheckApiChecksums`...
100+
loadIndirect<UniffiLibChecksums>(componentName)
101+
.also { lib: UniffiLibChecksums ->
102+
uniffiCheckApiChecksums(lib)
103+
}
104+
// ... and then we load the library as `UniffiLib`
105+
// N.B. we cannot use `loadIndirect` once and then try to cast it to `UniffiLib`
106+
// => results in `java.lang.ClassCastException: com.sun.proxy.$Proxy cannot be cast to ...`
107+
// error. So we must call `loadIndirect` twice. For crates large enough
108+
// to trigger this issue, the performance impact is negligible, running on
109+
// a macOS M1 machine the `loadIndirect` call takes ~50ms.
110+
loadIndirect<UniffiLib>(componentName)
111+
.also { lib: UniffiLib ->
112+
uniffiCheckContractApiVersion(lib)
113+
{% for fn in self.initialization_fns() -%}
114+
{{ fn }}(lib)
115+
{% endfor -%}
116+
}
72117
}
73118
{% if ci.contains_object_types() %}
74119
// The Cleaner for the whole library
@@ -77,12 +122,7 @@ internal interface UniffiLib : Library {
77122
}
78123
{%- endif %}
79124
}
80-
81-
{% for func in ci.iter_ffi_function_definitions() -%}
82-
fun {{ func.name() }}(
83-
{%- call kt::arg_list_ffi_decl(func) %}
84-
): {% match func.return_type() %}{% when Some with (return_type) %}{{ return_type.borrow()|ffi_type_name_by_value }}{% when None %}Unit{% endmatch %}
85-
{% endfor %}
125+
{%- call decl_kotlin_functions(ci.iter_ffi_function_definitions_excluding_checksums()) %}
86126
}
87127

88128
private fun uniffiCheckContractApiVersion(lib: UniffiLib) {
@@ -96,7 +136,7 @@ private fun uniffiCheckContractApiVersion(lib: UniffiLib) {
96136
}
97137

98138
@Suppress("UNUSED_PARAMETER")
99-
private fun uniffiCheckApiChecksums(lib: UniffiLib) {
139+
private fun uniffiCheckApiChecksums(lib: UniffiLibChecksums) {
100140
{%- for (name, expected_checksum) in ci.iter_checksums() %}
101141
if (lib.{{ name }}() != {{ expected_checksum }}.toShort()) {
102142
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")

uniffi_bindgen/src/interface/mod.rs

+24-3
Original file line numberDiff line numberDiff line change
@@ -657,12 +657,33 @@ impl ComponentInterface {
657657
/// The set of FFI functions is derived automatically from the set of higher-level types
658658
/// along with the builtin FFI helper functions.
659659
pub fn iter_ffi_function_definitions(&self) -> impl Iterator<Item = FfiFunction> + '_ {
660-
self.iter_user_ffi_function_definitions()
660+
self.iter_ffi_function_definitions_conditionally_include_checksums(true)
661+
}
662+
663+
pub fn iter_ffi_function_definitions_excluding_checksums(
664+
&self,
665+
) -> impl Iterator<Item = FfiFunction> + '_ {
666+
self.iter_ffi_function_definitions_conditionally_include_checksums(false)
667+
}
668+
669+
fn iter_ffi_function_definitions_conditionally_include_checksums(
670+
&self,
671+
include_checksums: bool,
672+
) -> impl Iterator<Item = FfiFunction> + '_ {
673+
let iterator = self
674+
.iter_user_ffi_function_definitions()
661675
.cloned()
662676
.chain(self.iter_rust_buffer_ffi_function_definitions())
663677
.chain(self.iter_futures_ffi_function_definitions())
664-
.chain(self.iter_checksum_ffi_functions())
665-
.chain([self.ffi_uniffi_contract_version()])
678+
.chain([self.ffi_uniffi_contract_version()]);
679+
680+
// Conditionally determine if the checksums should be included or not.
681+
if include_checksums {
682+
Box::new(iterator.chain(self.iter_checksum_ffi_functions()))
683+
as Box<dyn Iterator<Item = FfiFunction> + '_>
684+
} else {
685+
Box::new(iterator) as Box<dyn Iterator<Item = FfiFunction> + '_>
686+
}
666687
}
667688

668689
/// Alternate version of iter_ffi_function_definitions for languages that don't support async

0 commit comments

Comments
 (0)