Main Content

Automatic or thread local variable escaping from a thread

Variable is passed from one thread to another without ensuring that variable stays alive through duration of latter thread

Since R2020a

Description

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

This defect occurs when an automatic or thread local variable is passed by address from one thread to another thread without ensuring that the variable stays alive through the duration of the latter thread.

The defect checker applies to both C11 and POSIX® threads.

Risk

An automatic or thread local variable is allocated on the stack at the beginning of a thread and its lifetime extends till the end of the thread. The variable is not guaranteed to be alive when a different thread accesses it.

For instance, consider the start function of a C11 thread with these lines:

int start_thread(thrd_t *tid) {
   int aVar = 0;
   if(thrd_success != thrd_create(tid, start_thread_child, &aVar) {
     ...
   }
}

The thrd_create function creates a child thread with start function start_thread_child and passes the address of the automatic variable aVar to this function. When this child thread accesses aVar, the parent thread might have completed execution and aVar is no longer on the stack. The access might result in reading unpredictable values.

Fix

When you pass a variable from one thread to another, make sure that the variable lifetime matches or exceeds the lifetime of both threads. You can achieve this synchronization in one of these ways:

  • Declare the variable static so that it does not go out of stack when the current thread completes execution.

  • Dynamically allocate the storage for the variable so that it is allocated on the heap instead of the stack and must be explicitly deallocated. Make sure that the deallocation happens after both threads complete execution.

These solutions require you to create a variable in nonlocal memory. Instead, you can use other solutions such as the shared keyword with OpenMP's threading interface that allows you to safely share local variables across threads.

Examples

expand all

#include <threads.h>
#include <stdio.h>

int create_child_thread(void *childVal) {
  int *res = (int *)childVal;
  printf("Result: %d\n", *res);
  return 0;
}

void create_parent_thread(thrd_t *tid, int *parentPtr) {
   if (thrd_success != thrd_create(tid, create_child_thread, parentPtr)) {
    /* Handle error */
  }
}

int main(void) {
  thrd_t tid;
  int parentVal = 1;
  
  create_parent_thread(&tid, &parentVal);


  if (thrd_success != thrd_join(tid, NULL)) {
    /* Handle error */
  }
  return 0;
}

In this example, the value parentVal is local to the parent thread that starts in main and continues into the function create_parent_thread. However, in the body of create_parent_thread, the address of this local variable is passed to a child thread (the thread with start routine create_child_thread). The parent thread might have completed execution and the variable parentVal might have gone out of scope when the child thread accesses this variable.

The same issue appears if the variable is declared as thread-local, for instance with the C11 keyword _Thread_local (or thread_local):

_Thread_local int parentVal = 1;

Correction – Use Static Variables

One possible correction is to declare the variable parentVal as static so that the variable is on the stack for the entire duration of the program.

#include <threads.h>
#include <stdio.h>

int create_child_thread(void *childVal) {
  int *res = (int *)childVal;
  printf("Result: %d\n", *res);
  return 0;
}

void create_parent_thread(thrd_t *tid, int *parentPtr) {
   if (thrd_success != thrd_create(tid, create_child_thread, parentPtr)) {
    /* Handle error */
  }
}

int main(void) {
  thrd_t tid;
  static int parentVal = 1;
  
  create_parent_thread(&tid, &parentVal);


  if (thrd_success != thrd_join(tid, NULL)) {
    /* Handle error */
  }
  return 0;
}
Correction – Use Dynamic Memory Allocation

Another possible correction is to dynamically allocate storage for variables to be shared across threads and explicitly free the storage after the threads complete execution.

#include <threads.h>
#include <stdio.h>

int create_child_thread(void *childVal) {
  int *res = (int *)childVal;
  printf("Result: %d\n", *res);
  return 0;
}

void create_parent_thread(thrd_t *tid, int *parentPtr) {
   if (thrd_success != thrd_create(tid, create_child_thread, parentPtr)) {
    /* Handle error */
  }
}

int main(void) {
  thrd_t tid;
  int parentPtr = (int*) malloc(sizeof(int));
  
  if(parentPtr) {
      create_parent_thread(&tid, parentPtr);
     

      if (thrd_success != thrd_join(tid, NULL)) {
        /* Handle error */
      }
      free(parentPtr);
  }
  return 0;
}

Result Information

Group: Concurrency
Language: C | C++
Default: Off
Command-Line Syntax: LOCAL_ADDR_ESCAPE_THREAD
Impact: Medium

Version History

Introduced in R2020a