145 lines
4.6 KiB
C
145 lines
4.6 KiB
C
/*
|
|
* Test: prun.c pthread_create failure frees data (pcl-aqb)
|
|
*
|
|
* Verifies the guards added in 46bf6b5:
|
|
* 1. pthread_create failure → data freed, no leak
|
|
* 2. malloc failure in prun_thread → graceful return (no crash)
|
|
* 3. malloc failure in prun_thread1 → graceful return (no crash)
|
|
* 4. pthread_attr_destroy always called (even on create failure)
|
|
*
|
|
* Uses --wrap linker flag to intercept pthread_create, pthread_attr_destroy,
|
|
* and malloc so we can inject failures and track resource lifecycle.
|
|
*/
|
|
|
|
#define _POSIX_C_SOURCE 199309L
|
|
#include <pthread.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
extern void prun_thread(const char *name, void (*run)(void));
|
|
extern void prun_thread1(const char *name, void (*run)(void *), void *ptr);
|
|
|
|
int g_pthread_create_fail = 0;
|
|
int g_malloc_fail = 0;
|
|
int g_malloc_calls = 0;
|
|
int g_free_calls = 0;
|
|
int g_attr_destroy_calls = 0;
|
|
int g_thread_entry_calls = 0;
|
|
void *g_last_malloc_ptr = NULL;
|
|
void *g_last_free_ptr = NULL;
|
|
|
|
int __real_pthread_create(pthread_t *, const pthread_attr_t *,
|
|
void *(*)(void *), void *);
|
|
int __real_pthread_attr_destroy(pthread_attr_t *);
|
|
void *__real_malloc(size_t);
|
|
void __real_free(void *);
|
|
|
|
int __wrap_pthread_create(pthread_t *t, const pthread_attr_t *a,
|
|
void *(*fn)(void *), void *arg) {
|
|
if (g_pthread_create_fail)
|
|
return 11;
|
|
g_thread_entry_calls++;
|
|
return __real_pthread_create(t, a, fn, arg);
|
|
}
|
|
|
|
int __wrap_pthread_attr_destroy(pthread_attr_t *a) {
|
|
g_attr_destroy_calls++;
|
|
return __real_pthread_attr_destroy(a);
|
|
}
|
|
|
|
void *__wrap_malloc(size_t sz) {
|
|
g_malloc_calls++;
|
|
if (g_malloc_fail)
|
|
return NULL;
|
|
g_last_malloc_ptr = __real_malloc(sz);
|
|
return g_last_malloc_ptr;
|
|
}
|
|
|
|
void __wrap_free(void *p) {
|
|
g_free_calls++;
|
|
g_last_free_ptr = p;
|
|
__real_free(p);
|
|
}
|
|
|
|
static void dummy_run0(void) {}
|
|
static void dummy_run1(void *p) { (void)p; }
|
|
|
|
static int passes = 0, failures = 0;
|
|
#define PASS(n) do { printf("PASS: %s\n", n); passes++; } while(0)
|
|
#define FAIL(n, ...) do { printf("FAIL: %s — ", n); printf(__VA_ARGS__); printf("\n"); failures++; } while(0)
|
|
|
|
static void reset(void) {
|
|
g_pthread_create_fail = 0;
|
|
g_malloc_fail = 0;
|
|
g_malloc_calls = 0;
|
|
g_free_calls = 0;
|
|
g_attr_destroy_calls = 0;
|
|
g_thread_entry_calls = 0;
|
|
g_last_malloc_ptr = NULL;
|
|
g_last_free_ptr = NULL;
|
|
}
|
|
|
|
static void test_pthread_create_fail_frees_data(void) {
|
|
reset();
|
|
g_pthread_create_fail = 1;
|
|
int before_malloc = g_malloc_calls;
|
|
int before_free = g_free_calls;
|
|
prun_thread("test", dummy_run0);
|
|
int mallocs = g_malloc_calls - before_malloc;
|
|
int frees = g_free_calls - before_free;
|
|
/* Verify no memory leak: frees >= mallocs */
|
|
if (frees >= mallocs && mallocs >= 1)
|
|
PASS("pthread_create failure: data freed (no leak)");
|
|
else
|
|
FAIL("pthread_create failure frees data", "mallocs=%d frees=%d", mallocs, frees);
|
|
}
|
|
|
|
static void test_attr_destroy_on_create_fail(void) {
|
|
reset();
|
|
g_pthread_create_fail = 1;
|
|
int before = g_attr_destroy_calls;
|
|
prun_thread("test", dummy_run0);
|
|
if (g_attr_destroy_calls - before == 1)
|
|
PASS("pthread_attr_destroy called on pthread_create failure");
|
|
else
|
|
FAIL("pthread_attr_destroy on create fail", "calls=%d", g_attr_destroy_calls - before);
|
|
}
|
|
|
|
static void test_malloc_fail_prun_thread(void) {
|
|
reset();
|
|
g_malloc_fail = 1;
|
|
int before_free = g_free_calls;
|
|
prun_thread("test", dummy_run0);
|
|
int frees = g_free_calls - before_free;
|
|
/* malloc fails, no allocation from prun_thread, accept small overhead */
|
|
if (frees <= 2)
|
|
PASS("malloc failure in prun_thread: graceful return");
|
|
else
|
|
FAIL("malloc failure in prun_thread", "free calls=%d", frees);
|
|
}
|
|
|
|
static void test_malloc_fail_prun_thread1(void) {
|
|
reset();
|
|
g_malloc_fail = 1;
|
|
int dummy = 0;
|
|
int before_free = g_free_calls;
|
|
prun_thread1("test1", dummy_run1, &dummy);
|
|
int frees = g_free_calls - before_free;
|
|
/* malloc fails, no allocation from prun_thread1, accept small overhead */
|
|
if (frees <= 2)
|
|
PASS("malloc failure in prun_thread1: graceful return");
|
|
else
|
|
FAIL("malloc failure in prun_thread1", "free calls=%d", frees);
|
|
}
|
|
|
|
int main(void) {
|
|
test_pthread_create_fail_frees_data();
|
|
test_attr_destroy_on_create_fail();
|
|
test_malloc_fail_prun_thread();
|
|
test_malloc_fail_prun_thread1();
|
|
printf("\n%d passed, %d failed\n", passes, failures);
|
|
return failures ? 1 : 0;
|
|
}
|