uniffi-rs
uniffi-rs copied to clipboard
Improve linker error reporting during ComponentInterface checksum mismatch to avoid time wastage
If an interface has changed, and one side of the bindings (eg gecko_js) has been regenerated, but the other (eg .rs) hasn't, linking fails by design, but in an impressively inscrutable way:
0:11.34 toolkit/library/build/XUL
0:11.36 warning: /Users/dmose/r/hg-mc/toolkit/library/rust/shared/Cargo.toml: dependency (uniffi_build) specified without providing a local path, Git repository, or version to use. This will be considered an error in future versions
0:14.18 Finished dev [optimized + debuginfo] target(s) in 2.84s
0:19.79 Undefined symbols for architecture x86_64:
0:19.79 "_nimbus_3b6_NimbusClient_opt_out", referenced from:
0:19.82 __ZN7mozilla3dom12NimbusClient6OptOutERK12nsTSubstringIDsERNS_11ErrorResultE in Unified_cpp_components_nimbus0.o
0:20.04 "_ffi_nimbus_3b6_rustbuffer_reserve", referenced from:
0:20.07 __ZN7mozilla3dom13nimbus_detail6Writer9WriteInt8ERKa in Unified_cpp_components_nimbus0.o
0:20.07 __ZN7mozilla3dom13nimbus_detail6Writer11WriteStringERK12nsTSubstringIDsE in Unified_cpp_components_nimbus0.o
0:20.29 "_nimbus_3b6_NimbusClient_opt_in_with_branch", referenced from:
0:20.32 __ZN7mozilla3dom12NimbusClient15OptInWithBranchERK12nsTSubstringIDsES5_RNS_11ErrorResultE in Unified_cpp_components_nimbus0.o
0:20.53 "_nimbus_3b6_NimbusClient_reset_enrollment", referenced from:
0:20.56 __ZN7mozilla3dom12NimbusClient15ResetEnrollmentERK12nsTSubstringIDsERNS_11ErrorResultE in Unified_cpp_components_nimbus0.o
0:20.78 "_nimbus_3b6_NimbusClient_get_active_experiments", referenced from:
0:20.81 __ZN7mozilla3dom12NimbusClient20GetActiveExperimentsER8nsTArrayINS0_18EnrolledExperimentEERNS_11ErrorResultE in Unified_cpp_components_nimbus0.o
0:21.03 "_ffi_nimbus_3b6_rustbuffer_free", referenced from:
0:21.06 __ZN7mozilla3dom13nimbus_detail6ViaFfiI8nsTArrayINS0_18EnrolledExperimentEE21nimbus_3b6_RustBufferLb0EE4LiftERKS6_RS5_ in Unified_cpp_components_nimbus0.o
0:21.06 __ZN7mozilla3dom13nimbus_detail6ViaFfiINS0_8NullableI9nsTStringIDsEEE21nimbus_3b6_RustBufferLb0EE4LiftERKS7_RS6_ in Unified_cpp_components_nimbus0.o
0:21.28 "_ffi_nimbus_3b6_NimbusClient_object_free", referenced from:
0:21.31 __ZN7mozilla3dom12NimbusClientD2Ev in Unified_cpp_components_nimbus0.o
0:21.52 "_nimbus_3b6_NimbusClient_get_experiment_branch", referenced from:
0:21.55 __ZN7mozilla3dom12NimbusClient19GetExperimentBranchERK12nsTSubstringIDsERS3_RNS_11ErrorResultE in Unified_cpp_components_nimbus0.o
0:21.77 "_ffi_nimbus_3b6_rustbuffer_alloc", referenced from:
0:21.80 __ZN7mozilla3dom12NimbusClient11ConstructorERNS0_12GlobalObjectERK12nsTSubstringIDsERKNS0_10AppContextES7_RKNS0_20RemoteSettingsConfigERKNS0_27AvailableRandomizationUnitsERNS_11ErrorResultE in Unified_cpp_components_nimbus0.o
0:21.80 __ZN7mozilla3dom13nimbus_detail6ViaFfiI12nsTSubstringIDsE21nimbus_3b6_RustBufferLb0EE5LowerERKS4_ in Unified_cpp_components_nimbus0.o
0:21.80 __ZN7mozilla3dom13nimbus_detail6ViaFfiINS0_20RemoteSettingsConfigE21nimbus_3b6_RustBufferLb1EE5LowerERKS3_ in Unified_cpp_components_nimbus0.o
0:22.01 "_nimbus_3b6_NimbusClient_update_experiments", referenced from:
0:22.04 __ZN7mozilla3dom12NimbusClient17UpdateExperimentsERNS_11ErrorResultE in Unified_cpp_components_nimbus0.o
0:22.26 "_nimbus_3b6_NimbusClient_new", referenced from:
0:22.29 __ZN7mozilla3dom12NimbusClient11ConstructorERNS0_12GlobalObjectERK12nsTSubstringIDsERKNS0_10AppContextES7_RKNS0_20RemoteSettingsConfigERKNS0_27AvailableRandomizationUnitsERNS_11ErrorResultE in Unified_cpp_components_nimbus0.o
0:22.87 ld: symbol(s) not found for architecture x86_64
0:22.97 clang-11: error: linker command failed with exit code 1 (use -v to see invocation)
0:22.97 make[4]: *** [XUL] Error 1
0:22.97 make[3]: *** [toolkit/library/build/target] Error 2
If know happens to know to strip this output of the extra Mac _ symbol character and feeds it to llvm-cxxfilt, one can get something marginally more comprehensible:
# cat build.log.txt | sed s/__/_/g | ~/.mozbuild/clang/bin/llvm-cxxfilt
[...]
0:11.34 toolkit/library/build/XUL
0:11.36 warning: /Users/dmose/r/hg-mc/toolkit/library/rust/shared/Cargo.toml: dependency (uniffi_build) specified without proxviding a local path, Git repository, or version to use. This will be considered an error in future versions
0:14.18 Finished dev [optimized + debuginfo] target(s) in 2.84s
0:19.79 Undefined symbols for architecture x86_64:
0:19.79 "_nimbus_3b6_NimbusClient_opt_out", referenced from:
0:19.82 mozilla::dom::NimbusClient::OptOut(nsTSubstring<char16_t> const&, mozilla::ErrorResult&) in Unified_cpp_components_nimbus0.o
0:20.04 "_ffi_nimbus_3b6_rustbuffer_reserve", referenced from:
0:20.07 mozilla::dom::nimbus_detail::Writer::WriteInt8(signed char const&) in Unified_cpp_components_nimbus0.o
0:20.07 mozilla::dom::nimbus_detail::Writer::WriteString(nsTSubstring<char16_t> const&) in Unified_cpp_components_nimbus0.o
0:20.29 "_nimbus_3b6_NimbusClient_opt_in_with_branch", referenced from:
0:20.32 mozilla::dom::NimbusClient::OptInWithBranch(nsTSubstring<char16_t> const&, nsTSubstring<char16_t> const&, mozilla::ErrorResult&) in Unified_cpp_components_nimbus0.o
0:20.53 "_nimbus_3b6_NimbusClient_reset_enrollment", referenced from:
0:20.56 mozilla::dom::NimbusClient::ResetEnrollment(nsTSubstring<char16_t> const&, mozilla::ErrorResult&) in Unified_cpp_components_nimbus0.o
0:20.78 "_nimbus_3b6_NimbusClient_get_active_experiments", referenced from:
0:20.81 mozilla::dom::NimbusClient::GetActiveExperiments(nsTArray<mozilla::dom::EnrolledExperiment>&, mozilla::ErrorResult&) in Unified_cpp_components_nimbus0.o
0:21.03 "_ffi_nimbus_3b6_rustbuffer_free", referenced from:
0:21.06 mozilla::dom::nimbus_detail::ViaFfi<nsTArray<mozilla::dom::EnrolledExperiment>, nimbus_3b6_RustBuffer, false>::Lift(nimbus_3b6_RustBuffer const&, nsTArray<mozilla::dom::EnrolledExperiment>&) in Unified_cpp_components_nimbus0.o
0:21.06 mozilla::dom::nimbus_detail::ViaFfi<mozilla::dom::Nullable<nsTString<char16_t> >, nimbus_3b6_RustBuffer, false>::Lift(nimbus_3b6_RustBuffer const&, mozilla::dom::Nullable<nsTString<char16_t> >&) in Unified_cpp_components_nimbus0.o
0:21.28 "_ffi_nimbus_3b6_NimbusClient_object_free", referenced from:
0:21.31 mozilla::dom::NimbusClient::~NimbusClient() in Unified_cpp_components_nimbus0.o
0:21.52 "_nimbus_3b6_NimbusClient_get_experiment_branch", referenced from:
0:21.55 mozilla::dom::NimbusClient::GetExperimentBranch(nsTSubstring<char16_t> const&, nsTSubstring<char16_t>&, mozilla::ErrorResult&) in Unified_cpp_components_nimbus0.o
0:21.77 "_ffi_nimbus_3b6_rustbuffer_alloc", referenced from:
0:21.80 mozilla::dom::NimbusClient::Constructor(mozilla::dom::GlobalObject&, nsTSubstring<char16_t> const&, mozilla::dom::AppContext const&, nsTSubstring<char16_t> const&, mozilla::dom::RemoteSettingsConfig const&, mozilla::dom::AvailableRandomizationUnits const&, mozilla::ErrorResult&) in Unified_cpp_components_nimbus0.o
0:21.80 mozilla::dom::nimbus_detail::ViaFfi<nsTSubstring<char16_t>, nimbus_3b6_RustBuffer, false>::Lower(nsTSubstring<char16_t> const&) in Unified_cpp_components_nimbus0.o
0:21.80 mozilla::dom::nimbus_detail::ViaFfi<mozilla::dom::RemoteSettingsConfig, nimbus_3b6_RustBuffer, true>::Lower(mozilla::dom::RemoteSettingsConfig const&) in Unified_cpp_components_nimbus0.o
0:22.01 "_nimbus_3b6_NimbusClient_update_experiments", referenced from:
0:22.04 mozilla::dom::NimbusClient::UpdateExperiments(mozilla::ErrorResult&) in Unified_cpp_components_nimbus0.o
0:22.26 "_nimbus_3b6_NimbusClient_new", referenced from:
0:22.29 mozilla::dom::NimbusClient::Constructor(mozilla::dom::GlobalObject&, nsTSubstring<char16_t> const&, mozilla::dom::AppContext const&, nsTSubstring<char16_t> const&, mozilla::dom::RemoteSettingsConfig const&, mozilla::dom::AvailableRandomizationUnits const&, mozilla::ErrorResult&) in Unified_cpp_components_nimbus0.o
0:22.87 ld: symbol(s) not found for architecture x86_64
0:22.97 clang-11: error: linker command failed with exit code 1 (use -v to see invocation)
0:22.97 make[4]: *** [XUL] Error 1
0:22.97 make[3]: *** [toolkit/library/build/target] Error 2
Since the linker is the thing giving the error message, it occurs to me that perhaps one could add a simple dummy symbol named something like:
__ffi_nimbus_306_if_u_see_this_in_a_linker_error_read_uniffi_rs_uniffi_bindgen_src_interface_mod_checksum_comment.rs
then, as long as all the symbols are resolving, the developer sees nothing, but when this set of symbols fails to resolve at least there's an (incredibly hacky, but hey...) breadcrumb to help them figure out what's going on.
┆Issue is synchronized with this Jira Task ┆Issue Number: UNIFFI-32
... which will nudge the reader to find this explanation
Aha, that explains so much!
When we added this code, we put the following in one of the comments:
/// The result will be an ugly inscrutable link-time error, but that is a lot
/// better than triggering potentially arbitrary memory unsafety!
Having been bitten by the promised "ugly inscrutable link-time error", I'm curious if you feel that it was indeed better than triggering potentially arbitrary memory unsafety (now that you know that that's what it was for).
We should definitely still figure out how to make the error more scrutable.
Given how often memory unsafety issues cause security holes and never even get found by the white hats, it seems like a reasonable trade to me, particularly if we up the scrutability.
@rfk If we go forward with the symbol hack I propose, maybe we'd do better to point it to the comment in the same file that you quoted, since it explains what's going on at a higher level.
FWIW, I wasted probably a couple of hours figuring out exactly what was going on here, which is the motivation for spending time on this bug.
For not too much more effort, I think we could start an FAQ section in the manual and provide a "check the uniffi faq about linker errors" hint using the technique you suggest.
The above failure errors are specific to a C++ backend, but the same general error can also occur in other languages. If anyone would like to have a go at improving the failure mode here, you can reproduce it as follows:
- Run
cargo test -p uniffi-example-geometryto generate scaffolding and bindings and run tests for the "geometry" example. - Edit
examples/geometry/src/geometry.udland change the interface, e.g. by adding a newdictionarymember.- The idea here is that this will change the public interface, thus changing its checksum, thus changing the names of all the FFI functions and triggering the linker error.
- Regenerate the foreign-language bindings without recompiling the Rust crate; here's what I did to regenerate the Kotlin bindings:
cargo run -p uniffi_bindgen -- generate ./examples/geometry/src/geometry.udl --out-dir ./target/debug/ -l kotlin
- Try to run the tests again without recompiling the Rust crate; here's what I did to run the Kotlin tests again:
cargo run -p uniffi_bindgen -- test ./target/debug ./examples/geometry/src/geometry.udl ./examples/geometry/tests/bindings/test_geometry.kts
This produced the following linker error:
java.lang.UnsatisfiedLinkError: Error looking up function 'ffi_geometry_7bfb_rustbuffer_alloc': dlsym(0x7f8de6f53c10, ffi_geometry_7bfb_rustbuffer_alloc): symbol not found
The broad suggestion in this bug is that maybe we can improve the failure mode here by defining a symbol with a helpful name, like ffi_geometry_7bdb_check_the_faq_about_linker_errors, and arranging for that symbol to be the first one that we try to link so that it's the one that generates the error message.
(In the specific case of Kotlin, this error also happens at runtime when the tests try to actually use the generated bindings; if would be neat if we could move this failure mode to load-time in all bindings)