-
Notifications
You must be signed in to change notification settings - Fork 184
Description
Hello! I have been trying out Criterion for a new C library, but I have run into a perplexing issue - not sure whether i'm doing something wrong or whether this is a bug, so any help would be much appreciated!
The crux of my issue (minimal code example below) is that if I have a struct that contains pointers to other structs etc. (e.g. binary tree or linked list), and I have a custom function for freeing the entire structure that assigns each pointer to NULL after each free (or cr_free as the case may be), but if I use this freeing function in the callback for cr_make_param_array, the test segfaults when run with -j2 or greater, but runs fine with -j1.
Here is the smallest code i managed to reduce the problem into:
#include <criterion/criterion.h>
#include <criterion/parameterized.h>
struct Bar {
int val;
};
struct Foo {
struct Bar *bar;
};
struct Parameters {
struct Foo *expected;
};
void free_foo(struct Foo **foo_ptr) {
struct Foo *foo = *foo_ptr;
cr_free(foo->bar);
foo->bar = NULL;
cr_free(foo);
*foo_ptr = NULL;
}
void free_params(struct criterion_test_params *ctp) {
for (int i = 0; i < ctp->length; i++) {
struct Parameters *param = (struct Parameters *) ctp->params + i;
free_foo(&(param->expected));
}
cr_free(ctp->params);
}
ParameterizedTestParameters(test, test_bug_minimal) {
struct Parameters *params = cr_malloc(sizeof(struct Parameters));
params[0].expected = cr_malloc(sizeof(struct Foo));
params[0].expected->bar = cr_malloc(sizeof(struct Bar));
params[0].expected->bar->val = 42;
return cr_make_param_array(struct Parameters, params, 1, free_params);
}
ParameterizedTest(struct Parameters *params, test, test_bug_minimal) {
cr_log_info("%p %p", params->expected, params->expected->bar);
cr_assert_eq(params->expected->bar->val, 42); // SEGFAULT if j > 1
}Compiling this simply with (using clang instead didn't resolve the issue)
gcc -c src/test_bug.c -o build/test_bug.o -lcriterion
gcc build/test_bug.o -o ../bin/test_bug -lcriterion
and then running with -j1 works fine:
$ ../bin/test_bug --verbose=0 -j1
[----] Criterion v2.4.1
[====] Running 1 test from test:
[RUN ] test::test_bug_minimal
[----] 0x5931c1000058 0x5931c1000078
[PASS] test::test_bug_minimal: (0.00s)
[====] Synthesis: Tested: 1 | Passing: 1 | Failing: 0 | Crashing: 0
Both of the pointers are intact inside the test function, so everything succeeds as i would expect. However, running with -j2 or greater results in a segfault:
$ ../bin/test_bug --verbose=0 -j2
[----] Criterion v2.4.1
[====] Running 1 test from test:
[RUN ] test::test_bug_minimal
[----] 0x56184e000058 (nil)
[----] src/test_bug.c:51: Unexpected signal caught below this line!
[FAIL] test::test_bug_minimal: CRASH!
[====] Synthesis: Tested: 1 | Passing: 0 | Failing: 1 | Crashing: 1
In this case, the inner pointer got set to NULL before the test started, resulting in a segfault when accessing the fields of that struct.
I am using Ubuntu 24:04.2 on WSL 2.4:
WSL version: 2.4.13.0
Kernel version: 5.15.167.4-1
WSLg version: 1.0.65
MSRDC version: 1.2.5716
Direct3D version: 1.611.1-81528511
DXCore version: 10.0.26100.1-240331-1435.ge-release
Windows version: 10.0.22631.5039
and Criterion 2.4.1 installed via apt-get:
Package: libcriterion-dev
Status: install ok installed
Priority: optional
Section: libdevel
Installed-Size: 688
Architecture: amd64
Multi-Arch: same
Source: criterion
Version: 2.4.1-2build2
though i also tried to compile Criterion from source and it didn't help.
I have tried following the documentation and the examples as much as possible, but if i am doing something wrong, your help would be much appreciated, and if it is a bug, i am happy to provide more information. Thank you!