Unexpected `pointer being freed was not allocated` in tests but OK outside

Hi there.

I am new at Rust, come from 4y+ exp of Go, so, maybe my problems comes from my brain that is not fully adapted yet for rust. Whatever.

I trying to rewrite my own error-logging Go framework on Rust as my pet-project.

It is a shortened gist (don't want to paste it as a part of message, there 200+ lines).
It's a first part of error module. There are error's namespaces, classes, etc. Gist contains only those code that is necessary to build this example.

So, there are tests submodule to run tests and main func to run as a binary.

If I run a test like cargo test class_get -- --nocapture sometimes it leads to

Namespace: "Common", ID: 1
ekaru_test-16fc58a13eeab6a4(10944,0x70000e3f3000) malloc: *** error for object 0xa600a1ffff009c: pointer being freed was not allocated
ekaru_test-16fc58a13eeab6a4(10944,0x70000e3f3000) malloc: *** set a breakpoint in malloc_error_break to debug

ofc addresses are vary.

Because of output (there only 1st is printed of 2 expected), I guess there is some problem in representing Class as string and generate that format string. Something trying to be freed that should not. And I don't understand what. And how may I fix that.

Moreover. I run both of test (using command above) and binary in both of envs: debug, release. A lot of times. Both with and w/o recompilation. I never got that error if I run a binary. That's very strange (for me) even more.

So, I have one general question and one additional.
A general:
What goes wrong? Why? And how may I fix that or avoid?

Additionally.
You may notice an unsafe function with an expected name. It does one unforgivable thing. & -> &mut.
I may guarantee that it's OK, cause using that function it overwrites a different parts of RAM, because it's a vector and each time it addresses to different pieces (because of atomic inc). And it's a vector that will never move nor its data (caused by growth). It will never grow.
However, I think, it's bad. I think, I should not do that. Does I have a way achieve what I want somehow not violating a rule that is prohibited even by nomicon? Or it's the only way?

rustc 1.49.0, macOS 11.4

Use a mutex to guard the global data.

  • Transmuting a &T to &mut T is never correct.

Using set_len in that way is wrong because you must have initialized the vector up to that length. You might as well fill up the vector with dummy initialized values, because that step will only happen once.

I thought about mutexes but one of the goals was "Do not spend resources for mutexes to protect data, that is correct for RO access if you may access it". So, having a vector filled with correct values up to N, we can access [0..N]. The code that creates and inserts a new value to vector should store returned &'static ref and then use it or expose for another. No matter what of those will be used, the new value already ready to be accessed.

Mutex is obvious and simple solution. Can I do something else?


About set_len. Thank you. I don't test it yet, but bet you're right. Do I understand correctly, that in this case a String destructor tries to free allocated RAM and can't do that because of incorrect ptr to heap data? By the way, what String object in this case is? What ptr is? NULL? C-like undefined uninitialized?

But even if I sure you're right and this is the root of problem, why I got that error only if I run tests?

Your error (one of, anyway) is caused because you are dropping Namespace's that were created with uninitialized memory.

Specifically, this line:

dest[(nid-1) as usize] = Namespace{ id: nid, name };

This will take the element of the vector at that position which is uninitialized because of your use of set_len() , drop it, and insert that new Namespace.

Because that dropping involves dropping its fields, one of which is a string which needs to drop its allocated heap memory (if any), doing this is very very bad.

2 Likes

This exactly. You didn't initialize the vector so any spare capacity is C like uninitialized.

When you invoke undefined behaviour (the very same UB as in C), you lose all guarantees about what your program may do. It may crash, produce invalid results, or most insidiously, it may work as intended.

I highly reccomend you run yout code through MIRI. It will catch many of thrse sorts of errors, as well as many others.

2 Likes

The minimal tool you need to go from &T to &mut U is
https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html
Cell/RefCell/Mutex/etc. are all built on top of this type, because it is a type known to the compiler, that allows shared mutability without UB.

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.