Main Content

Data race on adjacent bit fields

Multiple threads perform unprotected operations on adjacent bit fields of a shared data structure

Since R2020b

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:

  1. Multiple tasks perform unprotected operations on bit fields that are part of the same structure.

    For instance, a task operates on field errorFlag1 and another task on field errorFlag2 in a variable of this type:

    struct errorFlags {
       unsigned int errorFlag1 : 1;
       unsigned int errorFlag2 : 1;
       ...
    }
    Suppose that the operations are not atomic with respect to each other. In other words, you have not implemented protection mechanisms to ensure that one operation is completed before another operation begins.

  2. At least one of the unprotected operations is a write operation.

To find this defect, before analysis, you must specify the multitasking options. To specify these options, on the Configuration pane, select Multitasking. For more information, see Configuring Polyspace Multitasking Analysis Manually.

Risk

Adjacent bit fields that are part of the same structure might be stored in one byte in the same memory location. Read or write operations on all variables including bit fields occur one byte or word at a time. To modify only specific bits in a byte, steps similar to these steps occur in sequence:

  1. The byte is loaded into RAM.

  2. A mask is created so that only specific bits are modified to the intended value and the remaining bits remain unchanged.

  3. A bitwise OR operation is performed between the copy of the byte in RAM and the mask.

  4. The byte with specific bits modified is copied back from RAM.

When you access two different bit fields, these four steps have to be performed for each bit field. If the accesses are not protected, all four steps for one bit field might not be completed before the four steps for the other bit field begin. As a result, the modification of one bit field might undo the modification of an adjacent bit field. For instance, in the preceding example, the modification of errorFlag1 and errorFlag2 can occur in the following sequence. Steps 1,2 and 5 relate to modification of errorFlag1 and while steps 3,4 and 6 relate to that of errorFlag2.

  1. The byte with both errorFlag1 and errorFlag2 unmodified is copied into RAM, for purposes of modifying errorFlag1.

  2. A mask that modifies only errorFlag1 is bitwise OR-ed with this copy.

  3. The byte containing both errorFlag1 and errorFlag2 unmodified is copied into RAM a second time, for purposes of modifying errorFlag2.

  4. A mask that modifies only errorFlag2 is bitwise OR-ed with this second copy.

  5. The version with errorFlag1 modified is copied back. This version has errorFlag2 unmodified.

  6. The version with errorFlag2 modified is copied back. This version has errorFlag1 unmodified and overwrites the previous modification.

Fix

To fix this defect, protect the operations on bit fields that are part of the same structure by using critical sections, temporal exclusion, or another means. See Protections for Shared Variables in Multitasking Code.

To identify existing protections that you can reuse, see the table and graphs associated with the result. The table shows each pair of conflicting calls. The Access Protections column shows existing protections on the calls. To see the function call sequence leading to the conflicts, click the icon.

Examples

expand all

typedef struct
{
   unsigned int IOFlag :1;
   unsigned int InterruptFlag :1;
   unsigned int Register1Flag :1;
   unsigned int SignFlag :1;
   unsigned int SetupFlag :1;
   unsigned int Register2Flag :1;
   unsigned int ProcessorFlag :1;
   unsigned int GeneralFlag :1;
} InterruptConfigbits_t;

InterruptConfigbits_t InterruptConfigbitsProc12;

void task1 (void) {
    InterruptConfigbitsProc12.IOFlag = 0;
}

void task2 (void) {
    InterruptConfigbitsProc12.SetupFlag = 0;
}

In this example, task1 and task2 access different bit fields IOFlag and SetupFlag, which belong to the same structured variable InterruptConfigbitsProc12.

To emulate multitasking behavior, specify the options listed in this table.

OptionSpecification
Configure multitasking manually
Tasks

task1

task2

At the command-line, use:

 polyspace-bug-finder 
   -entry-points task1,task2

Correction – Use Critical Sections

One possible correction is to wrap the bit field access in a critical section. A critical section lies between a call to a lock function and an unlock function. In this correction, the critical section lies between the calls to functions begin_critical_section and end_critical_section.

typedef struct
{
   unsigned int IOFlag :1;
   unsigned int InterruptFlag :1;
   unsigned int Register1Flag :1;
   unsigned int SignFlag :1;
   unsigned int SetupFlag :1;
   unsigned int Register2Flag :1;
   unsigned int ProcessorFlag :1;
   unsigned int GeneralFlag :1;
} InterruptConfigbits_t;

InterruptConfigbits_t InterruptConfigbitsProc12;

void begin_critical_section(void);
void end_critical_section(void);

void task1 (void) {
    begin_critical_section();
    InterruptConfigbitsProc12.IOFlag = 0;
    end_critical_section();
}

void task2 (void) {
    begin_critical_section();
    InterruptConfigbitsProc12.SetupFlag = 0;
    end_critical_section();
}

In this example, to emulate multitasking behavior, specify options listed in this table.

OptionSpecification
Configure multitasking manually
Tasks

task1

task2

Critical section detailsStarting routineEnding routine
begin_critical_sectionend_critical_section

At the command-line, use:

 polyspace-bug-finder 
   -entry-points task1,task2
   -critical-section-begin begin_critical_section:cs1
   -critical-section-end end_critical_section:cs1

Correction – Avoid Bit Fields

If you do not have memory constraints, use the char data type instead of bit fields. The char variables in a structure occupy at least one byte and do not have the thread-safety issues that come from bit manipulations in a byte-sized operation. Data races do not result from unprotected operations on different char variables that are part of the same structure.

typedef struct
{
   unsigned char IOFlag;
   unsigned char InterruptFlag;
   unsigned char Register1Flag;
   unsigned char SignFlag;
   unsigned char SetupFlag;
   unsigned char Register2Flag;
   unsigned char ProcessorFlag;
   unsigned char GeneralFlag;
} InterruptConfigbits_t;

InterruptConfigbits_t InterruptConfigbitsProc12;

void task1 (void) {
    InterruptConfigbitsProc12.IOFlag = 0;
}

void task2 (void) {
    InterruptConfigbitsProc12.SetupFlag = 0;
}

Though the checker does not flag this correction, do not use this correction for C99 or earlier. Only from C11 and later does the C Standard mandate that distinct char variables cannot be accessed using the same word.

Correction – Insert Bit Field of Size 0

You can enter a non-bit field member or an unnamed bit field member of size 0 between two adjacent bit fields that might be accessed concurrently. A non-bit field member or size 0 bit field member ensures that the subsequent bit field starts from a new memory location. In this corrected example, the size 0 bit field member ensures that IOFlag and SetupFlag are stored in distinct memory locations.

typedef struct
{
   unsigned int IOFlag :1;
   unsigned int InterruptFlag :1;
   unsigned int Register1Flag :1;
   unsigned int SignFlag :1;
   unsigned int : 0;
   unsigned int SetupFlag :1;
   unsigned int Register2Flag :1;
   unsigned int ProcessorFlag :1;
   unsigned int GeneralFlag :1;
} InterruptConfigbits_t;

InterruptConfigbits_t InterruptConfigbitsProc12;

void task1 (void) {
    InterruptConfigbitsProc12.IOFlag = 0;
}

void task2 (void) {
    InterruptConfigbitsProc12.SetupFlag = 0;
}

Result Information

Group: Concurrency
Language: C | C++
Default: On
Command-Line Syntax: DATA_RACE_BIT_FIELDS
Impact: High

Version History

Introduced in R2020b