Sanitizer runtimes produce symbol confilcts or leave undefined symbols

According to the rust documentation, build.rustflags should be used to pass a flag to every compiler process, even the dependencies. So we can use it to apply a sanitizer that requires a runtime (e.g., -Zsanitizer=address). Then I add to the cargo project folder a .cargo/config file, which only contains:

[build]
rustflags = ["-Zsanitizer=address"]

If I do it in a clean project (cargo new test), the compilation process works (cargo build --release). But the compilation fails as soon as I add proc-macro-error (1.0.4) as a dependency (through Cargo.toml). The error happens when trying to compile proc-macro-error, after successfully compiling all its dependencies.
Generally, I observe that this happens to whatever rlib dependency that at it's time depends on a proc-macro. I don't know if there's an additional constraint, but I discuss the dependency tree below.

With the current nightly rust compiler (1.71.0) it gives the next error:

error[E0519]: the current crate is indistinguishable from one of its dependencies: it has the same crate-name `proc_macro_error_attr` and was compiled with the same `-C metadata` arguments. This will result in symbol conflicts between the two.
   --> ~/.cargo/registry/src/index.crates.io-6f17d22bba15001f/proc-macro-error-1.0.4/src/lib.rs:284:9
    |
284 | pub use proc_macro_error_attr::proc_macro_error;
    |         ^^^^^^^^^^^^^^^^^^^^^

But if I build rustc myself the nightly version of the repository (tag 1.69.0 in the repository) and add rustc = "BUILD_DIR_BIN/rustc" to the aforementioned .cargo/config, I get a different error over the same source line, telling about the sanitizer runtime symbol not being defined:

error: .../test/target/release/deps/libproc_macro_error_attr-363b79ca82bc726b.so: undefined symbol: __asan_option_detect_stack_use_after_return
   --> ~/.cargo/registry/src/index.crates.io-6f17d22bba15001f/proc-macro-error-1.0.4/src/lib.rs:284:9
    |
284 | pub use proc_macro_error_attr::proc_macro_error;
    |         ^^^^^^^^^^^^^^^^^^^^^

For reference, if I run cargo tree I get the next output:

test v0.1.0 (.../test)
└── proc-macro-error v1.0.4
    ├── proc-macro-error-attr v1.0.4 (proc-macro)
    │   ├── proc-macro2 v1.0.56
    │   │   └── unicode-ident v1.0.8
    │   └── quote v1.0.26
    │       └── proc-macro2 v1.0.56 (*)
    │   [build-dependencies]
    │   └── version_check v0.9.4
    ├── proc-macro2 v1.0.56 (*)
    ├── quote v1.0.26 (*)
    └── syn v1.0.109
        ├── proc-macro2 v1.0.56 (*)
        └── unicode-ident v1.0.8
    [build-dependencies]
    └── version_check v0.9.4

The crate types are:

  • bin for test
  • proc-macro for proc-macro-aror-attr
  • rlib for unicode-ident and proc-macro2

If I use any other sanitizer, I get the missing symbol problem for another runtime (e.g., __tsan_read8 for thread sanitizer). The reason I add the custom flags and compiler over .cargo/config is because the build.rustflags aren't used if this is done over Cargo.toml. Still, when calling cargo build and adding the --config flag with the paramenters mentioned, I reproduce the same behavior as with .cargo/config.

When looking more in detail (by using verbose output of cargo), the exact same proc-macro2 is compiled twice but with different output directories (due to metadata) and optimization (it adds -C opt-level=3 to one of those instances).

This behavior doesn't sound right to me, let me know if I'm doing something wrong or is the expected to be like that. I haven't found any information regarding a similar issue, so I consider this to not have been discussed before. I'm happy to discuss and potentially fix the problem, but I would need to hear from someone more experienced about the root cause. Should I post this on the github issues or internals discourse?

Using the cargo flag --target ${RUSTC_TARGET} I'm able to don't raise those errors. But when looking at the verbose output from cargo, not all the rustc commands have the rustflags specified in .cargo/config. What's the criteria to use a one target directory or another and therefore the specified flags (target/release vs target/${RUST_TARGET}/release?

Build scripts and proc macros that need to be build for the host will end up in target/release, while crates that end up in the final compilation output will end up in target/$TARGET/release.

1 Like

Still, I find that cargo invokes rustc without those arguments for certain lib crate types (e.g., quote and unidoce-ident), it is because they are dependencies of a proc-macro crate type? There are indeed 2 code invokations for unicode-ident (each going in a different target directory) and only one for quote.

But I have kind of basic question, does then all the dependency of the proc-macro crate type miss the sanitization? In this case proc-macro-error will use functions of their dependencies, or it is limited to do so?

Procedural macros run at build time anyway, their code isn't present in the final sanitized binary at all.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.