Main Content

MISRA C:2023 Rule 22.15

Thread synchronization objects and thread-specific storage pointers shall not be destroyed until after all threads accessing them have terminated

Since R2024b

Description

This checker is deactivated in a default Polyspace® as You Code analysis. See Checkers Deactivated in Polyspace as You Code Analysis (Polyspace Access).

Rule Definition

Thread synchronization objects and thread-specific storage pointers shall not be destroyed until after all threads accessing them have terminated.

Rationale

Destroying mutex objects or thread-specific storage objects before the termination of all threads accessing these resources can lead to undefined behavior. For example:

  • The function mtx_destroy() releases the resources used by a mutex object. In this code, the thread bar() locks the mutex object myMutex and the thread foo() destroys it. The mutex object myMutex can be locked when it is destroyed by foo().

    mtx_t myMutex;
    //...
    void foo() { // Thread foo
    	//...
    	mtx_destroy(myMutex); // Can be undefined behavior
    }
    
    
    void bar() { // Thread bar
    	mtx_lock(myMutex);
    	//...
    }
    Destroying a locked mutex results in undefined behavior.

  • The function tss_delete() releases all resources used by a thread-specific storage object. In this code, the thread foo() accesses the thread specific storage object myStorage. When the thread bar() releases this storage, myStorage might still be used by the thread foo(), resulting in undefined behavior.

    tss_t myStorage;
    //...
    void foo() { // Thread foo
    	tss_set(myStorage, malloc(4));
        //...
    }
    
    
    void bar() { // Thread bar
    	//..
    	tss_delete(myStorage);      // Can be undefined behavior
    }

  • The function cnd_destroy() destroys condition variables. In this code, the threads foo() and bar() use the condition variable myCV. When the thread bar() destroys myCV, the condition variable might still be used by the thread foo(), resulting in undefined behavior.

    mtx_t myMutex;
    cnd_t myCV;
    //...
    void foo() { // Thread foo
    	//...
    	cnd_wait(&myCV, &myMutex);
    	//...
    }
    
    
    void bar() { // Thread bar
    	//..
        cnd_wait(&myCV, &myMutex);
        //..
    	cnd_destroy(&cond_var);      // Can be undefined behavior
    }

To avoid such instances of undefined behavior, check that all threads have been terminated before destroying shared resources. Alternatively, avoid destroying shared resources at all.

Polyspace Implementation

Polyspace reports a violation of this rule when a task destroys a mutex object after it is locked and before it is unlocked. The locking and destruction can happen in the same task or different tasks. A task is a function that you specify as an entry point using the option Tasks (-entry-points).

On the Result Details pane, you see two events, the locking and destruction of the mutex, and the tasks that initiate the events. To navigate to the corresponding line in your source code, click the event.

Troubleshooting

If you expect a rule violation but do not see it, refer to Diagnose Why Coding Standard Violations Do Not Appear as Expected.

Examples

expand all

In this example, the mutex object mutexB is locked by the thread worker1(). This mutex object is then destroyed by worker2(). The thread worker1() might still be blocked by mutexB when worker2() destroys the mutex object, which results in undefined behavior. Polyspace reports a violation.

#include <stdio.h>
#include <threads.h>
void doSomeWork(void);
// Mutex declaration
mtx_t mutexA, mutexB;

// Worker function 1
int worker1(void *arg) {
	printf("Worker 1 is trying to lock the mutex.\n");
	mtx_lock(&mutexA);
	mtx_lock(&mutexB);
	printf("Worker 1 has locked the mutex.\n");
	// Work
	doSomeWork();
	printf("Worker 1 is unlocking the mutex.\n");
	mtx_unlock(&mutexA);
	return 0;
}

// Worker function 2
int worker2(void *arg) {
	printf("Worker 2 is trying to lock the mutex.\n");
	mtx_lock(&mutexA);
	printf("Worker 2 has locked the mutex.\n");
	// Work
	doSomeWork() ;
	printf("Worker 2 is unlocking the mutex.\n");
	mtx_unlock(&mutexA);
	// destroy mutexB
	mtx_destroy(&mutexB); /* Noncompliant */
	return 0;
}

// Main function
int main() {
	thrd_t t1, t2;

	// Initialize mutex
	mtx_init(&mutexA, mtx_plain);
	mtx_init(&mutexB, mtx_plain);

	// Create worker threads
	if(thrd_create(&t1, worker1, NULL) != thrd_success) {
		printf("Failed to create thread 1.\n");
		return 1;
	}
	if(thrd_create(&t2, worker2, NULL) != thrd_success) {
		printf("Failed to create thread 2.\n");
		return 1;
	}

	// Wait for threads to finish
	thrd_join(t1, NULL);
	thrd_join(t2, NULL);



	return 0;
}

For this example, specify worker1 and worker2 as entry-point functions. In the Polyspace user interface, specify these options:

Alternatively, at the command line, use this command:

polyspace-bug-finder -entry-points worker1,worker2

Check Information

Group: Resources
Category: Required
AGC Category: Required

Version History

Introduced in R2024b