Skip to content

Some notes about the panic #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
edwin0cheng opened this issue May 26, 2019 · 8 comments
Open

Some notes about the panic #1

edwin0cheng opened this issue May 26, 2019 · 8 comments

Comments

@edwin0cheng
Copy link

edwin0cheng commented May 26, 2019

Hi, I am studying the https://github.com/fedochet/rust-proc-macro-expander which is an awesome project. And see the issue in rust-lang/rust#60593.

After a little bit of digging, my guess is, it is not related to ABI compatible problem. It seem to be related to the different of thread_local storage of static linkage and dynamic linkage.

The actual panic is this line.
And state is actually a thread_local :

https://github.com/rust-lang/rust/blob/dc6db14e1cd60012f25be4fd8d2eb96ea5b4bb68/src/libproc_macro/bridge/client.rs#L269

On the other hand, in your example code :

client.run function will call Bridge::enter and set the state to BridgeState::Connected. Note that this is statically link to main crate.

And after that, when the actual TokenStream start to parse, Bridge::with will be called from shared library crate and it will trigger the panic.

It seem to be two thread local are different, such that the panic get raised. But I don't know enough about linux shared librrary and thread local mechanism to understand this bug.

@fedochet
Copy link
Owner

@edwin0cheng hi! Thanks for your interest in proc macro expander :)

I have thought about this possibility too (the fact that there are two independent thread local variables), but, unfortunately, it does not explain why there is no panic when procedural macros itself is compiled with one rust version, and code which links it dynamically is compiled with another

I do agree that it does not relate to ABI compatibility, and I am not sure whether it's a bug or very strange behavior (from my point of view) that is expected

Also, the approach I am using in proc macro expander is mocked from the compiler itself; however, the compiler does not run into this kind of problems for some reason

@edwin0cheng
Copy link
Author

@fedochet It is so strange... For some reason i have to upgrade my Windows WSL from Ubuntu Xenial (16.04) to Bionic (18.04), and the bug is gone! And then i reinstall Xenial and confirm the bug still exists in there.

I start to strongly suspect it is related how dynamic linkage works and will try to investigate further.

@edwin0cheng
Copy link
Author

edwin0cheng commented May 30, 2019

Finally i found the actual reason of the bug.

I can confirmed that the thread_local i mentioned previously are different in Bridge::enter and Bridge::with (by inserting dbg! in rustc source code directly). And it is because Bridge::with use a wrong thread local coming from static linkage.

Why the static linkage is introduced ? Surprisingly it is introduced by syn crate we used to parse the input string to TokenStream. Seem like dynamic linker is "smart" enough to merge some code path from dynamic linkage to static linkage. And that's why it depends on how dynamic linker implementation.

Also, the approach I am using in proc macro expander is mocked from the compiler itself; however, the compiler does not run into this kind of problems for some reason

It is because in rustc they don't use procmacro::TokenStream, they use syntax::TokenStream :

https://github.com/rust-lang/rust/blob/9ebf47851a357faa4cd97f4b1dc7835f6376e639/src/libsyntax_ext/proc_macro_impl.rs#L17

use syntax::tokenstream::TokenStream;

// ....

impl base::AttrProcMacro for AttrProcMacro {
    fn expand<'cx>(&self,
                   ecx: &'cx mut ExtCtxt<'_>,
                   span: Span,
                   annotation: TokenStream,
                   annotated: TokenStream)
                   -> TokenStream {
        let server = proc_macro_server::Rustc::new(ecx);
        match self.client.run(&EXEC_STRATEGY, server, annotation, annotated) {
    // ....
}

So that's why in server.rs, it use a Server trait :

impl client::Client<fn(crate::TokenStream) -> crate::TokenStream> {
pub fn run<S: Server>(

So they don't introduce the static linkage of thread_local in their code.

@edwin0cheng
Copy link
Author

edwin0cheng commented May 30, 2019

it does not explain why there is no panic when procedural macros itself is compiled with one rust version, and code which links it dynamically is compiled with another

It also explains why there is no panic in here. Since if the version of static linkage are different from dynamically one, the dynamic linker will not try to merge its code path. So that both bridge::enter and bridge::with will use the same version of code. And that's why no panic occurred.

@fedochet
Copy link
Owner

@edwin0cheng that's a nice investigation, thanks a lot! I hope that, if you're right, there's something that could be done about it to prevent such kind of linkage issues, so expander could use macros compiled with the same version of rustc. Let's see what Eddyb would say about your theory

@edwin0cheng
Copy link
Author

Seem like we can solve this problem by adding a flag RTLD_DEEPBIND when loading the dynamic library:

More info:
https://stackoverflow.com/questions/34073051/when-we-are-supposed-to-use-rtld-deepbind

fedochet added a commit to fedochet/rust-proc-macro-expander that referenced this issue Jun 1, 2019
@fedochet
Copy link
Owner

fedochet commented Jun 2, 2019

@edwin0cheng I've tried to use RTLD_DEEPBIND and it indeed worked! Thanks again for your investigation on this question 👍

@edwin0cheng
Copy link
Author

edwin0cheng commented Jun 2, 2019

You are welcome :) Since I am planing to use rust-proc-macro-expander in other project but this bug really block that usage.

However, note that RTLD_DEEPBIND only works in linux and mac. FreeBSD / OpenBSD do not support it. If your use case included these two platform, maybe you would consider to use another parsing method instead of syn crate.

bors bot added a commit to rust-lang/rust-analyzer that referenced this issue Apr 11, 2020
3920: Implement expand_task and list_macros in proc_macro_srv r=matklad a=edwin0cheng

This PR finish up the remain `proc_macro_srv` implementation :

1. Added dylib loading code for proc-macro crate dylib. Note that we have to add some special flags for unix loading because of a bug in old version of glibc, see fedochet/rust-proc-macro-panic-inside-panic-expample#1 and rust-lang/rust#60593 for details.

2. Added tests for proc-macro expansion: We use a trick here by adding `serde_derive` to dev-dependencies and calling `cargo-metadata` for searching its dylib path, and expand it in our tests. 

[EDIT]
Note that this PR **DO NOT** implement the final glue code with rust-analzyer and proc-macro-srv yet.
 

Co-authored-by: Edwin Cheng <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants