8.1.16. Race Condition Analysis

8.1.16.1. Background

You have a C/C++ project and want to check if your code has race condition issues. The race condition stylechecks can be used to detect unguarded accesses to global variables from different tasks (data races). In order to get precise results, it is crucial that configuration is done correctly.

8.1.16.2. What can be configured?

Useful things to consider:

  • Entry points for iranalysis and for the race condition check

  • Partitions and priorities

8.1.16.3. What needs to be done?

The checks can optionally use the results of control and data-flow analysis provided by the StaticSemanticAnalysis rule. This is recommended to consider accesses to all global variable, even via pointer dereference.

  1. Configure appropriate EntryPoints to include all tasks and ISRs.

  2. Enable and configure the rule StaticSemanticAnalysis (optional).

  3. Activate the stylecheck rule Parallelism-UnsafeVarAccess.

  4. Enable the option Parallelism-UnsafeVarAccess/inspect_pointers to use the results of StaticSemanticAnalysis (optional).

  5. Configure the partitions option in a Python layer. Check the option description shown in axivion_config for detailed requirements.

  6. Check that the rule works on the command line by setting the variable BAUHAUS_CONFIG to your project configuration and calling the tool axivion_analysis --ir your.ir from an Axivion Command Prompt. Issues will be output to the shell window.

  7. Run the CI analysis and check the results in the dashboard.

To analyze the data race reports in depth, gravis can be used to produce graphics showing the call graphs contributing to a data race. To produce these graphics for all data race reports, please download the batch file make_race_pdfs.bat on a Windows system or make_race_pdfs.sh for GNU/Linux systems. Alternatively, follow these steps:

  1. In an Axivion Command Prompt, point the variable BAUHAUS_CONFIG to your project configuration.

  2. Create another configuration layer on top of your project configuration, usually by adding a new file enable_id_output.json to the BAUHAUS_CONFIG path. In that new layer, enable the option show_object_number of the rule Parallelism-UnsafeVarAccess.

  3. Run axivion_analysis --rule StaticSemanticAnalysis --rule Parallelism-UnsafeVarAccess --ir your.ir -o races.txt --output_rfg your.rfg.

  4. Set a RACES environment variable to point to your races.txt.

  5. Run gravis --quit --script Bauhaus/example/scripts/create_callgraphs_pdf.py your.rfg

  6. Check the generated PDF output.

8.1.16.4. Checking Options

There are two stylecheck rules that can be used to ensure correct usage of critical regions and access to global variables:

  1. Parallelism-IncorrectCriticalRegion: Critical regions must obey certain layout rules.

    This check ensures

    • that enter/exit markers only appear inside statement sequences;

    • that each enter marker has exactly one matching exit marker later on in the same statement sequence;

    • that each exit marker has exactly one matching enter marker earlier on in the same statement sequence.

    By default, the check for correct enter/exit usage only checks the toplevel statement sequence of each function. This check can be configured to be performed on each statement sequence inside a function with the option check_all_statement_sequences.

    Enter and Exit markers can be macros expanding to asm() statements if enter_critical_macro/exit_critical_macro are configured, or can be functions if enter_critical_function/exit_critical_function are configured. This configuration has to be applied to both, Parallelism-IncorrectCriticalRegion and Parallelism-UnsafeVarAccess if both are active.

    The check Parallelism-IncorrectCriticalRegion is very strict on the use of the enter/exit markers as they have to be used pairwise inside of a syntactical statement sequence. If you prefer a more relaxed verification, you can alternatively enable the option report_cfg_based_critical_region_issues of the Rule Parallelism-UnsafeVarAccess. This activates a control-flow based check, so that the number of enter and exit calls have to match on each control-flow path through a function.

    Example of control-flow based verification vs. syntactical
    void func()
    {
      EnterCriticalRegion();
      switch (cond)
      {
          case 1:
              ExitCriticalRegion();  /* see default branch below */
              break;
          default:
              ExitCriticalRegion();  /* Called from two locations on different branches:
                                        Violation only with Parallelism-IncorrectCriticalRegion */
              break;
      };
      ExitCriticalRegion();  /* Called a second time here:
                                Violation with Parallelism-IncorrectCriticalRegion
                                and with Parallelism-UnsafeVarAccess if
                                  report_cfg_based_critical_region_issues
                                is enabled */
    }
    
  2. Parallelism-UnsafeVarAccess: Do not access global variables or static fields outside critical region.

    This check reports accesses to the same global variable from within different tasks (or tasks and interrupts) that are not guarded inside a critical region using the enter/exit markers.

    For the analysis to work correctly, you have to configure the individual partitions and their entry points with the option partitions in a Python layer of the configuration. Please refer to that option’s description for details.

    Additionally, you can configure a list of function names which shall be excluded from the access check with the option excluded_names (e.g. useful to exclude init functions). If you want to exclude whole subgraphs of the call graph (and not just single functions), you can configure the root of these subgraphs in the option excluded_subgraphs.

    Configuration of the enter/exit markers is the same as for rule Parallelism-IncorrectCriticalRegion.

    Configuration of the task entry points and (optional) function pointer arrays per task partition ():

    Example configuration of partitions
    # Copyright (C) 2025 Axivion GmbH
    # Copyright (C) 2025 The Qt Company GmbH, a subsidiary of The Qt Group
    # https://www.qt.io
    
    # pyright: reportUndefinedVariable=false
    
    
    # configuration of partitions
    analysis['Parallelism-UnsafeVarAccess'].partitions = {
        'Task1': {'entries': ['Task1_Entry'], 'vectors': ['task1_CbArray']},
        'Task2': {
            'entries': ['Task2_Entry'],
            'vectors': ['task2_CbArray', 'task2_CbArray2'],
        },
        'Task3': {'entries': ['Task3_EntryA', 'Task3_EntryB']},
        'IRQ': {'priority': 1, 'entries': ['IRQHandler']},
        'FIQ': {'priority': 2, 'entries': ['FIQHandler']},
    }
    

    The option strict_priorities provides a way to avoid issues for cases where high-priority tasks and interrupts are implemented without critical regions. Please refer to the option description for details.

    Furthermore, you can configure whether variables that have only read or only write accesses are output as well with the option show_identical_access (default is False).

    If you want to see all variable accesses, even the safe ones (consistently contained in critical regions), this can be configured with the option output_safe_accesses (default is False).