DOI: 10.5281/zenodo.14864784
title: Detecting Data Races in Language Virtual Machines with RaceHunter. Lessons Learned. Artifacts.

description: Below are example programs, i.e. not real code, each illustrating a data race-related issue.

region_allocator

The test is based on a VM allocator implementation. The main function creates multiple threads that intensively allocate memory using the allocator while simulating workload.

The AllocRegular function first attempts to allocate memory atomically within the current memory region (Alloc<true>, line 159). If atomic allocation fails, it creates a new memory region (line 173) under mutex lock protection. The function then allocates memory non-atomically (Alloc<false>, line 175) and sets the newly allocated region as the current one (line 176).

A potential issue arises due to weak memory models: assigning a new memory region as the current one may happen before the non-atomic allocation within that region is completed. If two threads call AllocRegular simultaneously, a race condition may occur—one thread performing atomic allocation (Alloc<true>) while the other performs non-atomic allocation (Alloc<false>). This leads to concurrent writes to the top_ pointer — one atomic (line 76) and one non-atomic (line 84) — potentially resulting in an inconsistent pointer value.

release_under_conditions

The test is based on a VM thread manager implementation. The main thread performs initialization and then calls the thread0 function, which simulates a thread manager by creating and destroying threads within the VM.

In this test, thread0 creates a daemon thread (thread1), then shuts it down by setting a special flag (stopped = true) and waiting for its state to transition to TERMINATED or SUSPENDED. Once this condition is met, the associated data is deallocated.

The daemon thread simulates typical execution by cycling through states such as RUNNING, BLOCKED, WAITING, and SUSPENDED while periodically passing through safepoints. If the daemon detects stopped = true at a safepoint, it terminates and releases the data.

A bug occurs when the daemon thread exits in the SUSPENDED state (line 78). In this scenario, memory deallocation may happen before the daemon releases the data, resulting in a use-after-free bug.

To assist the tool in detecting this issue, the test includes specific annotations: SMCEffectStart/SMCEffectEnd and SMCInfoStart/SMCInfoEnd.





Effect operations indicate actions that may impact other threads.




Info operations specify a point and state where effects should be applied.



When reaching an SMCEffectStart point, the tool waits until a corresponding Info with a new state value is found. Once detected, the affected thread is suspended, and execution control switches to the Effect operation. After reaching SMCEffectEnd, control returns to the Info point, ensuring that the Effect is successfully applied before the corresponding Info point in the new state.

           *EffectAction*  *InfoAction*
                 |            |
                 |            |
 EffectStart [  wait <----- signal (if isNew) ] InfoStart
                 |            +               ]
 EffectEnd   [ signal -----> wait             ]
             [   +            |
             [ wait  <----- signal            ] InfoEnd

Let's return to the test. The operation stopped = true is marked as an Effect operation, prompting the tool to switch to thread1 before executing it. When thread1 reaches a safepoint, it detects the corresponding Info point, executes the Effect operation, and sets stopped = true. Immediately after, control switches back to thread1, which then enters the if condition (line 38), and completes the Info operation. Following this, thread0 resumes execution.

The tool iterates through Effect-Info sequences across different thread states. If a state is repeated, signal-wait synchronization is no longer triggered. For RUNNING, BLOCKED, and WAITING states, no issues are detected. However, when analyzing the SUSPENDED state with the corresponding synchronization order, the tool successfully detects the data race that leads to a use-after-free error.

tsan_false_race

This example illustrates a false positive reported by a happens-before based TSAN tool. The program consists of two threads: func1 and func2.





func1 initializes some data, sets up a memory fence, and finally sets an atomic flag to indicate that the data is ready.




func2 continuously waits in a loop until the flag is set, then accesses the data.



TSAN incorrectly detects a data race on the shared data. However, no actual data race occurs because atomic_thread_fence establishes proper synchronization with atomic_load_explicit, ensuring safe memory access.

 

This work was supported by the Ministry of Economic Development of the Russian Federation (code 25-139-66879-1-0003)